From c1ad9c32768cead0ee81217c882b3ba097bac471 Mon Sep 17 00:00:00 2001
From: Martin Brennan <martin@discourse.org>
Date: Wed, 9 Feb 2022 16:11:41 +1000
Subject: [PATCH] DEV: Make clipboardCopy util available for import (#15874)

We need this in other places, this commit moves clipboardCopy
to the utilities.js lib. Had to remove use of Promise as well because
lib/utilities cannot import it, otherwise it will cause a mini racer error.
---
 .../addon/controllers/admin-permalinks.js     |  8 +-
 .../app/initializers/copy-codeblocks.js       | 98 ++++++-------------
 .../discourse/app/lib/utilities.js            | 47 +++++++++
 3 files changed, 77 insertions(+), 76 deletions(-)

diff --git a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js
index d4f354d3219..9993a19fe5f 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js
@@ -5,6 +5,7 @@ import Permalink from "admin/models/permalink";
 import bootbox from "bootbox";
 import discourseDebounce from "discourse-common/lib/debounce";
 import { observes } from "discourse-common/utils/decorators";
+import { clipboardCopy } from "discourse/lib/utilities";
 
 export default Controller.extend({
   loading: false,
@@ -29,12 +30,7 @@ export default Controller.extend({
 
     copyUrl(pl) {
       let linkElement = document.querySelector(`#admin-permalink-${pl.id}`);
-      let textArea = document.createElement("textarea");
-      textArea.value = linkElement.textContent;
-      document.body.appendChild(textArea);
-      textArea.select();
-      document.execCommand("Copy");
-      textArea.remove();
+      clipboardCopy(linkElement.textContent);
     },
 
     destroy(record) {
diff --git a/app/assets/javascripts/discourse/app/initializers/copy-codeblocks.js b/app/assets/javascripts/discourse/app/initializers/copy-codeblocks.js
index b11925807ca..efe8a4af214 100644
--- a/app/assets/javascripts/discourse/app/initializers/copy-codeblocks.js
+++ b/app/assets/javascripts/discourse/app/initializers/copy-codeblocks.js
@@ -1,61 +1,10 @@
 import { cancel, later } from "@ember/runloop";
 import I18n from "I18n";
-import { Promise } from "rsvp";
 import { guidFor } from "@ember/object/internals";
+import { clipboardCopy } from "discourse/lib/utilities";
 import { iconHTML } from "discourse-common/lib/icon-library";
 import { withPluginApi } from "discourse/lib/plugin-api";
 
-// http://github.com/feross/clipboard-copy
-function clipboardCopy(text) {
-  // Use the Async Clipboard API when available.
-  // Requires a secure browsing context (i.e. HTTPS)
-  if (navigator.clipboard) {
-    return navigator.clipboard.writeText(text).catch(function (err) {
-      throw err !== undefined
-        ? err
-        : new DOMException("The request is not allowed", "NotAllowedError");
-    });
-  }
-
-  // ...Otherwise, use document.execCommand() fallback
-
-  // Put the text to copy into a <span>
-  const span = document.createElement("span");
-  span.textContent = text;
-
-  // Preserve consecutive spaces and newlines
-  span.style.whiteSpace = "pre";
-
-  // Add the <span> to the page
-  document.body.appendChild(span);
-
-  // Make a selection object representing the range of text selected by the user
-  const selection = window.getSelection();
-  const range = window.document.createRange();
-  selection.removeAllRanges();
-  range.selectNode(span);
-  selection.addRange(range);
-
-  // Copy text to the clipboard
-  let success = false;
-  try {
-    success = window.document.execCommand("copy");
-  } catch (err) {
-    // eslint-disable-next-line no-console
-    console.log("error", err);
-  }
-
-  // Cleanup
-  selection.removeAllRanges();
-  window.document.body.removeChild(span);
-
-  return success
-    ? Promise.resolve()
-    : Promise.reject(
-        new DOMException("The request is not allowed", "NotAllowedError")
-      );
-}
-
 let _copyCodeblocksClickHandlers = {};
 let _fadeCopyCodeblocksRunners = {};
 
@@ -79,6 +28,25 @@ export default {
         _fadeCopyCodeblocksRunners = {};
       }
 
+      function _copyComplete(button) {
+        button.classList.add("copied");
+        const state = button.innerHTML;
+        button.innerHTML = I18n.t("copy_codeblock.copied");
+
+        const commandId = guidFor(button);
+
+        if (_fadeCopyCodeblocksRunners[commandId]) {
+          cancel(_fadeCopyCodeblocksRunners[commandId]);
+          delete _fadeCopyCodeblocksRunners[commandId];
+        }
+
+        _fadeCopyCodeblocksRunners[commandId] = later(() => {
+          button.classList.remove("copied");
+          button.innerHTML = state;
+          delete _fadeCopyCodeblocksRunners[commandId];
+        }, 3000);
+      }
+
       function _handleClick(event) {
         if (!event.target.classList.contains("copy-cmd")) {
           return;
@@ -96,24 +64,14 @@ export default {
             )
             .trim();
 
-          clipboardCopy(text).then(() => {
-            button.classList.add("copied");
-            const state = button.innerHTML;
-            button.innerHTML = I18n.t("copy_codeblock.copied");
-
-            const commandId = guidFor(button);
-
-            if (_fadeCopyCodeblocksRunners[commandId]) {
-              cancel(_fadeCopyCodeblocksRunners[commandId]);
-              delete _fadeCopyCodeblocksRunners[commandId];
-            }
-
-            _fadeCopyCodeblocksRunners[commandId] = later(() => {
-              button.classList.remove("copied");
-              button.innerHTML = state;
-              delete _fadeCopyCodeblocksRunners[commandId];
-            }, 3000);
-          });
+          const result = clipboardCopy(text);
+          if (result.then) {
+            result.then(() => {
+              _copyComplete(button);
+            });
+          } else if (result) {
+            _copyComplete(button);
+          }
         }
       }
 
diff --git a/app/assets/javascripts/discourse/app/lib/utilities.js b/app/assets/javascripts/discourse/app/lib/utilities.js
index ddb80fafb2a..9005d55320c 100644
--- a/app/assets/javascripts/discourse/app/lib/utilities.js
+++ b/app/assets/javascripts/discourse/app/lib/utilities.js
@@ -503,5 +503,52 @@ export function translateModKey(string) {
 
   return string;
 }
+
+// http://github.com/feross/clipboard-copy
+export function clipboardCopy(text) {
+  // Use the Async Clipboard API when available.
+  // Requires a secure browsing context (i.e. HTTPS)
+  if (navigator.clipboard) {
+    return navigator.clipboard.writeText(text).catch(function (err) {
+      throw err !== undefined
+        ? err
+        : new DOMException("The request is not allowed", "NotAllowedError");
+    });
+  }
+
+  // ...Otherwise, use document.execCommand() fallback
+
+  // Put the text to copy into a <span>
+  const span = document.createElement("span");
+  span.textContent = text;
+
+  // Preserve consecutive spaces and newlines
+  span.style.whiteSpace = "pre";
+
+  // Add the <span> to the page
+  document.body.appendChild(span);
+
+  // Make a selection object representing the range of text selected by the user
+  const selection = window.getSelection();
+  const range = window.document.createRange();
+  selection.removeAllRanges();
+  range.selectNode(span);
+  selection.addRange(range);
+
+  // Copy text to the clipboard
+  let success = false;
+  try {
+    success = window.document.execCommand("copy");
+  } catch (err) {
+    // eslint-disable-next-line no-console
+    console.log("error", err);
+  }
+
+  // Cleanup
+  selection.removeAllRanges();
+  window.document.body.removeChild(span);
+  return success;
+}
+
 // This prevents a mini racer crash
 export default {};