diff --git a/apps/desktop/src/components/editor/SqlPreviewPanel.vue b/apps/desktop/src/components/editor/SqlPreviewPanel.vue index 18a1871bd..5edb79065 100644 --- a/apps/desktop/src/components/editor/SqlPreviewPanel.vue +++ b/apps/desktop/src/components/editor/SqlPreviewPanel.vue @@ -182,10 +182,10 @@ onBeforeUnmount(() => { -
+ -{{ displaySql }}
+ {{ displaySql }}
diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue
index 9638814a0..80a1e2912 100644
--- a/apps/desktop/src/components/grid/DataGrid.vue
+++ b/apps/desktop/src/components/grid/DataGrid.vue
@@ -129,7 +129,7 @@ import { parseClipboardTable } from "@/lib/gridSelection";
import { useToast } from "@/composables/useToast";
import { useDataGridExport } from "@/composables/useDataGridExport";
-import { readTextFromClipboard } from "@/lib/clipboard";
+import { eventTargetAllowsNativeClipboard, isPlainClipboardShortcut, readTextFromClipboard } from "@/lib/clipboard";
import ExportProgressDialog from "@/components/export/ExportProgressDialog.vue";
import { DATA_GRID_ROW_NUM_WIDTH, useDataGridColumnResize } from "@/composables/useDataGridColumnResize";
import { useDataGridSelection } from "@/composables/useDataGridSelection";
@@ -4746,25 +4746,6 @@ function openImagePreview(src: string, title: string) {
imagePreviewOpen.value = true;
}
-function selectionNodeElement(node: Node | null): Element | null {
- if (!node) return null;
- return node instanceof Element ? node : node.parentElement;
-}
-
-function hasNativeClipboardSelection(): boolean {
- const selection = window.getSelection();
- if (!selection || selection.isCollapsed) return false;
- const anchorRegion = selectionNodeElement(selection.anchorNode)?.closest("[data-native-clipboard]");
- const focusRegion = selectionNodeElement(selection.focusNode)?.closest("[data-native-clipboard]");
- return !!anchorRegion && anchorRegion === focusRegion;
-}
-
-function eventTargetAllowsNativeClipboard(event: KeyboardEvent): boolean {
- const target = event.target as HTMLElement | null;
- if (target?.closest("input, textarea, [contenteditable='true'], [role='textbox']")) return true;
- return clipboardShortcut(event, "c") && hasNativeClipboardSelection();
-}
-
function onDrawerContextMenu(event: MouseEvent) {
event.stopPropagation();
const target = event.target as HTMLElement | null;
@@ -4773,7 +4754,7 @@ function onDrawerContextMenu(event: MouseEvent) {
}
function clipboardShortcut(event: KeyboardEvent, key: string): boolean {
- return (event.metaKey || event.ctrlKey) && !event.altKey && event.key.toLowerCase() === key;
+ return isPlainClipboardShortcut(event, key);
}
let lastPasteEventAt = 0;
diff --git a/apps/desktop/src/lib/clipboard.ts b/apps/desktop/src/lib/clipboard.ts
index da60ce809..716c95bce 100644
--- a/apps/desktop/src/lib/clipboard.ts
+++ b/apps/desktop/src/lib/clipboard.ts
@@ -37,6 +37,55 @@ export interface ClipboardEnvironment {
document?: ClipboardDocument;
}
+export interface ClipboardShortcutEvent {
+ key: string;
+ metaKey?: boolean;
+ ctrlKey?: boolean;
+ altKey?: boolean;
+ shiftKey?: boolean;
+ target?: EventTarget | null;
+}
+
+interface SelectionLike {
+ anchorNode: Node | null;
+ focusNode: Node | null;
+ isCollapsed: boolean;
+}
+
+interface NativeClipboardSelectionEnvironment {
+ getSelection?: () => SelectionLike | null;
+}
+
+const EDITABLE_CLIPBOARD_TARGET_SELECTOR = "input, textarea, [contenteditable='true'], [role='textbox']";
+const NATIVE_CLIPBOARD_REGION_SELECTOR = "[data-native-clipboard]";
+
+function closestElement(target: unknown, selector: string): unknown {
+ return (target as { closest?: (selector: string) => unknown } | null)?.closest?.(selector) ?? null;
+}
+
+function selectionNodeElement(node: Node | null): Element | null {
+ if (!node) return null;
+ if (typeof Element !== "undefined" && node instanceof Element) return node;
+ return (node as { parentElement?: Element | null }).parentElement ?? null;
+}
+
+export function isPlainClipboardShortcut(event: ClipboardShortcutEvent, key: string): boolean {
+ return !!(event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === key;
+}
+
+export function hasNativeClipboardSelection(env: NativeClipboardSelectionEnvironment = globalThis as unknown as NativeClipboardSelectionEnvironment): boolean {
+ const selection = env.getSelection?.();
+ if (!selection || selection.isCollapsed) return false;
+ const anchorRegion = selectionNodeElement(selection.anchorNode)?.closest(NATIVE_CLIPBOARD_REGION_SELECTOR);
+ const focusRegion = selectionNodeElement(selection.focusNode)?.closest(NATIVE_CLIPBOARD_REGION_SELECTOR);
+ return !!anchorRegion && anchorRegion === focusRegion;
+}
+
+export function eventTargetAllowsNativeClipboard(event: ClipboardShortcutEvent, env: NativeClipboardSelectionEnvironment = globalThis as unknown as NativeClipboardSelectionEnvironment): boolean {
+ if (closestElement(event.target, EDITABLE_CLIPBOARD_TARGET_SELECTOR)) return true;
+ return isPlainClipboardShortcut(event, "c") && hasNativeClipboardSelection(env);
+}
+
export async function readTextFromClipboard(env: ClipboardEnvironment = globalThis as unknown as ClipboardEnvironment): Promise