diff --git a/src/actions/actionClipboard.tsx b/src/actions/actionClipboard.tsx index 64c70532..cceaf353 100644 --- a/src/actions/actionClipboard.tsx +++ b/src/actions/actionClipboard.tsx @@ -1,11 +1,12 @@ import { CODES, KEYS } from "../keys"; import { register } from "./register"; -import { copyToClipboard } from "../clipboard"; +import { copyTextToSystemClipboard, copyToClipboard } from "../clipboard"; import { actionDeleteSelected } from "./actionDeleteSelected"; import { getSelectedElements } from "../scene/selection"; import { exportCanvas } from "../data/index"; import { getNonDeletedElements } from "../element"; import { t } from "../i18n"; +import { ExcalidrawTextElement } from "../element/types"; export const actionCopy = register({ name: "copy", @@ -126,3 +127,18 @@ export const actionCopyAsPng = register({ contextItemLabel: "labels.copyAsPng", keyTest: (event) => event.code === CODES.C && event.altKey && event.shiftKey, }); + +export const copyAllTextNodesAsText = register({ + name: "copyAllTextNodesAsText", + trackEvent: { category: "element" }, + perform: (elements) => { + const text = ( + getNonDeletedElements(elements) as ExcalidrawTextElement[] + ).reduce((acc, element) => `${acc}${element.text}\n`, ""); + copyTextToSystemClipboard(text); + return { + commitToHistory: false, + }; + }, + contextItemLabel: "labels.copyAllTextNodesAsText", +}); diff --git a/src/actions/index.ts b/src/actions/index.ts index d9fdfa70..34f84210 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -75,6 +75,7 @@ export { actionCut, actionCopyAsPng, actionCopyAsSvg, + copyAllTextNodesAsText, } from "./actionClipboard"; export { actionToggleGridMode } from "./actionToggleGridMode"; diff --git a/src/actions/types.ts b/src/actions/types.ts index 029d1d25..fe6044a3 100644 --- a/src/actions/types.ts +++ b/src/actions/types.ts @@ -41,6 +41,7 @@ export type ActionName = | "paste" | "copyAsPng" | "copyAsSvg" + | "copyAllTextNodesAsText" | "sendBackward" | "bringForward" | "sendToBack" diff --git a/src/components/App.tsx b/src/components/App.tsx index 9e287d27..a59a45d4 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -11,6 +11,7 @@ import { actionCopy, actionCopyAsPng, actionCopyAsSvg, + copyAllTextNodesAsText, actionCopyStyles, actionCut, actionDeleteSelected, @@ -5475,6 +5476,8 @@ class App extends React.Component { const elements = this.scene.getElements(); + const isTextNodesOnly = elements.every((element) => isTextElement(element)); + const options: ContextMenuOption[] = []; if (probablySupportsClipboardBlob && elements.length > 0) { options.push(actionCopyAsPng); @@ -5483,6 +5486,14 @@ class App extends React.Component { if (probablySupportsClipboardWriteText && elements.length > 0) { options.push(actionCopyAsSvg); } + + if ( + probablySupportsClipboardWriteText && + elements.length > 0 && + isTextNodesOnly + ) { + options.push(copyAllTextNodesAsText); + } if (type === "canvas") { const viewModeOptions = [ ...options, @@ -5526,6 +5537,10 @@ class App extends React.Component { probablySupportsClipboardWriteText && elements.length > 0 && actionCopyAsSvg, + probablySupportsClipboardWriteText && + elements.length > 0 && + isTextNodesOnly && + copyAllTextNodesAsText, ((probablySupportsClipboardBlob && elements.length > 0) || (probablySupportsClipboardWriteText && elements.length > 0)) && separator, diff --git a/src/locales/en.json b/src/locales/en.json index 58152c67..08eef15c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -9,6 +9,7 @@ "copy": "Copy", "copyAsPng": "Copy to clipboard as PNG", "copyAsSvg": "Copy to clipboard as SVG", + "copyAllTextNodesAsText": "Copy to clipboard as a single text element", "bringForward": "Bring forward", "sendToBack": "Send to back", "bringToFront": "Bring to front",