Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(clippy): reimplement copy logic for CSS editor #1170

Merged
merged 9 commits into from
Oct 30, 2023
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
Loading