Merge pull request #721 from zenorocha/feature-prettier

This commit is contained in:
Vitor Alencar 2021-01-21 17:32:52 +01:00 committed by GitHub
commit 221efae529
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 8650 additions and 856 deletions

View File

@ -7,7 +7,7 @@ root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 4
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf

9
.prettierignore Normal file
View File

@ -0,0 +1,9 @@
# Ignore artifacts:
dist
lib
npm-debug.log
bower_components
node_modules
yarn-error.log
yarn.lock

9
.prettierrc.json Normal file
View File

@ -0,0 +1,9 @@
{
"printWidth": 80,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always"
}

View File

@ -14,9 +14,5 @@
"/src",
"/lib"
],
"keywords": [
"clipboard",
"copy",
"cut"
]
"keywords": ["clipboard", "copy", "cut"]
}

View File

@ -24,5 +24,6 @@ Implement your bug fix or feature, write tests to cover it and make sure all tes
Documentation is extremely important and takes a fair deal of time and effort to write and keep updated. Please submit any and all improvements you can make to the repository's docs.
## Known issues
If you're using npm@3 you'll probably face some issues related to peerDependencies.
https://github.com/npm/npm/issues/9204

View File

@ -1,14 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>constructor-node</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<div id="btn" data-clipboard-text="1">
<span>Copy</span>
<span>Copy</span>
</div>
<!-- 2. Include library -->
@ -16,16 +16,16 @@
<!-- 3. Instantiate clipboard by passing a HTML element -->
<script>
var btn = document.getElementById('btn');
var clipboard = new ClipboardJS(btn);
var btn = document.getElementById('btn');
var clipboard = new ClipboardJS(btn);
clipboard.on('success', function(e) {
clipboard.on('success', function (e) {
console.log(e);
});
});
clipboard.on('error', function(e) {
clipboard.on('error', function (e) {
console.log(e);
});
});
</script>
</body>
</body>
</html>

View File

@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>constructor-nodelist</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<button data-clipboard-text="1">Copy</button>
<button data-clipboard-text="2">Copy</button>
@ -16,16 +16,16 @@
<!-- 3. Instantiate clipboard by passing a list of HTML elements -->
<script>
var btns = document.querySelectorAll('button');
var clipboard = new ClipboardJS(btns);
var btns = document.querySelectorAll('button');
var clipboard = new ClipboardJS(btns);
clipboard.on('success', function(e) {
clipboard.on('success', function (e) {
console.log(e);
});
});
clipboard.on('error', function(e) {
clipboard.on('error', function (e) {
console.log(e);
});
});
</script>
</body>
</body>
</html>

View File

@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>constructor-selector</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<button class="btn" data-clipboard-text="1">Copy</button>
<button class="btn" data-clipboard-text="2">Copy</button>
@ -16,15 +16,15 @@
<!-- 3. Instantiate clipboard by passing a string selector -->
<script>
var clipboard = new ClipboardJS('.btn');
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
clipboard.on('success', function (e) {
console.log(e);
});
});
clipboard.on('error', function(e) {
clipboard.on('error', function (e) {
console.log(e);
});
});
</script>
</body>
</body>
</html>

View File

@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>function-target</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<button class="btn">Copy</button>
<div>hello</div>
@ -15,19 +15,19 @@
<!-- 3. Instantiate clipboard -->
<script>
var clipboard = new ClipboardJS('.btn', {
target: function() {
return document.querySelector('div');
}
});
var clipboard = new ClipboardJS('.btn', {
target: function () {
return document.querySelector('div');
},
});
clipboard.on('success', function(e) {
clipboard.on('success', function (e) {
console.log(e);
});
});
clipboard.on('error', function(e) {
clipboard.on('error', function (e) {
console.log(e);
});
});
</script>
</body>
</body>
</html>

View File

@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>function-text</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<button class="btn">Copy</button>
@ -14,19 +14,19 @@
<!-- 3. Instantiate clipboard -->
<script>
var clipboard = new ClipboardJS('.btn', {
text: function() {
return 'to be or not to be';
}
});
var clipboard = new ClipboardJS('.btn', {
text: function () {
return 'to be or not to be';
},
});
clipboard.on('success', function(e) {
clipboard.on('success', function (e) {
console.log(e);
});
});
clipboard.on('error', function(e) {
clipboard.on('error', function (e) {
console.log(e);
});
});
</script>
</body>
</body>
</html>

View File

@ -1,29 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>target-div</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<div>hello</div>
<button class="btn" data-clipboard-action="copy" data-clipboard-target="div">Copy</button>
<button
class="btn"
data-clipboard-action="copy"
data-clipboard-target="div"
>
Copy
</button>
<!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script>
<!-- 3. Instantiate clipboard -->
<script>
var clipboard = new ClipboardJS('.btn');
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
clipboard.on('success', function (e) {
console.log(e);
});
});
clipboard.on('error', function(e) {
clipboard.on('error', function (e) {
console.log(e);
});
});
</script>
</body>
</body>
</html>

View File

@ -1,29 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>target-input</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<input id="foo" type="text" value="hello">
<button class="btn" data-clipboard-action="copy" data-clipboard-target="#foo">Copy</button>
<input id="foo" type="text" value="hello" />
<button
class="btn"
data-clipboard-action="copy"
data-clipboard-target="#foo"
>
Copy
</button>
<!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script>
<!-- 3. Instantiate clipboard -->
<script>
var clipboard = new ClipboardJS('.btn');
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
clipboard.on('success', function (e) {
console.log(e);
});
});
clipboard.on('error', function(e) {
clipboard.on('error', function (e) {
console.log(e);
});
});
</script>
</body>
</body>
</html>

View File

