From e592b96c599f39b10b097ff5ba6aafed6cc3f4c2 Mon Sep 17 00:00:00 2001 From: SimonLaux Date: Fri, 8 Sep 2023 00:24:56 +0200 Subject: [PATCH 1/2] Offer to copy non-HTTP links to the clipboard instead of trying to open them --- CHANGELOG.md | 1 + _locales/_untranslated_en.json | 3 +++ src/renderer/components/helpers/ChatMethods.ts | 4 ++-- .../components/helpers/ClickableLink.tsx | 4 ++-- .../components/helpers/LinkConfirmation.ts | 17 +++++++++++++++++ src/renderer/components/message/Link.tsx | 9 +++++---- src/renderer/runtime.ts | 6 +++++- 7 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/renderer/components/helpers/LinkConfirmation.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 56e99e5780..3a2d0e8224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added ### Changed +- Offer to copy non-HTTP links to the clipboard instead of trying to open them. ### Fixed diff --git a/_locales/_untranslated_en.json b/_locales/_untranslated_en.json index deac8dc3a3..ba82347b59 100644 --- a/_locales/_untranslated_en.json +++ b/_locales/_untranslated_en.json @@ -70,5 +70,8 @@ }, "open_sticker_folder": { "message": "Open Sticker Folder" + }, + "desktop_offer_copy_non_web_link_to_clipboard": { + "message": "The link \"%1$d\" can not be opened in the web browser. Do you want to copy the link to the clipboard instead?" } } diff --git a/src/renderer/components/helpers/ChatMethods.ts b/src/renderer/components/helpers/ChatMethods.ts index 5f27aa3651..14c2681a0e 100644 --- a/src/renderer/components/helpers/ChatMethods.ts +++ b/src/renderer/components/helpers/ChatMethods.ts @@ -1,7 +1,6 @@ import ChatStore, { ChatView } from '../../stores/chat' import { ScreenContext, unwrapContext } from '../../contexts' import { C } from '@deltachat/jsonrpc-client' -import { runtime } from '../../runtime' import { getLogger } from '../../../shared/logger' import AlertDialog from '../dialogs/AlertDialog' import { BackendRemote, EffectfulBackendActions, Type } from '../../backend-com' @@ -12,6 +11,7 @@ import ConfirmationDialog from '../dialogs/ConfirmationDialog' import { T } from '@deltachat/jsonrpc-client' import { sendMessageParams } from '../../../shared/shared-types' import chatStore from '../../stores/chat' +import { openLinkSafely } from './LinkConfirmation' const log = getLogger('renderer/message') @@ -202,7 +202,7 @@ export async function joinCall( throw new Error('Message has no video chat url') } - return runtime.openLink(message.videochatUrl) + return openLinkSafely(message.videochatUrl) } catch (error: todo) { log.error('failed to join call', error) screenContext.openDialog(AlertDialog, { message: error.toString() }) diff --git a/src/renderer/components/helpers/ClickableLink.tsx b/src/renderer/components/helpers/ClickableLink.tsx index 19b172a8b3..2543c67b6f 100644 --- a/src/renderer/components/helpers/ClickableLink.tsx +++ b/src/renderer/components/helpers/ClickableLink.tsx @@ -1,5 +1,5 @@ import React, { PropsWithChildren } from 'react' -import { runtime } from '../../runtime' +import { openLinkSafely } from './LinkConfirmation' export default class ClickableLink extends React.Component< PropsWithChildren<{ @@ -9,7 +9,7 @@ export default class ClickableLink extends React.Component< > { onClick(event: React.MouseEvent) { event.preventDefault() - runtime.openLink(this.props.href) + openLinkSafely(this.props.href) } render() { diff --git a/src/renderer/components/helpers/LinkConfirmation.ts b/src/renderer/components/helpers/LinkConfirmation.ts new file mode 100644 index 0000000000..1af21e8e57 --- /dev/null +++ b/src/renderer/components/helpers/LinkConfirmation.ts @@ -0,0 +1,17 @@ +import { runtime } from '../../runtime' +import ConfirmationDialog from '../dialogs/ConfirmationDialog' + +/** opens http, https and mailto links, offers to copy all other links */ +export function openLinkSafely(url: string) { + const tx = window.static_translate + if (url.startsWith('http') || url.startsWith('mailto')) { + runtime.openLink(url) + } else { + window.__openDialog(ConfirmationDialog, { + message: tx('desktop_offer_copy_non_web_link_to_clipboard', url), + confirmLabel: tx('menu_copy_link_to_clipboard'), + cancelLabel: tx('no'), + cb: yes => yes && runtime.writeClipboardText(url), + }) + } +} diff --git a/src/renderer/components/message/Link.tsx b/src/renderer/components/message/Link.tsx index 8b478646f9..8d556a9af6 100644 --- a/src/renderer/components/message/Link.tsx +++ b/src/renderer/components/message/Link.tsx @@ -13,6 +13,7 @@ import chatStore from '../../stores/chat' import reactStringReplace from 'react-string-replace' import { runtime } from '../../runtime' import { LinkDestination } from '@deltachat/message_parser_wasm' +import { openLinkSafely } from '../helpers/LinkConfirmation' const log = getLogger('renderer/LabeledLink') @@ -52,7 +53,7 @@ export const LabeledLink = ({ //check if domain is trusted, or if there is no domain like on mailto just open it if (isDeviceChat || !hostName || isDomainTrusted(hostName)) { - runtime.openLink(target) + openLinkSafely(target) return } // not trusted - ask for confirmation from user @@ -133,7 +134,7 @@ function labeledLinkConfirmationDialog( // trust url trustDomain(hostname) } - runtime.openLink(target) + openLinkSafely(target) }} > {tx('open')} @@ -164,7 +165,7 @@ export const Link = ({ destination }: { destination: LinkDestination }) => { punycode.punycode_encoded_url ) } else { - runtime.openLink(target) + openLinkSafely(target) } } return ( @@ -229,7 +230,7 @@ function openPunycodeUrlConfirmationDialog( className={`delta-button bold primary`} onClick={() => { onClose() - runtime.openLink(asciiUrl) + openLinkSafely(asciiUrl) }} > {tx('open')} diff --git a/src/renderer/runtime.ts b/src/renderer/runtime.ts index 2ef420ab49..1c0b6531e1 100644 --- a/src/renderer/runtime.ts +++ b/src/renderer/runtime.ts @@ -493,8 +493,12 @@ class Electron implements Runtime { openLink(link: string): void { if (link.startsWith('mailto:')) { processOpenQrUrl(link) - } else { + } else if (link.startsWith('http')) { ipcBackend.invoke('electron.shell.openExternal', link) + } else { + log.error('tried to open a non mailto or http/https external link', { + link, + }) } } private rc_config: RC_Config | null = null From 4e2ff7f3950a4942cad26dd6141cf81b1135c4bf Mon Sep 17 00:00:00 2001 From: SimonLaux Date: Fri, 8 Sep 2023 00:30:13 +0200 Subject: [PATCH 2/2] check schemes more explicitly --- src/renderer/components/helpers/LinkConfirmation.ts | 6 +++++- src/renderer/runtime.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/helpers/LinkConfirmation.ts b/src/renderer/components/helpers/LinkConfirmation.ts index 1af21e8e57..7b1ed05c1b 100644 --- a/src/renderer/components/helpers/LinkConfirmation.ts +++ b/src/renderer/components/helpers/LinkConfirmation.ts @@ -4,7 +4,11 @@ import ConfirmationDialog from '../dialogs/ConfirmationDialog' /** opens http, https and mailto links, offers to copy all other links */ export function openLinkSafely(url: string) { const tx = window.static_translate - if (url.startsWith('http') || url.startsWith('mailto')) { + if ( + url.startsWith('http:') || + url.startsWith('https:') || + url.startsWith('mailto:') + ) { runtime.openLink(url) } else { window.__openDialog(ConfirmationDialog, { diff --git a/src/renderer/runtime.ts b/src/renderer/runtime.ts index 1c0b6531e1..25398cb76d 100644 --- a/src/renderer/runtime.ts +++ b/src/renderer/runtime.ts @@ -493,7 +493,7 @@ class Electron implements Runtime { openLink(link: string): void { if (link.startsWith('mailto:')) { processOpenQrUrl(link) - } else if (link.startsWith('http')) { + } else if (link.startsWith('http:') || link.startsWith('https:')) { ipcBackend.invoke('electron.shell.openExternal', link) } else { log.error('tried to open a non mailto or http/https external link', {