Commit d5e14e1ab8139bc204500ef172deb3909f3d35a6

Michael Schmidt 2021-03-17T11:10:56

Copy to clipboard: Removed ClipboardJS dependency (#2784)

diff --git a/package-lock.json b/package-lock.json
index 1a678c5..a93c816 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1025,17 +1025,6 @@
         }
       }
     },
-    "clipboard": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz",
-      "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==",
-      "optional": true,
-      "requires": {
-        "good-listener": "^1.2.2",
-        "select": "^1.1.2",
-        "tiny-emitter": "^2.0.0"
-      }
-    },
     "cliui": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
@@ -1563,12 +1552,6 @@
       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
       "dev": true
     },
-    "delegate": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
-      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
-      "optional": true
-    },
     "deprecation": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
@@ -2545,15 +2528,6 @@
         "sparkles": "^1.0.0"
       }
     },
-    "good-listener": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
-      "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
-      "optional": true,
-      "requires": {
-        "delegate": "^3.1.2"
-      }
-    },
     "graceful-fs": {
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
@@ -5730,12 +5704,6 @@
       "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=",
       "dev": true
     },
-    "select": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
-      "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
-      "optional": true
-    },
     "semver": {
       "version": "5.6.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
@@ -6308,12 +6276,6 @@
       "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=",
       "dev": true
     },
-    "tiny-emitter": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
-      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
-      "optional": true
-    },
     "tmp": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
diff --git a/package.json b/package.json
index a9fd2d3..1c13095 100755
--- a/package.json
+++ b/package.json
@@ -29,9 +29,6 @@
   "author": "Lea Verou",
   "license": "MIT",
   "readmeFilename": "README.md",
-  "optionalDependencies": {
-    "clipboard": "^2.0.0"
-  },
   "devDependencies": {
     "chai": "^4.2.0",
     "danger": "^10.5.0",
diff --git a/plugins/copy-to-clipboard/index.html b/plugins/copy-to-clipboard/index.html
index e8cf3e3..9fdd6c5 100644
--- a/plugins/copy-to-clipboard/index.html
+++ b/plugins/copy-to-clipboard/index.html
@@ -21,12 +21,7 @@
 <section>
 	<h1>How to use</h1>
 
-	<p>The plugin depends on <a href="https://clipboardjs.com/">clipboard.js</a> and the Prism <a href="https://prismjs.com/plugins/toolbar/">Toolbar</a> plugin. In addition to including the plugin file with your PrismJS build, ensure they are loaded before the plugin.</p>
-
-	<p>The simplest way to include clipboard.js is to use any of the
-		<a href="https://github.com/zenorocha/clipboard.js/wiki/CDN-Providers">recommended CDNs</a>. If you're using Browserify, clipboard.js will be loaded automatically
-		if it's included in your <code>package.json</code>.
-		If you don't load clipboard.js yourself, the plugin will load it from a CDN for you.</p>
+	<p>The plugin depends on the Prism <a href="https://prismjs.com/plugins/toolbar/">Toolbar</a> plugin. In addition to including the plugin file with your PrismJS build, ensure it is loaded before the plugin.</p>
 </section>
 
 <section>
diff --git a/plugins/copy-to-clipboard/prism-copy-to-clipboard.js b/plugins/copy-to-clipboard/prism-copy-to-clipboard.js
index 8ebf659..d6e744f 100644
--- a/plugins/copy-to-clipboard/prism-copy-to-clipboard.js
+++ b/plugins/copy-to-clipboard/prism-copy-to-clipboard.js
@@ -9,30 +9,73 @@
 		return;
 	}
 