@ -1,29 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>target-textarea</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<textarea id="bar">hello</textarea>
<button class="btn" data-clipboard-action="cut" data-clipboard-target="#bar">Cut</button>
<button
class="btn"
data-clipboard-action="cut"
data-clipboard-target="#bar"
>
Cut
</button>
<!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script>
<!-- 3. Instantiate clipboard -->
<script>
var clipboard = new ClipboardJS('.btn');
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
clipboard.on('success', function (e) {
console.log(e);
});
});
clipboard.on('error', function(e) {
clipboard.on('error', function (e) {
console.log(e);
});
});
</script>
</body>
</body>
</html>

56
dist/clipboard.js vendored
View File

@ -71,7 +71,7 @@ var ClipboardAction = /*#__PURE__*/function () {
this.target = options.target;
this.text = options.text;
this.trigger = options.trigger;
this.selectedText = '';
this.selectedText = "";
}
/**
* Decides which selection strategy is going to be applied based
@ -97,28 +97,28 @@ var ClipboardAction = /*#__PURE__*/function () {
value: function selectFake() {
var _this = this;
var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
var isRTL = document.documentElement.getAttribute("dir") == "rtl";
this.removeFake();
this.fakeHandlerCallback = function () {
return _this.removeFake();
};
this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;
this.fakeElem = document.createElement('textarea'); // Prevent zooming on iOS
this.fakeHandler = this.container.addEventListener("click", this.fakeHandlerCallback) || true;
this.fakeElem = document.createElement("textarea"); // Prevent zooming on iOS
this.fakeElem.style.fontSize = '12pt'; // Reset box model
this.fakeElem.style.fontSize = "12pt"; // Reset box model
this.fakeElem.style.border = '0';
this.fakeElem.style.padding = '0';
this.fakeElem.style.margin = '0'; // Move element out of screen horizontally
this.fakeElem.style.border = "0";
this.fakeElem.style.padding = "0";
this.fakeElem.style.margin = "0"; // Move element out of screen horizontally
this.fakeElem.style.position = 'absolute';
this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically
this.fakeElem.style.position = "absolute";
this.fakeElem.style[isRTL ? "right" : "left"] = "-9999px"; // Move element to the same position vertically
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
this.fakeElem.style.top = "".concat(yPosition, "px");
this.fakeElem.setAttribute('readonly', '');
this.fakeElem.setAttribute("readonly", "");
this.fakeElem.value = this.text;
this.container.appendChild(this.fakeElem);
this.selectedText = select_default()(this.fakeElem);
@ -133,7 +133,7 @@ var ClipboardAction = /*#__PURE__*/function () {
key: "removeFake",
value: function removeFake() {
if (this.fakeHandler) {
this.container.removeEventListener('click', this.fakeHandlerCallback);
this.container.removeEventListener("click", this.fakeHandlerCallback);
this.fakeHandler = null;
this.fakeHandlerCallback = null;
}
@ -178,7 +178,7 @@ var ClipboardAction = /*#__PURE__*/function () {
}, {
key: "handleResult",
value: function handleResult(succeeded) {
this.emitter.emit(succeeded ? 'success' : 'error', {
this.emitter.emit(succeeded ? "success" : "error", {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
@ -216,10 +216,10 @@ var ClipboardAction = /*#__PURE__*/function () {
}, {
key: "action",
set: function set() {
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "copy";
this._action = action;
if (this._action !== 'copy' && this._action !== 'cut') {
if (this._action !== "copy" && this._action !== "cut") {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
}
}
@ -241,12 +241,12 @@ var ClipboardAction = /*#__PURE__*/function () {
key: "target",
set: function set(target) {
if (target !== undefined) {
if (target && _typeof(target) === 'object' && target.nodeType === 1) {
if (this.action === 'copy' && target.hasAttribute('disabled')) {
if (target && _typeof(target) === "object" && target.nodeType === 1) {
if (this.action === "copy" && target.hasAttribute("disabled")) {
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
}
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
if (this.action === "cut" && (target.hasAttribute("readonly") || target.hasAttribute("disabled"))) {
throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
}
@ -340,10 +340,10 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
key: "resolveOptions",
value: function resolveOptions() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
this.text = typeof options.text === 'function' ? options.text : this.defaultText;
this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;
this.action = typeof options.action === "function" ? options.action : this.defaultAction;
this.target = typeof options.target === "function" ? options.target : this.defaultTarget;
this.text = typeof options.text === "function" ? options.text : this.defaultText;
this.container = clipboard_typeof(options.container) === "object" ? options.container : document.body;
}
/**
* Adds a click event listener to the passed trigger.
@ -355,7 +355,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
value: function listenClick(trigger) {
var _this2 = this;
this.listener = listen_default()(trigger, 'click', function (e) {
this.listener = listen_default()(trigger, "click", function (e) {
return _this2.onClick(e);
});
}
@ -390,7 +390,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
}, {
key: "defaultAction",
value: function defaultAction(trigger) {
return getAttributeValue('action', trigger);
return getAttributeValue("action", trigger);
}
/**
* Default `target` lookup function.
@ -400,7 +400,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
}, {
key: "defaultTarget",
value: function defaultTarget(trigger) {
var selector = getAttributeValue('target', trigger);
var selector = getAttributeValue("target", trigger);
if (selector) {
return document.querySelector(selector);
@ -420,7 +420,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
* @param {Element} trigger
*/
value: function defaultText(trigger) {
return getAttributeValue('text', trigger);
return getAttributeValue("text", trigger);
}
/**
* Destroy lifecycle.
@ -439,8 +439,8 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
}], [{
key: "isSupported",
value: function isSupported() {
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];
var actions = typeof action === 'string' ? [action] : action;
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ["copy", "cut"];
var actions = typeof action === "string" ? [action] : action;
var support = !!document.queryCommandSupported;
actions.forEach(function (action) {
support = support && !!document.queryCommandSupported(action);

View File

@ -1,36 +1,36 @@
var webpackConfig = require("./webpack.config.js");
var webpackConfig = require('./webpack.config.js');
module.exports = function (karma) {
karma.set({
plugins: [
"karma-webpack",
"karma-chai",
"karma-sinon",
"karma-mocha",
"karma-chrome-launcher",
],
karma.set({
plugins: [
'karma-webpack',
'karma-chai',
'karma-sinon',
'karma-mocha',
'karma-chrome-launcher',
],
frameworks: ["chai", "sinon", "mocha", "webpack"],
frameworks: ['chai', 'sinon', 'mocha', 'webpack'],
files: [
{ pattern: "src/**/*.js", watched: false },
{ pattern: "test/**/*.js", watched: false },
],
files: [
{ pattern: 'src/**/*.js', watched: false },
{ pattern: 'test/**/*.js', watched: false },
],
preprocessors: {
"src/**/*.js": ["webpack"],
"test/**/*.js": ["webpack"],
},
preprocessors: {
'src/**/*.js': ['webpack'],
'test/**/*.js': ['webpack'],
},
webpack: {
module: webpackConfig.module,
plugins: webpackConfig.plugins,
},
webpack: {
module: webpackConfig.module,
plugins: webpackConfig.plugins,
},
webpackMiddleware: {
stats: "errors-only",
},
webpackMiddleware: {
stats: 'errors-only',
},
browsers: ["ChromeHeadless"],
});
browsers: ['ChromeHeadless'],
});
};

7772
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
// Package metadata for Meteor.js.
Package.describe({
name: "zenorocha:clipboard",
summary: "Modern copy to clipboard. No Flash. Just 3kb.",
version: "2.0.6",
git: "https://github.com/zenorocha/clipboard.js"
name: 'zenorocha:clipboard',
summary: 'Modern copy to clipboard. No Flash. Just 3kb.',
version: '2.0.6',
git: 'https://github.com/zenorocha/clipboard.js',
});
Package.onUse(function(api) {
api.addFiles("dist/clipboard.js", "client");
Package.onUse(function (api) {
api.addFiles('dist/clipboard.js', 'client');
});

View File

@ -22,13 +22,16 @@
"babel-loader": "^8.2.2",
"chai": "^4.2.0",
"cross-env": "^7.0.3",
"husky": "^4.3.8",
"karma": "^6.0.0",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1",
"karma-sinon": "^1.0.4",
"karma-webpack": "^5.0.0-alpha.5",
"lint-staged": "^10.5.3",
"mocha": "^8.2.1",
"prettier": "2.2.1",
"sinon": "^9.2.3",
"uglifyjs-webpack-plugin": "^2.2.0",
"webpack": "^5.15.0",
@ -41,5 +44,13 @@
"build-watch": "webpack --watch",
"test": "karma start --single-run",
"prepublish": "npm run build"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,css,md}": "prettier --write"
}
}

View File

@ -55,11 +55,11 @@ The value you include on this attribute needs to match another's element selecto
```html
<!-- Target -->
<input id="foo" value="https://github.com/zenorocha/clipboard.js.git">
<input id="foo" value="https://github.com/zenorocha/clipboard.js.git" />
<!-- Trigger -->
<button class="btn" data-clipboard-target="#foo">
<img src="assets/clippy.svg" alt="Copy to clipboard">
<img src="assets/clippy.svg" alt="Copy to clipboard" />
</button>
```
@ -77,7 +77,7 @@ If you omit this attribute, `copy` will be used by default.
<!-- Trigger -->
<button class="btn" data-clipboard-action="cut" data-clipboard-target="#bar">
Cut to clipboard
Cut to clipboard
</button>
```
@ -91,8 +91,11 @@ Truth is, you don't even need another element to copy its content from. You can
```html
<!-- Trigger -->
<button class="btn" data-clipboard-text="Just because you can doesn't mean you should — clipboard.js">
Copy to clipboard
<button
class="btn"
data-clipboard-text="Just because you can doesn't mean you should — clipboard.js"
>
Copy to clipboard
</button>
```
@ -105,17 +108,17 @@ That's why we fire custom events such as `success` and `error` for you to listen
```js
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
clipboard.on('success', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
e.clearSelection();
e.clearSelection();
});
clipboard.on('error', function(e) {
console.error('Action:', e.action);
console.error('Trigger:', e.trigger);
clipboard.on('error', function (e) {
console.error('Action:', e.action);
console.error('Trigger:', e.trigger);
});
```
@ -135,9 +138,9 @@ For instance, if you want to dynamically set a `target`, you'll need to return a
```js
new ClipboardJS('.btn', {
target: function(trigger) {
return trigger.nextElementSibling;
}
target: function (trigger) {
return trigger.nextElementSibling;
},
});
```
@ -145,9 +148,9 @@ If you want to dynamically set a `text`, you'll return a String.
```js
new ClipboardJS('.btn', {
text: function(trigger) {
return trigger.getAttribute('aria-label');
}
text: function (trigger) {
return trigger.getAttribute('aria-label');
},
});
```
@ -155,7 +158,7 @@ For use in Bootstrap Modals or with any other library that changes the focus you
```js
new ClipboardJS('.btn', {
container: document.getElementById('modal')
container: document.getElementById('modal'),
});
```
@ -171,8 +174,8 @@ clipboard.destroy();
This library relies on both [Selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection) and [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) APIs. The first one is [supported by all browsers](https://caniuse.com/#search=selection) while the second one is supported in the following browsers.
| <img src="https://clipboardjs.com/assets/images/chrome.png" width="48px" height="48px" alt="Chrome logo"> | <img src="https://clipboardjs.com/assets/images/edge.png" width="48px" height="48px" alt="Edge logo"> | <img src="https://clipboardjs.com/assets/images/firefox.png" width="48px" height="48px" alt="Firefox logo"> | <img src="https://clipboardjs.com/assets/images/ie.png" width="48px" height="48px" alt="Internet Explorer logo"> | <img src="https://clipboardjs.com/assets/images/opera.png" width="48px" height="48px" alt="Opera logo"> | <img src="https://clipboardjs.com/assets/images/safari.png" width="48px" height="48px" alt="Safari logo"> |
|:---:|:---:|:---:|:---:|:---:|:---:|
| 42+ ✔ | 12+ ✔ | 41+ ✔ | 9+ ✔ | 29+ ✔ | 10+ ✔ |
| :-------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: |
| 42+ ✔ | 12+ ✔ | 41+ ✔ | 9+ ✔ | 29+ ✔ | 10+ ✔ |
The good news is that clipboard.js gracefully degrades if you need to support older browsers. All you have to do is show a tooltip saying `Copied!` when `success` event is called and `Press Ctrl+C to copy` when `error` event is called because the text is already selected.
@ -180,7 +183,7 @@ You can also check if clipboard.js is supported or not by running `ClipboardJS.i
## Bonus
A browser extension that adds a "copy to clipboard" button to every code block on *GitHub, MDN, Gist, StackOverflow, StackExchange, npm, and even Medium.*
A browser extension that adds a "copy to clipboard" button to every code block on _GitHub, MDN, Gist, StackOverflow, StackExchange, npm, and even Medium._
Install for [Chrome](https://chrome.google.com/webstore/detail/codecopy/fkbfebkcoelajmhanocgppanfoojcdmg) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/codecopy/).

View File

@ -5,200 +5,206 @@ import select from 'select';
* properties and then executes copy or cut operations.
*/
class ClipboardAction {
/**
* @param {Object} options
*/
constructor(options) {
this.resolveOptions(options);
this.initSelection();
/**
* @param {Object} options
*/
constructor(options) {
this.resolveOptions(options);
this.initSelection();
}
/**
* Defines base properties passed from constructor.
* @param {Object} options
*/
resolveOptions(options = {}) {
this.action = options.action;
this.container = options.container;
this.emitter = options.emitter;
this.target = options.target;
this.text = options.text;
this.trigger = options.trigger;
this.selectedText = '';
}
/**
* Decides which selection strategy is going to be applied based
* on the existence of `text` and `target` properties.
*/
initSelection() {
if (this.text) {
this.selectFake();
} else if (this.target) {
this.selectTarget();
}
}
/**
* Creates a fake textarea element, sets its value from `text` property,
* and makes a selection on it.
*/
selectFake() {
const isRTL = document.documentElement.getAttribute('dir') == 'rtl';
this.removeFake();
this.fakeHandlerCallback = () => this.removeFake();
this.fakeHandler =
this.container.addEventListener('click', this.fakeHandlerCallback) ||
true;
this.fakeElem = document.createElement('textarea');
// Prevent zooming on iOS
this.fakeElem.style.fontSize = '12pt';
// Reset box model
this.fakeElem.style.border = '0';
this.fakeElem.style.padding = '0';
this.fakeElem.style.margin = '0';
// Move element out of screen horizontally
this.fakeElem.style.position = 'absolute';
this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
// Move element to the same position vertically
let yPosition = window.pageYOffset || document.documentElement.scrollTop;
this.fakeElem.style.top = `${yPosition}px`;
this.fakeElem.setAttribute('readonly', '');
this.fakeElem.value = this.text;
this.container.appendChild(this.fakeElem);
this.selectedText = select(this.fakeElem);
this.copyText();
}
/**
* Only removes the fake element after another click event, that way
* a user can hit `Ctrl+C` to copy because selection still exists.
*/
removeFake() {
if (this.fakeHandler) {
this.container.removeEventListener('click', this.fakeHandlerCallback);
this.fakeHandler = null;
this.fakeHandlerCallback = null;
}
/**
* Defines base properties passed from constructor.
* @param {Object} options
*/
resolveOptions(options = {}) {
this.action = options.action;
this.container = options.container;
this.emitter = options.emitter;
this.target = options.target;
this.text = options.text;
this.trigger = options.trigger;
if (this.fakeElem) {
this.container.removeChild(this.fakeElem);
this.fakeElem = null;
}
}
this.selectedText = '';
/**
* Selects the content from element passed on `target` property.
*/
selectTarget() {
this.selectedText = select(this.target);
this.copyText();
}
/**
* Executes the copy operation based on the current selection.
*/
copyText() {
let succeeded;
try {
succeeded = document.execCommand(this.action);
} catch (err) {
succeeded = false;
}
/**
* Decides which selection strategy is going to be applied based
* on the existence of `text` and `target` properties.
*/
initSelection() {
if (this.text) {
this.selectFake();
}
else if (this.target) {
this.selectTarget();
}
this.handleResult(succeeded);
}
/**
* Fires an event based on the copy operation result.
* @param {Boolean} succeeded
*/
handleResult(succeeded) {
this.emitter.emit(succeeded ? 'success' : 'error', {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this),
});
}
/**
* Moves focus away from `target` and back to the trigger, removes current selection.
*/
clearSelection() {
if (this.trigger) {
this.trigger.focus();
}
document.activeElement.blur();
window.getSelection().removeAllRanges();
}
/**
* Creates a fake textarea element, sets its value from `text` property,
* and makes a selection on it.
*/
selectFake() {
const isRTL = document.documentElement.getAttribute('dir') == 'rtl';
/**
* Sets the `action` to be performed which can be either 'copy' or 'cut'.
* @param {String} action
*/
set action(action = 'copy') {
this._action = action;
this.removeFake();
this.fakeHandlerCallback = () => this.removeFake();
this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;
this.fakeElem = document.createElement('textarea');
// Prevent zooming on iOS
this.fakeElem.style.fontSize = '12pt';
// Reset box model
this.fakeElem.style.border = '0';
this.fakeElem.style.padding = '0';
this.fakeElem.style.margin = '0';
// Move element out of screen horizontally
this.fakeElem.style.position = 'absolute';
this.fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px';
// Move element to the same position vertically
let yPosition = window.pageYOffset || document.documentElement.scrollTop;
this.fakeElem.style.top = `${yPosition}px`;
this.fakeElem.setAttribute('readonly', '');
this.fakeElem.value = this.text;
this.container.appendChild(this.fakeElem);
this.selectedText = select(this.fakeElem);
this.copyText();
if (this._action !== 'copy' && this._action !== 'cut') {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
}
}
/**
* Only removes the fake element after another click event, that way
* a user can hit `Ctrl+C` to copy because selection still exists.
*/
removeFake() {
if (this.fakeHandler) {
this.container.removeEventListener('click', this.fakeHandlerCallback);
this.fakeHandler = null;
this.fakeHandlerCallback = null;
/**
* Gets the `action` property.
* @return {String}
*/
get action() {
return this._action;
}
/**
* Sets the `target` property using an element
* that will be have its content copied.
* @param {Element} target
*/
set target(target) {
if (target !== undefined) {
if (target && typeof target === 'object' && target.nodeType === 1) {
if (this.action === 'copy' && target.hasAttribute('disabled')) {
throw new Error(
'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'
);
}
if (this.fakeElem) {
this.container.removeChild(this.fakeElem);
this.fakeElem = null;
}
}
/**
* Selects the content from element passed on `target` property.
*/
selectTarget() {
this.selectedText = select(this.target);
this.copyText();
}
/**
* Executes the copy operation based on the current selection.
*/
copyText() {
let succeeded;
try {
succeeded = document.execCommand(this.action);
}
catch (err) {
succeeded = false;
if (
this.action === 'cut' &&
(target.hasAttribute('readonly') || target.hasAttribute('disabled'))
) {
throw new Error(
'Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'
);
}
this.handleResult(succeeded);
this._target = target;
} else {
throw new Error('Invalid "target" value, use a valid Element');
}
}
}
/**
* Fires an event based on the copy operation result.
* @param {Boolean} succeeded
*/
handleResult(succeeded) {
this.emitter.emit(succeeded ? 'success' : 'error', {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
}
/**
* Gets the `target` property.
* @return {String|HTMLElement}
*/
get target() {
return this._target;
}
/**
* Moves focus away from `target` and back to the trigger, removes current selection.
*/
clearSelection() {
if (this.trigger) {
this.trigger.focus();
}
document.activeElement.blur();
window.getSelection().removeAllRanges();
}
/**
* Sets the `action` to be performed which can be either 'copy' or 'cut'.
* @param {String} action
*/
set action(action = 'copy') {
this._action = action;
if (this._action !== 'copy' && this._action !== 'cut') {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
}
}
/**
* Gets the `action` property.
* @return {String}
*/
get action() {
return this._action;
}
/**
* Sets the `target` property using an element
* that will be have its content copied.
* @param {Element} target
*/
set target(target) {
if (target !== undefined) {
if (target && typeof target === 'object' && target.nodeType === 1) {
if (this.action === 'copy' && target.hasAttribute('disabled')) {
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
}
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
}
this._target = target;
}
else {
throw new Error('Invalid "target" value, use a valid Element');
}
}
}
/**
* Gets the `target` property.
* @return {String|HTMLElement}
*/
get target() {
return this._target;
}
/**
* Destroy lifecycle.
*/
destroy() {
this.removeFake();
}
/**
* Destroy lifecycle.
*/
destroy() {
this.removeFake();
}
}
export default ClipboardAction;

View File

@ -7,129 +7,136 @@ import listen from 'good-listener';
* and instantiates a new `ClipboardAction` on each click.
*/
class Clipboard extends Emitter {
/**
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
* @param {Object} options
*/
constructor(trigger, options) {
super();
/**
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
* @param {Object} options
*/
constructor(trigger, options) {
super();
this.resolveOptions(options);
this.listenClick(trigger);
this.resolveOptions(options);
this.listenClick(trigger);
}
/**
* Defines if attributes would be resolved using internal setter functions
* or custom functions that were passed in the constructor.
* @param {Object} options
*/
resolveOptions(options = {}) {
this.action =
typeof options.action === 'function'
? options.action
: this.defaultAction;
this.target =
typeof options.target === 'function'
? options.target
: this.defaultTarget;
this.text =
typeof options.text === 'function' ? options.text : this.defaultText;
this.container =
typeof options.container === 'object' ? options.container : document.body;
}
/**
* Adds a click event listener to the passed trigger.
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
*/
listenClick(trigger) {
this.listener = listen(trigger, 'click', (e) => this.onClick(e));
}
/**
* Defines a new `ClipboardAction` on each click event.
* @param {Event} e
*/
onClick(e) {
const trigger = e.delegateTarget || e.currentTarget;
if (this.clipboardAction) {
this.clipboardAction = null;
}
/**
* Defines if attributes would be resolved using internal setter functions
* or custom functions that were passed in the constructor.
* @param {Object} options
*/
resolveOptions(options = {}) {
this.action = (typeof options.action === 'function') ? options.action : this.defaultAction;
this.target = (typeof options.target === 'function') ? options.target : this.defaultTarget;
this.text = (typeof options.text === 'function') ? options.text : this.defaultText;
this.container = (typeof options.container === 'object') ? options.container : document.body;
this.clipboardAction = new ClipboardAction({
action: this.action(trigger),
target: this.target(trigger),
text: this.text(trigger),
container: this.container,
trigger: trigger,
emitter: this,
});
}
/**
* Default `action` lookup function.
* @param {Element} trigger
*/
defaultAction(trigger) {
return getAttributeValue('action', trigger);
}
/**
* Default `target` lookup function.
* @param {Element} trigger
*/
defaultTarget(trigger) {
const selector = getAttributeValue('target', trigger);
if (selector) {
return document.querySelector(selector);
}
}
/**
* Adds a click event listener to the passed trigger.
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
*/
listenClick(trigger) {
this.listener = listen(trigger, 'click', (e) => this.onClick(e));
}
/**
* Defines a new `ClipboardAction` on each click event.
* @param {Event} e
*/
onClick(e) {
const trigger = e.delegateTarget || e.currentTarget;
if (this.clipboardAction) {
this.clipboardAction = null;
}
this.clipboardAction = new ClipboardAction({
action : this.action(trigger),
target : this.target(trigger),
text : this.text(trigger),
container : this.container,
trigger : trigger,
emitter : this
});
}
/**
* Default `action` lookup function.
* @param {Element} trigger
*/
defaultAction(trigger) {
return getAttributeValue('action', trigger);
}
/**
* Default `target` lookup function.
* @param {Element} trigger
*/
defaultTarget(trigger) {
const selector = getAttributeValue('target', trigger);
if (selector) {
return document.querySelector(selector);
}
}
/**
* Returns the support of the given action, or all actions if no action is
* given.
* @param {String} [action]
*/
static isSupported(action = ['copy', 'cut']) {
const actions = (typeof action === 'string') ? [action] : action;
let support = !!document.queryCommandSupported;
actions.forEach((action) => {
support = support && !!document.queryCommandSupported(action);
});
return support;
}
/**
* Default `text` lookup function.
* @param {Element} trigger
*/
defaultText(trigger) {
return getAttributeValue('text', trigger);
}
/**
* Destroy lifecycle.
*/
destroy() {
this.listener.destroy();
if (this.clipboardAction) {
this.clipboardAction.destroy();
this.clipboardAction = null;
}
/**
* Returns the support of the given action, or all actions if no action is
* given.
* @param {String} [action]
*/
static isSupported(action = ['copy', 'cut']) {
const actions = typeof action === 'string' ? [action] : action;
let support = !!document.queryCommandSupported;
actions.forEach((action) => {
support = support && !!document.queryCommandSupported(action);
});
return support;
}
/**
* Default `text` lookup function.
* @param {Element} trigger
*/
defaultText(trigger) {
return getAttributeValue('text', trigger);
}
/**
* Destroy lifecycle.
*/
destroy() {
this.listener.destroy();
if (this.clipboardAction) {
this.clipboardAction.destroy();
this.clipboardAction = null;
}
}
}
/**
* Helper function to retrieve attribute value.
* @param {String} suffix
* @param {Element} element
*/
function getAttributeValue(suffix, element) {
const attribute = `data-clipboard-${suffix}`;
const attribute = `data-clipboard-${suffix}`;
if (!element.hasAttribute(attribute)) {
return;
}
if (!element.hasAttribute(attribute)) {
return;
}
return element.getAttribute(attribute);
return element.getAttribute(attribute);
}
export default Clipboard;

View File

@ -2,242 +2,243 @@ import ClipboardAction from '../src/clipboard-action';
import Emitter from 'tiny-emitter';
describe('ClipboardAction', () => {
before(() => {
global.input = document.createElement('input');
global.input.setAttribute('id', 'input');
global.input.setAttribute('value', 'abc');
document.body.appendChild(global.input);
before(() => {
global.input = document.createElement('input');
global.input.setAttribute('id', 'input');
global.input.setAttribute('value', 'abc');
document.body.appendChild(global.input);
global.paragraph = document.createElement('p');
global.paragraph.setAttribute('id', 'paragraph');
global.paragraph.textContent = 'abc';
document.body.appendChild(global.paragraph);
global.paragraph = document.createElement('p');
global.paragraph.setAttribute('id', 'paragraph');
global.paragraph.textContent = 'abc';
document.body.appendChild(global.paragraph);
});
after(() => {
document.body.innerHTML = '';
});
describe('#resolveOptions', () => {
it('should set base properties', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'foo',
});
assert.property(clip, 'action');
assert.property(clip, 'container');
assert.property(clip, 'emitter');
assert.property(clip, 'target');
assert.property(clip, 'text');
assert.property(clip, 'trigger');
assert.property(clip, 'selectedText');
});
});
describe('#initSelection', () => {
it('should set the position right style property', (done) => {
// Set document direction
document.documentElement.setAttribute('dir', 'rtl');
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'foo',
});
assert.equal(clip.fakeElem.style.right, '-9999px');
done();
});
});
describe('#set action', () => {
it('should throw an error since "action" is invalid', (done) => {
try {
new ClipboardAction({
text: 'foo',
action: 'paste',
});
} catch (e) {
assert.equal(
e.message,
'Invalid "action" value, use either "copy" or "cut"'
);
done();
}
});
});
describe('#set target', () => {
it('should throw an error since "target" do not match any element', (done) => {
try {
new ClipboardAction({
target: document.querySelector('#foo'),
});
} catch (e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done();
}
});
});
describe('#selectText', () => {
it('should create a fake element and select its value', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'blah',
});
assert.equal(clip.selectedText, clip.fakeElem.value);
});
});
describe('#removeFake', () => {
it('should remove a temporary fake element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'blah',
});
clip.removeFake();
assert.equal(clip.fakeElem, null);
});
});
describe('#selectTarget', () => {
it('should select text from editable element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input'),
});
assert.equal(clip.selectedText, clip.target.value);
});
it('should select text from non-editable element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#paragraph'),
});
assert.equal(clip.selectedText, clip.target.textContent);
});
});
describe('#copyText', () => {
before(() => {
global.stub = sinon.stub(document, 'execCommand');
});
after(() => {
document.body.innerHTML = '';
global.stub.restore();
});
describe('#resolveOptions', () => {
it('should set base properties', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'foo'
});
it('should fire a success event on browsers that support copy command', (done) => {
global.stub.returns(true);
assert.property(clip, 'action');
assert.property(clip, 'container');
assert.property(clip, 'emitter');
assert.property(clip, 'target');
assert.property(clip, 'text');
assert.property(clip, 'trigger');
assert.property(clip, 'selectedText');
});
let emitter = new Emitter();
emitter.on('success', () => {
done();
});
let clip = new ClipboardAction({
emitter,
target: document.querySelector('#input'),
});
});
describe('#initSelection', () => {
it('should set the position right style property', done => {
// Set document direction
document.documentElement.setAttribute('dir', 'rtl');
it('should fire an error event on browsers that support copy command', (done) => {
global.stub.returns(false);
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'foo'
});
let emitter = new Emitter();
assert.equal(clip.fakeElem.style.right, '-9999px');
done();
});
emitter.on('error', () => {
done();
});
let clip = new ClipboardAction({
emitter,
target: document.querySelector('#input'),
});
});
});
describe('#handleResult', () => {
it('should fire a success event with certain properties', (done) => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input'),
});
clip.emitter.on('success', (e) => {
assert.property(e, 'action');
assert.property(e, 'text');
assert.property(e, 'trigger');
assert.property(e, 'clearSelection');
done();
});
clip.handleResult(true);
});
describe('#set action', () => {
it('should throw an error since "action" is invalid', done => {
try {
new ClipboardAction({
text: 'foo',
action: 'paste'
});
}
catch(e) {
assert.equal(e.message, 'Invalid "action" value, use either "copy" or "cut"');
done();
}
});
it('should fire a error event with certain properties', (done) => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input'),
});
clip.emitter.on('error', (e) => {
assert.property(e, 'action');
assert.property(e, 'trigger');
assert.property(e, 'clearSelection');
done();
});
clip.handleResult(false);
});
});
describe('#set target', () => {
it('should throw an error since "target" do not match any element', done => {
try {
new ClipboardAction({
target: document.querySelector('#foo')
});
}
catch(e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done();
}
});
describe('#clearSelection', () => {
it('should remove focus from target and text selection', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input'),
});
clip.clearSelection();
let selectedElem = document.activeElement;
let selectedText = window.getSelection().toString();
assert.equal(selectedElem, document.body);
assert.equal(selectedText, '');
});
});
describe('#selectText', () => {
it('should create a fake element and select its value', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'blah'
});
describe('#destroy', () => {
it('should destroy an existing fake element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'blah',
});
assert.equal(clip.selectedText, clip.fakeElem.value);
});
});
describe('#removeFake', () => {
it('should remove a temporary fake element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'blah'
});
clip.removeFake();
assert.equal(clip.fakeElem, null);
});
});
describe('#selectTarget', () => {
it('should select text from editable element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input')
});
assert.equal(clip.selectedText, clip.target.value);
});
it('should select text from non-editable element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#paragraph')
});
assert.equal(clip.selectedText, clip.target.textContent);
});
});
describe('#copyText', () => {
before(() => {
global.stub = sinon.stub(document, 'execCommand');
});
after(() => {
global.stub.restore();
});
it('should fire a success event on browsers that support copy command', done => {
global.stub.returns(true);
let emitter = new Emitter();
emitter.on('success', () => {
done();
});
let clip = new ClipboardAction({
emitter,
target: document.querySelector('#input')
});
});
it('should fire an error event on browsers that support copy command', done => {
global.stub.returns(false);
let emitter = new Emitter();
emitter.on('error', () => {
done();
});
let clip = new ClipboardAction({
emitter,
target: document.querySelector('#input')
});
});
});
describe('#handleResult', () => {
it('should fire a success event with certain properties', done => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input')
});
clip.emitter.on('success', (e) => {
assert.property(e, 'action');
assert.property(e, 'text');
assert.property(e, 'trigger');
assert.property(e, 'clearSelection');
done();
});
clip.handleResult(true);
});
it('should fire a error event with certain properties', done => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input')
});
clip.emitter.on('error', (e) => {
assert.property(e, 'action');
assert.property(e, 'trigger');
assert.property(e, 'clearSelection');
done();
});
clip.handleResult(false);
});
});
describe('#clearSelection', () => {
it('should remove focus from target and text selection', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input')
});
clip.clearSelection();
let selectedElem = document.activeElement;
let selectedText = window.getSelection().toString();
assert.equal(selectedElem, document.body);
assert.equal(selectedText, '');
});
});
describe('#destroy', () => {
it('should destroy an existing fake element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'blah'
});
clip.selectFake();
clip.destroy();
assert.equal(clip.fakeElem, null);
});
clip.selectFake();
clip.destroy();
assert.equal(clip.fakeElem, null);
});
});
});

