Skip to content

Commit

Permalink
fix(clippy): reimplement copy logic for CSS editor (#1170)
Browse files Browse the repository at this point in the history
This PR fixes the "Copy" button in the CSS editor, which copies the 
content of the CodeMirror editor and displays the message "Copied!".

Co-authored-by: Claas Augner <[email protected]>
  • Loading branch information
NiedziolkaMichal and caugner authored Oct 30, 2023
1 parent 37f01a4 commit e7c90d6
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 116 deletions.
7 changes: 4 additions & 3 deletions editor/js/editable-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ import "../css/editable-css.css";
const choiceButton = document.createElement("button");
const choiceButtonText = document.createElement("span");
const choiceCode = exampleChoice.querySelector("code");
const copyButton = exampleChoice.getElementsByClassName("copy")[0];

originalChoices.push(choiceCode.textContent);

applyCodeMirror(
const codeMirrorEditor = applyCodeMirror(
exampleChoice.querySelector("pre"),
choiceCode.textContent,
);
Expand All @@ -64,6 +65,8 @@ import "../css/editable-css.css";
}

choiceCode.remove();

clippy.addClippy(copyButton, codeMirrorEditor);
}

mceEvents.register();
Expand All @@ -72,8 +75,6 @@ import "../css/editable-css.css";
handleChoiceHover();
// Adding or removing class "invalid"
cssEditorUtils.applyInitialSupportWarningState(exampleChoices);

clippy.addClippy();
}

/**
Expand Down
100 changes: 55 additions & 45 deletions editor/js/editor-libs/clippy.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,88 @@
import * as mceUtils from "./mce-utils.js";
import Clipboard from "clipboard";
import { getEditorContent } from "./codemirror-editor.js";

/**
* Positions the copy to clipboard success message based on the
* position of the button that triggered the copy event.
* @param {Object} clippyEvent - The clipboardjs event object
* @param {Object} msgContainer - The feedback message container
* @param {HTMLButtonElement} copyButton - Button which can trigger copy action
* @param {HTMLElement} toastElement - The feedback message container
*/
function setClippyPosition(clippyEvent, msgContainer) {
const trigger = clippyEvent.trigger;
const triggerParent = trigger.offsetParent;
function setToastPosition(copyButton, toastElement) {
/** @var {HTMLElement} */
const copyBtnParent = copyButton.offsetParent;
/* calculate the base top offset by combining the top
offset of the button's parent element, and the height
of the button */
const positionTopBasis = triggerParent.offsetTop + trigger.clientHeight;
const positionTopBasis = copyBtnParent.offsetTop + copyButton.clientHeight;
// Add 10px padding to the base to avoid overlapping the button
const positionTop = positionTopBasis + 10 + "px";
const positionLeft = trigger.offsetLeft + "px";
const positionLeft = copyButton.offsetLeft + "px";

msgContainer.style.top = positionTop;
msgContainer.style.left = positionLeft;
toastElement.style.top = positionTop;
toastElement.style.left = positionLeft;
}

/**
* Initialise clipboard.js, and setup success handler
* Makes copyButton copy the textual content of the codeMirrorEditor upon click and show a toast with the text "Copied!"
* @param {HTMLButtonElement} copyButton
* @param {EditorView} codeMirrorEditor
*/
export function addClippy() {
const clipboard = new Clipboard(".copy", {
target: function (clippyButton) {
const targetAttr = clippyButton.dataset.clipboardTarget;
if (targetAttr) {
// The attribute will override the automated target selection
return document.querySelector(targetAttr);
} else {
// Get its parent until it finds an example choice
const choiceElem = mceUtils.findParentChoiceElem(clippyButton);
// Use the first code element to prevent extra text
const firstCodeElem = choiceElem.getElementsByTagName("code")[0];
return firstCodeElem;
}
},
export function addClippy(copyButton, codeMirrorEditor) {
copyButton.addEventListener("click", () => {
const currentText = getEditorContent(codeMirrorEditor);
copyText(currentText);

showToastCopied(copyButton);
});
}

clipboard.on("success", (event) => {
const msgContainer = document.getElementById("user-message");
/**
*
* @param {string} text
*/
function copyText(text) {
try {
// Available only in HTTPs & localhost
navigator.clipboard.writeText(text);
} catch (err) {
console.warn(`Unable to write text to clipboard`, err);
}
}

msgContainer.classList.add("show");
msgContainer.setAttribute("aria-hidden", false);
/**
* Displays and adjusts position of the "Copied!" toast
* @param {HTMLButtonElement} copyButton - Button which can trigger copy action
*/
function showToastCopied(copyButton) {
/** @var {HTMLElement} */
const toastElement = document.getElementById("user-message");

setClippyPosition(event, msgContainer);
const toggleToast = (show) => {
toastElement.classList.toggle("show", show);
toastElement.setAttribute("aria-hidden", JSON.stringify(!show));
};

window.setTimeout(() => {
msgContainer.classList.remove("show");
msgContainer.setAttribute("aria-hidden", true);
}, 1000);
toggleToast(true);

event.clearSelection();
});
setToastPosition(copyButton, toastElement);

window.setTimeout(() => {
toggleToast(false);
}, 1000);
}

/**
* Hides all instances of the clippy button, then shows
* the button in the container element passed in
* @param {Object} container - The container containing the button to show
* @param {HTMLElement} container - The container containing the button to show
*/
export function toggleClippy(container) {
/** @var {HTMLElement} */
const activeClippy = container.querySelector(".copy");
const clippyButtons = document.querySelectorAll(".copy");

for (let i = 0, l = clippyButtons.length; i < l; i++) {
clippyButtons[i].classList.add("hidden");
clippyButtons[i].setAttribute("aria-hidden", true);
for (const clippyButton of clippyButtons) {
const hide = clippyButton !== activeClippy;
clippyButton.classList.toggle("hidden", hide);
clippyButton.setAttribute("aria-hidden", JSON.stringify(hide));
}

activeClippy.classList.remove("hidden");
activeClippy.setAttribute("aria-hidden", false);
}
67 changes: 0 additions & 67 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@
"@lezer/html": "^1.0.1",
"@lezer/javascript": "^1.0.2",
"clean-css": "5.3.2",
"clipboard": "^2.0.11",
"codemirror": "^6.0.1",
"cosmiconfig": "8.3.6",
"css-loader": "^6.7.1",
Expand Down

0 comments on commit e7c90d6

Please sign in to comment.