From 3f78a3a8ce7837e8b32242c8edbbed431c68c062 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 15 Jan 2025 07:56:00 +1100 Subject: [PATCH] Fix edit menus on windows and linux --- docs/src/content/docs/changelog.mdx | 3 + v3/pkg/application/menuitem_roles.go | 91 +++++++++++++++++--- v3/pkg/application/webview_window.go | 21 +++-- v3/pkg/application/webview_window_windows.go | 84 ++++++++++++++++-- 4 files changed, 170 insertions(+), 29 deletions(-) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 87ec7568cd9..33da5a98cd9 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -25,6 +25,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) + ## v3.0.0-alpha.9 - 2025-01-13 ### Added diff --git a/v3/pkg/application/menuitem_roles.go b/v3/pkg/application/menuitem_roles.go index 6f7091f0b96..96dc964e0fa 100644 --- a/v3/pkg/application/menuitem_roles.go +++ b/v3/pkg/application/menuitem_roles.go @@ -32,34 +32,77 @@ func NewUnhideMenuItem() *MenuItem { } func NewUndoMenuItem() *MenuItem { - return NewMenuItem("Undo"). + result := NewMenuItem("Undo"). SetAccelerator("CmdOrCtrl+z") + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.undo() + } + }) + } + return result } -// newRedoMenuItem creates a new menu item for redoing the last action +// NewRedoMenuItem creates a new menu item for redoing the last action func NewRedoMenuItem() *MenuItem { - return NewMenuItem("Redo"). + result := NewMenuItem("Redo"). SetAccelerator("CmdOrCtrl+Shift+z") + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.redo() + } + }) + } + return result } func NewCutMenuItem() *MenuItem { - return NewMenuItem("Cut"). - SetAccelerator("CmdOrCtrl+x").OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.cut() - } - }) + result := NewMenuItem("Cut"). + SetAccelerator("CmdOrCtrl+x") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.cut() + } + }) + } + return result } func NewCopyMenuItem() *MenuItem { - return NewMenuItem("Copy"). + result := NewMenuItem("Copy"). SetAccelerator("CmdOrCtrl+c") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.copy() + } + }) + } + return result } func NewPasteMenuItem() *MenuItem { - return NewMenuItem("Paste"). + result := NewMenuItem("Paste"). SetAccelerator("CmdOrCtrl+v") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.paste() + } + }) + } + return result } func NewPasteAndMatchStyleMenuItem() *MenuItem { @@ -68,8 +111,18 @@ func NewPasteAndMatchStyleMenuItem() *MenuItem { } func NewDeleteMenuItem() *MenuItem { - return NewMenuItem("Delete"). + result := NewMenuItem("Delete"). SetAccelerator("backspace") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.delete() + } + }) + } + return result } func NewQuitMenuItem() *MenuItem { @@ -87,8 +140,18 @@ func NewQuitMenuItem() *MenuItem { } func NewSelectAllMenuItem() *MenuItem { - return NewMenuItem("Select All"). + result := NewMenuItem("Select All"). SetAccelerator("CmdOrCtrl+a") + + if runtime.GOOS != "darwin" { + result.OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.selectAll() + } + }) + } + return result } func NewAboutMenuItem() *MenuItem { diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index eedecf485be..c7dd3d9c0b2 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -1309,42 +1309,49 @@ func (w *WebviewWindow) SetIgnoreMouseEvents(ignore bool) Window { func (w *WebviewWindow) cut() { if w.impl == nil || w.isDestroyed() { - w.impl.cut() + return } + w.impl.cut() } func (w *WebviewWindow) copy() { if w.impl == nil || w.isDestroyed() { - w.impl.copy() + return } + w.impl.copy() } func (w *WebviewWindow) paste() { if w.impl == nil || w.isDestroyed() { - w.impl.paste() + return } + w.impl.paste() } func (w *WebviewWindow) selectAll() { if w.impl == nil || w.isDestroyed() { - w.impl.selectAll() + return } + w.impl.selectAll() } func (w *WebviewWindow) undo() { if w.impl == nil || w.isDestroyed() { - w.impl.undo() + return } + w.impl.undo() } func (w *WebviewWindow) delete() { if w.impl == nil || w.isDestroyed() { - w.impl.delete() + return } + w.impl.delete() } func (w *WebviewWindow) redo() { if w.impl == nil || w.isDestroyed() { - w.impl.redo() + return } + w.impl.redo() } diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index c82df1f1bee..e724dabe63e 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -74,30 +74,98 @@ type windowsWebviewWindow struct { } func (w *windowsWebviewWindow) cut() { - w32.Cut(w.hwnd) + w.execJS("document.execCommand('cut')") } func (w *windowsWebviewWindow) paste() { - w32.Paste(w.hwnd) + w.execJS(` + (async () => { + try { + // Try to read all available formats + const clipboardItems = await navigator.clipboard.read(); + + for (const clipboardItem of clipboardItems) { + // Check for image types + for (const type of clipboardItem.types) { + if (type.startsWith('image/')) { + const blob = await clipboardItem.getType(type); + const url = URL.createObjectURL(blob); + document.execCommand('insertHTML', false, ''); + return; + } + } + + // If no image found, try text + if (clipboardItem.types.includes('text/plain')) { + const text = await navigator.clipboard.readText(); + document.execCommand('insertText', false, text); + return; + } + } + } catch(err) { + // Fallback to text-only paste if clipboard access fails + try { + const text = await navigator.clipboard.readText(); + document.execCommand('insertText', false, text); + } catch(fallbackErr) { + console.error('Failed to paste:', err, fallbackErr); + } + } + })() + `) } func (w *windowsWebviewWindow) copy() { - w32.Copy(w.hwnd) + w.execJS(` + (async () => { + try { + const selection = window.getSelection(); + if (!selection.rangeCount) return; + + const range = selection.getRangeAt(0); + const container = document.createElement('div'); + container.appendChild(range.cloneContents()); + + // Check if we have any images in the selection + const images = container.getElementsByTagName('img'); + if (images.length > 0) { + // Handle image copy + const img = images[0]; // Take the first image + const response = await fetch(img.src); + const blob = await response.blob(); + await navigator.clipboard.write([ + new ClipboardItem({ + [blob.type]: blob + }) + ]); + } else { + // Handle text copy + const text = selection.toString(); + if (text) { + await navigator.clipboard.writeText(text); + } + } + } catch(err) { + console.error('Failed to copy:', err); + } + })() + `) } func (w *windowsWebviewWindow) selectAll() { - w32.SelectAll(w.hwnd) + w.execJS("document.execCommand('selectAll')") } func (w *windowsWebviewWindow) undo() { - w32.Undo(w.hwnd) + w.execJS("document.execCommand('undo')") } -func (w *windowsWebviewWindow) delete() { - w32.Delete(w.hwnd) +func (w *windowsWebviewWindow) redo() { + w.execJS("document.execCommand('redo')") } -func (w *windowsWebviewWindow) redo() { +func (w *windowsWebviewWindow) delete() { + w.execJS("document.execCommand('delete')") } func (w *windowsWebviewWindow) handleKeyEvent(_ string) {