diff --git a/playwright-tests/tests/blocksTests.spec.js b/playwright-tests/tests/blocksTests.spec.js index bb9b58bd8..698776ade 100644 --- a/playwright-tests/tests/blocksTests.spec.js +++ b/playwright-tests/tests/blocksTests.spec.js @@ -39,8 +39,7 @@ test.describe("Issues", () => { await page .getByTestId("action-block") .filter({ hasText: 'Code preview: print("hello")' }) - .getByRole("button") - .nth(2) + .getByTestId(/^action-checkbox/) .click(); //uncheck codeblock await configPage.removeAction(); diff --git a/src/renderer/config-blocks/CodeBlock.svelte b/src/renderer/config-blocks/CodeBlock.svelte index 3a7b30e1b..47159a884 100644 --- a/src/renderer/config-blocks/CodeBlock.svelte +++ b/src/renderer/config-blocks/CodeBlock.svelte @@ -46,6 +46,8 @@ import SendFeedback from "../main/user-interface/SendFeedback.svelte"; import { MoltenPushButton } from "@intechstudio/grid-uikit"; + import { copyContextMenu } from "../main/_actions/copy-context-menu.action"; + import MoltenPopup from "../main/panels/preferences/MoltenPopup.svelte"; import { Modal } from "../main/modals/modal.store"; import Monaco from "../main/modals/Monaco.svelte"; @@ -121,13 +123,30 @@
     
- +
+ +
+ + navigator.clipboard.writeText( + GridScript.expandScript($action.script), + )} + text={"Copy Code"} + > + + +
diff --git a/src/renderer/main/_actions/copy-context-menu.action.ts b/src/renderer/main/_actions/copy-context-menu.action.ts new file mode 100644 index 000000000..732cd4d2a --- /dev/null +++ b/src/renderer/main/_actions/copy-context-menu.action.ts @@ -0,0 +1,110 @@ +const INLINE_TAGS = new Set([ + "span", + "a", + "em", + "strong", + "b", + "i", + "u", + "code", +]); + +function getTextFromNode(node: Node): string { + if (node.nodeType === Node.TEXT_NODE) { + return node.textContent ?? ""; + } + if (node.nodeType !== Node.ELEMENT_NODE) return ""; + + const el = node as HTMLElement; + const tag = el.tagName.toLowerCase(); + const childTexts = Array.from(el.childNodes).map(getTextFromNode); + + // Grid rows: join cells inline with spaces + if (el.className?.includes?.("grid-cols")) { + return childTexts.filter((s) => s.trim().length > 0).join(" "); + } + + // Flex rows that are direct data rows (not layout wrappers): join inline + if (el.className?.includes?.("flex-row") && el.className?.includes?.("gap")) { + return childTexts.filter((s) => s.trim().length > 0).join(" "); + } + + // Inline elements: join without separator to preserve token continuity + if (INLINE_TAGS.has(tag)) { + return childTexts.join(""); + } + + // Block elements: join with newlines + return childTexts.filter((s) => s.trim().length > 0).join("\n"); +} + +function getSelectionText(): string { + const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) return ""; + + const range = selection.getRangeAt(0); + const fragment = range.cloneContents(); + + const texts = Array.from(fragment.childNodes) + .map(getTextFromNode) + .filter((s) => s.length > 0); + + return texts.join("\n").trim(); +} + +export function copyContextMenu(node: HTMLElement) { + let menu: HTMLElement | null = null; + + function removeMenu() { + menu?.remove(); + menu = null; + } + + function handleContextMenu(e: MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + removeMenu(); + + menu = document.createElement("div"); + menu.className = + "fixed z-50 bg-primary border border-gray-600 rounded shadow-lg py-1 text-sm text-white"; + menu.style.left = `${e.clientX}px`; + menu.style.top = `${e.clientY}px`; + + const button = document.createElement("button"); + button.textContent = "Copy"; + button.className = "w-full text-left px-4 py-1 hover:bg-gray-700"; + button.onclick = () => { + navigator.clipboard.writeText(getSelectionText()); + removeMenu(); + }; + + menu.appendChild(button); + document.body.appendChild(menu); + } + + function handleCopy(e: ClipboardEvent) { + const text = getSelectionText(); + if (text) { + e.clipboardData?.setData("text/plain", text); + e.preventDefault(); + } + } + + function handleClick() { + removeMenu(); + } + + node.addEventListener("contextmenu", handleContextMenu); + node.addEventListener("copy", handleCopy); + window.addEventListener("click", handleClick); + + return { + destroy() { + node.removeEventListener("contextmenu", handleContextMenu); + node.removeEventListener("copy", handleCopy); + window.removeEventListener("click", handleClick); + removeMenu(); + }, + }; +} diff --git a/src/renderer/main/panels/DebugMonitor/DebugMonitor.svelte b/src/renderer/main/panels/DebugMonitor/DebugMonitor.svelte index 9862cd491..5802457f4 100644 --- a/src/renderer/main/panels/DebugMonitor/DebugMonitor.svelte +++ b/src/renderer/main/panels/DebugMonitor/DebugMonitor.svelte @@ -23,6 +23,7 @@ import DebugTextList from "./DebugTextList.svelte"; import { scrollToBottom } from "../../_actions/scroll.move"; import SendImmediate from "./SendImmediate.svelte"; + import { copyContextMenu } from "../../_actions/copy-context-menu.action"; import SendEvaluate from "./SendEvaluate.svelte"; import SendRaw from "./SendRaw.svelte"; @@ -255,6 +256,7 @@
{#each $debug_lowlevel_store as debug, i} diff --git a/src/renderer/main/panels/DebugMonitor/DebugTextList.svelte b/src/renderer/main/panels/DebugMonitor/DebugTextList.svelte index 3ba0aad43..a04b16e80 100644 --- a/src/renderer/main/panels/DebugMonitor/DebugTextList.svelte +++ b/src/renderer/main/panels/DebugMonitor/DebugTextList.svelte @@ -1,6 +1,7 @@
{#each $debug_monitor_store as message} {#each message.split("\n") as part} diff --git a/src/renderer/main/panels/MidiMonitor/MidiMonitor.svelte b/src/renderer/main/panels/MidiMonitor/MidiMonitor.svelte index 0b0e3e998..c2f8dbe8b 100644 --- a/src/renderer/main/panels/MidiMonitor/MidiMonitor.svelte +++ b/src/renderer/main/panels/MidiMonitor/MidiMonitor.svelte @@ -21,6 +21,7 @@ import { Grid } from "../../../lib/_utils"; import MidiTester from "./MidiTester.svelte"; import DebugTextList from "../DebugMonitor/DebugTextList.svelte"; + import { copyContextMenu } from "../../_actions/copy-context-menu.action"; import { onDestroy, onMount, tick } from "svelte"; import { type MidiWorkerCommand, @@ -325,7 +326,8 @@
{#if lastMidiMessageIndex > 0} @@ -386,7 +388,8 @@ {:else}
MIDI Messages
{#if lastMidiMessageIndex > 0} @@ -492,7 +495,8 @@ System Exclusive Messages
{#if lastSysExMessageIndex > 0}