Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Fix #6179: Add clean copy feature
Browse files Browse the repository at this point in the history
  • Loading branch information
cuba committed Sep 7, 2023
1 parent d971006 commit 2709264
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 58 deletions.
52 changes: 52 additions & 0 deletions Sources/Brave/Extensions/UIAction+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2023 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import UIKit
import Strings

extension UIAction {
static func makePasteAndGoAction(pasteCallback: @escaping (String) -> Void) -> UIAction {
return UIAction(
identifier: .pasteAndGo,
handler: UIAction.deferredActionHandler { _ in
if let pasteboardContents = UIPasteboard.general.string {
pasteCallback(pasteboardContents)
}
}
)
}

static func makePasteAction(pasteCallback: @escaping (String) -> Void) -> UIAction {
return UIAction(
identifier: .paste,
handler: UIAction.deferredActionHandler { _ in
if let pasteboardContents = UIPasteboard.general.string {
pasteCallback(pasteboardContents)
}
}
)
}

static func makeCopyAction(for url: URL) -> UIAction {
return UIAction(
title: Strings.copyLinkActionTitle,
image: UIImage(systemName: "doc.on.doc"),
handler: UIAction.deferredActionHandler { _ in
UIPasteboard.general.url = url as URL
}
)
}

static func makeCleanCopyAction(for url: URL, isPrivateMode: Bool) -> UIAction {
return UIAction(
title: Strings.copyCleanLink,
image: UIImage(systemName: "doc.on.doc"),
handler: UIAction.deferredActionHandler { _ in
let cleanedURL = CleanURLService.shared.cleanup(url: url, isPrivateMode: isPrivateMode)
UIPasteboard.general.url = cleanedURL
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -913,13 +913,17 @@ extension BrowserViewController: ToolbarDelegate {

extension BrowserViewController: UIContextMenuInteractionDelegate {
public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {

let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [unowned self] _ in
var actionMenus: [UIMenu?] = []
var pasteMenuChildren: [UIAction] = []

let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [unowned self] _ in
var actionMenu: [UIMenu] = []
let tab = tabManager.selectedTab
var reloadMenu: UIMenu?

if let pasteMenu = makePasteMenu() {
actionMenu.append(pasteMenu)
}

if let copyMenu = makeCopyMenu(isPrivateMode: tab?.isPrivate ?? true) {
actionMenu.append(copyMenu)
}

if let url = tab?.url, url.isWebPage() {
let reloadTitle = tab?.isDesktopSite == true ? Strings.appMenuViewMobileSiteTitleString : Strings.appMenuViewDesktopSiteTitleString
Expand All @@ -931,52 +935,11 @@ extension BrowserViewController: UIContextMenuInteractionDelegate {
tab?.switchUserAgent()
})

reloadMenu = UIMenu(options: .displayInline, children: [reloadAction])
}

let pasteGoAction = UIAction(
identifier: .pasteAndGo,
handler: UIAction.deferredActionHandler { _ in
if let pasteboardContents = UIPasteboard.general.string {
self.topToolbar(self.topToolbar, didSubmitText: pasteboardContents)
}
})

let pasteAction = UIAction(
identifier: .paste,
handler: UIAction.deferredActionHandler { _ in
if let pasteboardContents = UIPasteboard.general.string {
self.topToolbar.enterOverlayMode(pasteboardContents, pasted: true, search: true)
}
})

pasteMenuChildren = [pasteGoAction, pasteAction]

if #unavailable(iOS 16.0), isUsingBottomBar {
pasteMenuChildren.reverse()
}

let copyAction = UIAction(
title: Strings.copyAddressTitle,
image: UIImage(systemName: "doc.on.doc"),
handler: UIAction.deferredActionHandler { _ in
if let url = self.topToolbar.currentURL {
UIPasteboard.general.url = url as URL
}
})

let copyMenu = UIMenu(options: .displayInline, children: [copyAction])

if UIPasteboard.general.hasStrings || UIPasteboard.general.hasURLs {
let pasteMenu = UIMenu(options: .displayInline, children: pasteMenuChildren)
actionMenus.append(contentsOf: [pasteMenu, copyMenu])
} else {
actionMenus.append(copyMenu)
let reloadMenu = UIMenu(options: .displayInline, children: [reloadAction])
actionMenu.append(reloadMenu)
}

actionMenus.append(reloadMenu)

return UIMenu(children: actionMenus.compactMap { $0 })
return UIMenu(children: actionMenu)
}

if #available(iOS 16.0, *) {
Expand All @@ -985,4 +948,38 @@ extension BrowserViewController: UIContextMenuInteractionDelegate {

return configuration
}

private func makePasteMenu() -> UIMenu? {
guard UIPasteboard.general.hasStrings || UIPasteboard.general.hasURLs else { return nil }

var children: [UIAction] = [
UIAction.makePasteAndGoAction(pasteCallback: { pasteboardContents in
self.topToolbar(self.topToolbar, didSubmitText: pasteboardContents)
}),
UIAction.makePasteAction(pasteCallback: { pasteboardContents in
self.topToolbar.enterOverlayMode(pasteboardContents, pasted: true, search: true)
})
]

if #unavailable(iOS 16.0), isUsingBottomBar {
children.reverse()
}

return UIMenu(options: .displayInline, children: children)
}

private func makeCopyMenu(isPrivateMode: Bool) -> UIMenu? {
guard let url = self.topToolbar.currentURL else { return nil }

var children: [UIAction] = [
UIAction.makeCopyAction(for: url),
UIAction.makeCleanCopyAction(for: url, isPrivateMode: isPrivateMode)
]

if #unavailable(iOS 16.0), isUsingBottomBar {
children.reverse()
}

return UIMenu(options: .displayInline, children: children)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,7 @@ extension BrowserViewController: WKUIDelegate {
}
}
openNewPrivateTabAction.accessibilityLabel = "linkContextMenu.openInNewPrivateTab"

actions.append(openNewPrivateTabAction)

if UIApplication.shared.supportsMultipleScenes {
Expand Down Expand Up @@ -1018,15 +1019,13 @@ extension BrowserViewController: WKUIDelegate {
actions.append(openNewPrivateWindowAction)
}

let copyAction = UIAction(
title: Strings.copyLinkActionTitle,
image: UIImage(systemName: "doc.on.doc")
) { _ in
UIPasteboard.general.url = url
}
let copyAction = UIAction.makeCopyAction(for: url)
copyAction.accessibilityLabel = "linkContextMenu.copyLink"

actions.append(copyAction)

let copyCleanLinkAction = UIAction.makeCleanCopyAction(for: url, isPrivateMode: currentTab.isPrivate)
copyCleanLinkAction.accessibilityLabel = "linkContextMenu.copyCleanLink"
actions.append(copyCleanLinkAction)

if let braveWebView = webView as? BraveWebView {
let shareAction = UIAction(
Expand Down
27 changes: 27 additions & 0 deletions Sources/Brave/WebFilters/CleanURLService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2023 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import Foundation
import BraveCore

/// A helper class that helps us clean urls for "clean copy" feature
class CleanURLService {
public static let shared = CleanURLService()

private lazy var urlSanitizerService = URLSanitizerServiceFactory.get(privateMode: false)
private lazy var privateURLSanitizerService = URLSanitizerServiceFactory.get(privateMode: true)

/// Initialize this instance with a network manager
init() {}

/// Cleanup the url using the stored matcher
func cleanup(url: URL, isPrivateMode: Bool) -> URL {
if isPrivateMode {
return privateURLSanitizerService?.sanitizeURL(url) ?? url
} else {
return urlSanitizerService?.sanitizeURL(url) ?? url
}
}
}
9 changes: 8 additions & 1 deletion Sources/BraveStrings/BraveStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ extension Strings {
public static let download = NSLocalizedString("CommonDownload", tableName: "BraveShared", bundle: .module, value: "Download", comment: "Text to choose for downloading a file (example: saving an image to phone)")
public static let showLinkPreviewsActionTitle = NSLocalizedString("ShowLinkPreviewsActionTitle", tableName: "BraveShared", bundle: .module, value: "Show Link Previews", comment: "Context menu item for showing link previews")
public static let hideLinkPreviewsActionTitle = NSLocalizedString("HideLinkPreviewsActionTitle", tableName: "BraveShared", bundle: .module, value: "Hide Link Previews", comment: "Context menu item for hiding link previews")
public static let copyAddressTitle = NSLocalizedString("CopyAddressTitle", bundle: .module, value: "Copy Address", comment: "The title for the button that lets you copy the url from the location bar.")
public static let learnMore = NSLocalizedString(
"learnMore", tableName: "BraveShared",
bundle: .module, value: "Learn More", comment: "")
Expand Down Expand Up @@ -974,6 +973,14 @@ extension Strings {
public static let codeWordInputHelp = NSLocalizedString("CodeWordInputHelp", tableName: "BraveShared", bundle: .module, value: "Type your supplied sync chain code words into the form below.", comment: "Code words input help")
public static let copyToClipboard = NSLocalizedString("CopyToClipboard", tableName: "BraveShared", bundle: .module, value: "Copy to Clipboard", comment: "Copy codewords title")
public static let copiedToClipboard = NSLocalizedString("CopiedToClipboard", tableName: "BraveShared", bundle: .module, value: "Copied to Clipboard!", comment: "Copied codewords title")

/// A menu option available when long pressing on a link which allows you to copy a clean version of the url which strips out some query parameters.
public static let copyCleanLink = NSLocalizedString(
"CopyCleanLink", tableName: "BraveShared", bundle: .module,
value: "Copy Clean Link",
comment: "A menu option available when long pressing on a link which allows you to copy a clean version of the url which strips out some query parameters."
)

public static let syncUnsuccessful = NSLocalizedString("SyncUnsuccessful", tableName: "BraveShared", bundle: .module, value: "Unsuccessful", comment: "")
public static let syncUnableCreateGroup = NSLocalizedString("SyncUnableCreateGroup", tableName: "BraveShared", bundle: .module, value: "Can't sync this device", comment: "Description on popup when setting up a sync group fails")
public static let copied = NSLocalizedString("Copied", tableName: "BraveShared", bundle: .module, value: "Copied!", comment: "Copied action complete title")
Expand Down

0 comments on commit 2709264

Please sign in to comment.