123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- import { ExcalidrawElement } from "./element/types";
- import { getSelectedElements } from "./scene";
- import { AppState } from "./types";
- let CLIPBOARD = "";
- let PREFER_APP_CLIPBOARD = false;
- export const probablySupportsClipboardReadText =
- "clipboard" in navigator && "readText" in navigator.clipboard;
- export const probablySupportsClipboardWriteText =
- "clipboard" in navigator && "writeText" in navigator.clipboard;
- export const probablySupportsClipboardBlob =
- "clipboard" in navigator &&
- "write" in navigator.clipboard &&
- "ClipboardItem" in window &&
- "toBlob" in HTMLCanvasElement.prototype;
- export async function copyToAppClipboard(
- elements: readonly ExcalidrawElement[],
- appState: AppState,
- ) {
- CLIPBOARD = JSON.stringify(getSelectedElements(elements, appState));
- try {
- // when copying to in-app clipboard, clear system clipboard so that if
- // system clip contains text on paste we know it was copied *after* user
- // copied elements, and thus we should prefer the text content.
- await copyTextToSystemClipboard(null);
- PREFER_APP_CLIPBOARD = false;
- } catch {
- // if clearing system clipboard didn't work, we should prefer in-app
- // clipboard even if there's text in system clipboard on paste, because
- // we can't be sure of the order of copy operations
- PREFER_APP_CLIPBOARD = true;
- }
- }
- export function getAppClipboard(): {
- elements?: readonly ExcalidrawElement[];
- } {
- try {
- const clipboardElements = JSON.parse(CLIPBOARD);
- if (
- Array.isArray(clipboardElements) &&
- clipboardElements.length > 0 &&
- clipboardElements[0].type // need to implement a better check here...
- ) {
- return { elements: clipboardElements };
- }
- } catch (error) {
- console.error(error);
- }
- return {};
- }
- export async function getClipboardContent(
- event: ClipboardEvent | null,
- ): Promise<{
- text?: string;
- elements?: readonly ExcalidrawElement[];
- }> {
- try {
- const text = event
- ? event.clipboardData?.getData("text/plain").trim()
- : probablySupportsClipboardReadText &&
- (await navigator.clipboard.readText());
- if (text && !PREFER_APP_CLIPBOARD) {
- return { text };
- }
- } catch (error) {
- console.error(error);
- }
- return getAppClipboard();
- }
- export async function copyCanvasToClipboardAsPng(canvas: HTMLCanvasElement) {
- return new Promise((resolve, reject) => {
- try {
- canvas.toBlob(async function(blob: any) {
- try {
- await navigator.clipboard.write([
- new window.ClipboardItem({ "image/png": blob }),
- ]);
- resolve();
- } catch (error) {
- reject(error);
- }
- });
- } catch (error) {
- reject(error);
- }
- });
- }
- export async function copyTextToSystemClipboard(text: string | null) {
- let copied = false;
- if (probablySupportsClipboardWriteText) {
- try {
- // NOTE: doesn't work on FF on non-HTTPS domains, or when document
- // not focused
- await navigator.clipboard.writeText(text || "");
- copied = true;
- } catch (error) {
- console.error(error);
- }
- }
- // Note that execCommand doesn't allow copying empty strings, so if we're
- // clearing clipboard using this API, we must copy at least an empty char
- if (!copied && !copyTextViaExecCommand(text || " ")) {
- throw new Error("couldn't copy");
- }
- }
- // adapted from https://github.com/zenorocha/clipboard.js/blob/ce79f170aa655c408b6aab33c9472e8e4fa52e19/src/clipboard-action.js#L48
- function copyTextViaExecCommand(text: string) {
- const isRTL = document.documentElement.getAttribute("dir") === "rtl";
- const textarea = document.createElement("textarea");
- textarea.style.border = "0";
- textarea.style.padding = "0";
- textarea.style.margin = "0";
- textarea.style.position = "absolute";
- textarea.style[isRTL ? "right" : "left"] = "-9999px";
- const yPosition = window.pageYOffset || document.documentElement.scrollTop;
- textarea.style.top = `${yPosition}px`;
- // Prevent zooming on iOS
- textarea.style.fontSize = "12pt";
- textarea.setAttribute("readonly", "");
- textarea.value = text;
- document.body.appendChild(textarea);
- let success = false;
- try {
- textarea.select();
- textarea.setSelectionRange(0, textarea.value.length);
- success = document.execCommand("copy");
- } catch (error) {
- console.error(error);
- }
- textarea.remove();
- return success;
- }
|