View File

@ -3,130 +3,132 @@ import ClipboardAction from '../src/clipboard-action';
import listen from 'good-listener';
describe('Clipboard', () => {
before(() => {
global.button = document.createElement('button');
global.button.setAttribute('class', 'btn');
global.button.setAttribute('data-clipboard-text', 'foo');
document.body.appendChild(global.button);
global.span = document.createElement('span');
global.span.innerHTML = 'bar';
global.button.appendChild(span);
global.event = {
target: global.button,
currentTarget: global.button,
};
});
after(() => {
document.body.innerHTML = '';
});
describe('#resolveOptions', () => {
before(() => {
global.button = document.createElement('button');
global.button.setAttribute('class', 'btn');
global.button.setAttribute('data-clipboard-text', 'foo');
document.body.appendChild(global.button);
global.span = document.createElement('span');
global.span.innerHTML = 'bar';
global.button.appendChild(span);
global.event = {
target: global.button,
currentTarget: global.button
};
global.fn = () => {};
});
after(() => {
document.body.innerHTML = '';
it('should set action as a function', () => {
let clipboard = new Clipboard('.btn', {
action: global.fn,
});
assert.equal(global.fn, clipboard.action);
});
describe('#resolveOptions', () => {
before(() => {
global.fn = () => {};
});
it('should set target as a function', () => {
let clipboard = new Clipboard('.btn', {
target: global.fn,
});
it('should set action as a function', () => {
let clipboard = new Clipboard('.btn', {
action: global.fn
});
assert.equal(global.fn, clipboard.action);
});
it('should set target as a function', () => {
let clipboard = new Clipboard('.btn', {
target: global.fn
});
assert.equal(global.fn, clipboard.target);
});
it('should set text as a function', () => {
let clipboard = new Clipboard('.btn', {
text: global.fn
});
assert.equal(global.fn, clipboard.text);
});
it('should set container as an object', () => {
let clipboard = new Clipboard('.btn', {
container: document.body
});
assert.equal(document.body, clipboard.container);
});
it('should set container as body by default', () => {
let clipboard = new Clipboard('.btn');
assert.equal(document.body, clipboard.container);
});
assert.equal(global.fn, clipboard.target);
});
describe('#listenClick', () => {
it('should add a click event listener to the passed selector', () => {
let clipboard = new Clipboard('.btn');
assert.isObject(clipboard.listener);
});
it('should set text as a function', () => {
let clipboard = new Clipboard('.btn', {
text: global.fn,
});
assert.equal(global.fn, clipboard.text);
});
describe('#onClick', () => {
it('should create a new instance of ClipboardAction', () => {
let clipboard = new Clipboard('.btn');
it('should set container as an object', () => {
let clipboard = new Clipboard('.btn', {
container: document.body,
});
clipboard.onClick(global.event);
assert.instanceOf(clipboard.clipboardAction, ClipboardAction);
});
it('should use an event\'s currentTarget when not equal to target', () => {
let clipboard = new Clipboard('.btn');
let bubbledEvent = { target: global.span, currentTarget: global.button };
clipboard.onClick(bubbledEvent);
assert.instanceOf(clipboard.clipboardAction, ClipboardAction);
});
it('should throw an exception when target is invalid', done => {
try {
const clipboard = new Clipboard('.btn', {
target() {
return null;
}
});
clipboard.onClick(global.event);
}
catch(e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done();
}
});
assert.equal(document.body, clipboard.container);
});
describe('#static isSupported', () => {
it('should return the support of the given action', () => {
assert.equal(Clipboard.isSupported('copy'), true);
assert.equal(Clipboard.isSupported('cut'), true);
});
it('should set container as body by default', () => {
let clipboard = new Clipboard('.btn');
it('should return the support of the cut and copy actions', () => {
assert.equal(Clipboard.isSupported(), true);
});
assert.equal(document.body, clipboard.container);
});
});
describe('#listenClick', () => {
it('should add a click event listener to the passed selector', () => {
let clipboard = new Clipboard('.btn');
assert.isObject(clipboard.listener);
});
});
describe('#onClick', () => {
it('should create a new instance of ClipboardAction', () => {
let clipboard = new Clipboard('.btn');
clipboard.onClick(global.event);
assert.instanceOf(clipboard.clipboardAction, ClipboardAction);
});
describe('#destroy', () => {
it('should destroy an existing instance of ClipboardAction', () => {
let clipboard = new Clipboard('.btn');
it("should use an event's currentTarget when not equal to target", () => {
let clipboard = new Clipboard('.btn');
let bubbledEvent = {
target: global.span,
currentTarget: global.button,
};
clipboard.onClick(global.event);
clipboard.destroy();
assert.equal(clipboard.clipboardAction, null);
});
clipboard.onClick(bubbledEvent);
assert.instanceOf(clipboard.clipboardAction, ClipboardAction);
});
it('should throw an exception when target is invalid', (done) => {
try {
const clipboard = new Clipboard('.btn', {
target() {
return null;
},
});
clipboard.onClick(global.event);
} catch (e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done();
}
});
});
describe('#static isSupported', () => {
it('should return the support of the given action', () => {
assert.equal(Clipboard.isSupported('copy'), true);
assert.equal(Clipboard.isSupported('cut'), true);
});
it('should return the support of the cut and copy actions', () => {
assert.equal(Clipboard.isSupported(), true);
});
});
describe('#destroy', () => {
it('should destroy an existing instance of ClipboardAction', () => {
let clipboard = new Clipboard('.btn');
clipboard.onClick(global.event);
clipboard.destroy();
assert.equal(clipboard.clipboardAction, null);
});
});
});

View File

@ -11,36 +11,35 @@ https://clipboardjs.com/
Licensed MIT © Zeno Rocha`;
module.exports = {
entry: './src/clipboard.js',
mode: 'production',
output: {
filename: production ? 'clipboard.min.js' : 'clipboard.js',
path: path.resolve(__dirname, 'dist'),
library: 'ClipboardJS',
globalObject: 'this',
libraryExport: 'default',
libraryTarget: 'umd'
},
module: {
rules: [
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'}
]
},
optimization: {
minimize: production,
minimizer: [
new UglifyJSPlugin({
parallel: require('os').cpus().length,
uglifyOptions: {
ie8: false,
keep_fnames: false,
output: {
beautify: false,
comments: (node, {value, type}) => type == 'comment2' && value.startsWith('!')
}
}
})
]
},
plugins: [new webpack.BannerPlugin({ banner })]
entry: './src/clipboard.js',
mode: 'production',
output: {
filename: production ? 'clipboard.min.js' : 'clipboard.js',
path: path.resolve(__dirname, 'dist'),
library: 'ClipboardJS',
globalObject: 'this',
libraryExport: 'default',
libraryTarget: 'umd',
},
module: {
rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }],
},
optimization: {
minimize: production,
minimizer: [
new UglifyJSPlugin({
parallel: require('os').cpus().length,
uglifyOptions: {
ie8: false,
keep_fnames: false,
output: {
beautify: false,
comments: (node, { value, type }) =>
type == 'comment2' && value.startsWith('!'),
},
},
}),
],
},
plugins: [new webpack.BannerPlugin({ banner })],
};