diff --git a/apps/studio/package.json b/apps/studio/package.json index 95e84fac9f..68ba874c68 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -83,7 +83,7 @@ "@wp-playground/blueprints": "3.1.45", "cli-table3": "^0.6.5", "date-fns": "^4.1.0", - "electron": "43.4.1", + "electron": "44.1.1", "electron-devtools-installer": "^4.0.0", "electron-vite": "6.0.0-beta.1", "immer": "^11.0.1", diff --git a/apps/studio/src/tests/text-context-menu.test.ts b/apps/studio/src/tests/text-context-menu.test.ts index 73feba0165..401f49e0d6 100644 --- a/apps/studio/src/tests/text-context-menu.test.ts +++ b/apps/studio/src/tests/text-context-menu.test.ts @@ -14,7 +14,7 @@ import { vi.mock( 'electron', () => ( { BrowserWindow: { fromWebContents: vi.fn() }, Menu: { buildFromTemplate: vi.fn() }, - clipboard: { availableFormats: vi.fn(), writeText: vi.fn() }, + clipboard: { has: vi.fn(), writeText: vi.fn() }, } ) ); function makeContext( overrides: Partial< TextContextMenuContext > = {} ): TextContextMenuContext { @@ -239,11 +239,13 @@ describe( 'buildTextContextMenuTemplate', () => { } ); describe( 'hasTextClipboardFormat', () => { - it( 'detects normalized and native plain-text formats without reading clipboard contents', () => { - expect( hasTextClipboardFormat( [ 'text/plain' ] ) ).toBe( true ); - expect( hasTextClipboardFormat( [ 'text/plain;charset=utf-8' ] ) ).toBe( true ); - expect( hasTextClipboardFormat( [ 'public.utf8-plain-text' ] ) ).toBe( true ); - expect( hasTextClipboardFormat( [ 'image/png' ] ) ).toBe( false ); + it( 'detects plain text without reading clipboard contents', async () => { + vi.mocked( clipboard.has ).mockResolvedValue( true ); + await expect( hasTextClipboardFormat() ).resolves.toBe( true ); + expect( clipboard.has ).toHaveBeenCalledWith( 'text/plain' ); + + vi.mocked( clipboard.has ).mockResolvedValue( false ); + await expect( hasTextClipboardFormat() ).resolves.toBe( false ); } ); } ); @@ -251,7 +253,7 @@ describe( 'showTextContextMenu', () => { it( 'returns the selected text when Quote in composer is chosen', async () => { const popup = vi.fn(); vi.mocked( BrowserWindow.fromWebContents ).mockReturnValue( null ); - vi.mocked( clipboard.availableFormats ).mockReturnValue( [] ); + vi.mocked( clipboard.has ).mockResolvedValue( false ); vi.mocked( Menu.buildFromTemplate ).mockReturnValue( { popup } as unknown as Menu ); const event = { sender: { showDefinitionForSelection: vi.fn() }, @@ -261,6 +263,8 @@ describe( 'showTextContextMenu', () => { event, makeContext( { selectionText: 'Selected reply', canQuoteSelection: true } ) ); + // The clipboard check is awaited before the menu is built. + await vi.waitFor( () => expect( Menu.buildFromTemplate ).toHaveBeenCalled() ); const template = vi.mocked( Menu.buildFromTemplate ).mock.calls[ 0 ][ 0 ]; const quote = template.find( ( item ) => item.label === 'Quote in composer' ); ( quote?.click as () => void )(); diff --git a/apps/studio/src/text-context-menu.ts b/apps/studio/src/text-context-menu.ts index 3d995823da..1b5de7c6f1 100644 --- a/apps/studio/src/text-context-menu.ts +++ b/apps/studio/src/text-context-menu.ts @@ -58,11 +58,10 @@ function toLookUpLabel( selection: string ): string { return sprintf( __( 'Look Up ā€œ%sā€' ), truncated ); } -export function hasTextClipboardFormat( formats: string[] ): boolean { - return formats.some( ( format ) => { - const normalized = format.toLowerCase(); - return normalized.startsWith( 'text/plain' ) || normalized.includes( 'plain-text' ); - } ); +// Electron normalizes clipboard formats to W3C MIME types, so plain text is +// reported as `text/plain` on every platform rather than the native spellings. +export function hasTextClipboardFormat(): Promise< boolean > { + return clipboard.has( 'text/plain' ); } /** @@ -129,15 +128,15 @@ export async function showTextContextMenu( context, { lookUpSelection: () => event.sender.showDefinitionForSelection(), - copyMessage: ( text ) => clipboard.writeText( text ), - copyCode: ( text ) => clipboard.writeText( text ), + copyMessage: ( text ) => void clipboard.writeText( text ), + copyCode: ( text ) => void clipboard.writeText( text ), quoteSelection: () => { result = { action: 'quote-selection', selectionText: context.selectionText.trim() }; }, }, { platform: process.platform, - canPaste: hasTextClipboardFormat( clipboard.availableFormats() ), + canPaste: await hasTextClipboardFormat(), } ); diff --git a/apps/studio/src/updates.ts b/apps/studio/src/updates.ts index 56f5533b9d..e081fe4234 100644 --- a/apps/studio/src/updates.ts +++ b/apps/studio/src/updates.ts @@ -402,7 +402,7 @@ async function showLinuxUpdateAvailableNotice( version: string, downloadUrl: str ); return; } - clipboard.writeText( command ); + await clipboard.writeText( command ); void shellOpenExternalWrapper( parsedUrl.toString() ); } catch { Sentry.captureException( new Error( `Malformed downloadUrl: ${ downloadUrl }` ) ); diff --git a/package-lock.json b/package-lock.json index 766803165f..54bba77912 100644 --- a/package-lock.json +++ b/package-lock.json @@ -576,7 +576,7 @@ "@wp-playground/blueprints": "3.1.45", "cli-table3": "^0.6.5", "date-fns": "^4.1.0", - "electron": "43.4.1", + "electron": "44.1.1", "electron-devtools-installer": "^4.0.0", "electron-vite": "6.0.0-beta.1", "immer": "^11.0.1", @@ -16690,9 +16690,9 @@ "license": "MIT" }, "node_modules/electron": { - "version": "43.4.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-43.4.1.tgz", - "integrity": "sha512-5b+EuiwkgG5iRcsEL34rimgRpkYp15SsfZOa0pC5kXs0Tb82TH4n95rpQzTZa7yRCbA7tm0WoEbuBL6NaAhAcA==", + "version": "44.1.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-44.1.1.tgz", + "integrity": "sha512-N2WCq2sbOkqQgvXJYx2lS6UiO8bF+Yr67trDnS6JKa2WxTCRQsAjGl57SUtdW9h6r5PlduBFjIhxhgd3dzv1hg==", "dev": true, "license": "MIT", "dependencies": { @@ -17029,9 +17029,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.405", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.405.tgz", - "integrity": "sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew==", + "version": "1.5.420", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", + "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==", "dev": true, "license": "ISC" }, @@ -23838,7 +23838,9 @@ } }, "node_modules/node-abi": { - "version": "3.92.0", + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz", + "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==", "dev": true, "license": "MIT", "dependencies": {