-	var ClipboardJS = window.ClipboardJS || undefined;
-
-	if (!ClipboardJS && typeof require === 'function') {
-		ClipboardJS = require('clipboard');
+	/**
+	 * When the given elements is clicked by the user, the given text will be copied to clipboard.
+	 *
+	 * @param {HTMLElement} element
+	 * @param {CopyInfo} copyInfo
+	 *
+	 * @typedef CopyInfo
+	 * @property {() => string} getText
+	 * @property {() => void} success
+	 * @property {(reason: unknown) => void} error
+	 */
+	function registerClipboard(element, copyInfo) {
+		element.addEventListener('click', function () {
+			copyTextToClipboard(copyInfo);
+		});
 	}
 
-	var callbacks = [];
+	// https://stackoverflow.com/a/30810322/7595472
+
+	/** @param {CopyInfo} copyInfo */
+	function fallbackCopyTextToClipboard(copyInfo) {
+		var textArea = document.createElement("textarea");
+		textArea.value = copyInfo.getText();
 
-	if (!ClipboardJS) {
-		var script = document.createElement('script');
-		var head = document.querySelector('head');
+		// Avoid scrolling to bottom
+		textArea.style.top = "0";
+		textArea.style.left = "0";
+		textArea.style.position = "fixed";
 
-		script.onload = function () {
-			ClipboardJS = window.ClipboardJS;
+		document.body.appendChild(textArea);
+		textArea.focus();
+		textArea.select();
 
-			if (ClipboardJS) {
-				while (callbacks.length) {
-					callbacks.pop()();
+		try {
+			var successful = document.execCommand('copy');
+			setTimeout(function () {
+				if (successful) {
+					copyInfo.success();
+				} else {
+					copyInfo.error();
 				}
-			}
-		};
+			}, 1);
+		} catch (err) {
+			setTimeout(function () {
+				copyInfo.error(err);
+			}, 1);
+		}
 
-		script.src = 'https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js';
-		head.appendChild(script);
+		document.body.removeChild(textArea);
+	}
+	/** @param {CopyInfo} copyInfo */
+	function copyTextToClipboard(copyInfo) {
+		if (navigator.clipboard) {
+			navigator.clipboard.writeText(copyInfo.getText()).then(copyInfo.success, copyInfo.error);
+		} else {
+			fallbackCopyTextToClipboard(copyInfo);
+		}
+	}
+
+	/**
+	 * Selects the text content of the given element.
+	 *
+	 * @param {Element} element
+	 */
+	function selectElementText(element) {
+		// https://stackoverflow.com/a/20079910/7595472
+		window.getSelection().selectAllChildren(element)
 	}
 
 	/**
@@ -74,32 +117,27 @@
 		linkCopy.textContent = settings['copy'];
 		linkCopy.setAttribute('type', 'button');
 
-		if (!ClipboardJS) {
-			callbacks.push(registerClipboard);
-		} else {
-			registerClipboard();
-		}
-
-		return linkCopy;
-
-		function registerClipboard() {
-			var clip = new ClipboardJS(linkCopy, {
-				'text': function () {
-					return element.textContent;
-				}
-			});
-
-			clip.on('success', function () {
+		registerClipboard(linkCopy, {
+			getText: function () {
+				return element.textContent;
+			},
+			success: function () {
 				linkCopy.textContent = settings['copy-success'];
 
 				resetText();
-			});
-			clip.on('error', function () {
+			},
+			error: function () {
 				linkCopy.textContent = settings['copy-error'];
 
+				setTimeout(function () {
+					selectElementText(element);
+				}, 1);
+
 				resetText();
-			});
-		}
+			}
+		});
+
+		return linkCopy;
 
 		function resetText() {
 			setTimeout(function () {
diff --git a/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js b/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js
index 3a03d17..d45ee5f 100644
--- a/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js
+++ b/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js
@@ -1 +1 @@
-!function(){if("undefined"!=typeof self&&self.Prism&&self.document)if(Prism.plugins.toolbar){var c=window.ClipboardJS||void 0;c||"function"!=typeof require||(c=require("clipboard"));var u=[];if(!c){var t=document.createElement("script"),o=document.querySelector("head");t.onload=function(){if(c=window.ClipboardJS)for(;u.length;)u.pop()()},t.src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js",o.appendChild(t)}Prism.plugins.toolbar.registerButton("copy-to-clipboard",function(t){var o=t.element,e=function(t){var o={copy:"Copy","copy-error":"Press Ctrl+C to copy","copy-success":"Copied!","copy-timeout":5e3};for(var e in o){for(var n="data-prismjs-"+e,r=t;r&&!r.hasAttribute(n);)r=r.parentElement;r&&(o[e]=r.getAttribute(n))}return o}(o),n=document.createElement("button");return n.textContent=e.copy,n.setAttribute("type","button"),c?r():u.push(r),n;function r(){var t=new c(n,{text:function(){return o.textContent}});t.on("success",function(){n.textContent=e["copy-success"],i()}),t.on("error",function(){n.textContent=e["copy-error"],i()})}function i(){setTimeout(function(){n.textContent=e.copy},e["copy-timeout"])}})}else console.warn("Copy to Clipboard plugin loaded before Toolbar plugin.")}();
\ No newline at end of file
+!function(){function c(t,e){t.addEventListener("click",function(){!function(t){navigator.clipboard?navigator.clipboard.writeText(t.getText()).then(t.success,t.error):function(e){var t=document.createElement("textarea");t.value=e.getText(),t.style.top="0",t.style.left="0",t.style.position="fixed",document.body.appendChild(t),t.focus(),t.select();try{var o=document.execCommand("copy");setTimeout(function(){o?e.success():e.error()},1)}catch(t){setTimeout(function(){e.error(t)},1)}document.body.removeChild(t)}(t)}(e)})}"undefined"!=typeof self&&self.Prism&&self.document&&(Prism.plugins.toolbar?Prism.plugins.toolbar.registerButton("copy-to-clipboard",function(t){var e=t.element,o=function(t){var e={copy:"Copy","copy-error":"Press Ctrl+C to copy","copy-success":"Copied!","copy-timeout":5e3};for(var o in e){for(var n="data-prismjs-"+o,r=t;r&&!r.hasAttribute(n);)r=r.parentElement;r&&(e[o]=r.getAttribute(n))}return e}(e),n=document.createElement("button");return n.textContent=o.copy,n.setAttribute("type","button"),c(n,{getText:function(){return e.textContent},success:function(){n.textContent=o["copy-success"],r()},error:function(){n.textContent=o["copy-error"],setTimeout(function(){!function(t){window.getSelection().selectAllChildren(t)}(e)},1),r()}}),n;function r(){setTimeout(function(){n.textContent=o.copy},o["copy-timeout"])}}):console.warn("Copy to Clipboard plugin loaded before Toolbar plugin."))}();
\ No newline at end of file