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 1, 2023
1 parent b09dfe7 commit 108596d
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 57 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.copyAddressTitle,
image: UIImage(systemName: "doc.on.doc"),
handler: UIAction.deferredActionHandler { _ in
UIPasteboard.general.url = url as URL
}
)
}

static func makeCleanCopyAction(for url: URL) -> UIAction {
return UIAction(
title: Strings.copyCleanLink,
image: UIImage(systemName: "doc.on.doc"),
handler: UIAction.deferredActionHandler { _ in
let cleanedURL = CleanURLService.shared.cleanup(url: url as URL)
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() {
actionMenu = [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 reloadMenu = UIMenu(options: .displayInline, children: [reloadAction])
actionMenu.append(reloadMenu)
}

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)
}

actionMenus.append(reloadMenu)

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

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

return configuration
}

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

var pasteMenuChildren: [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 {
pasteMenuChildren.reverse()
}

return UIMenu(title: "", options: .displayInline, children: pasteMenuChildren)
}

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

return UIMenu(title: "", options: .displayInline, children: [
UIAction.makeCopyAction(for: url),
UIAction.makeCleanCopyAction(for: url)
])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ extension BrowserViewController: WKNavigationDelegate {
adBlockService: self.braveCore.adblockService
)

// Setup any services that use the sanitizerService
CleanURLService.shared.setup(with: self.braveCore.urlSanitizerService)

if let mainDocumentURL = navigationAction.request.mainDocumentURL {
if mainDocumentURL != tab?.currentPageData?.mainFrameURL {
// Clear the current page data if the page changes.
Expand Down Expand Up @@ -996,6 +999,7 @@ extension BrowserViewController: WKUIDelegate {
}
}
openNewPrivateTabAction.accessibilityLabel = "linkContextMenu.openInNewPrivateTab"

actions.append(openNewPrivateTabAction)

if UIApplication.shared.supportsMultipleScenes {
Expand Down Expand Up @@ -1030,15 +1034,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)
copyCleanLinkAction.accessibilityLabel = "linkContextMenu.copyCleanLink"
actions.append(copyCleanLinkAction)

if let braveWebView = webView as? BraveWebView {
let shareAction = UIAction(
Expand Down
28 changes: 28 additions & 0 deletions Sources/Brave/WebFilters/CleanURLService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 weak var urlSanitizerService: URLSanitizerService?

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

/// Setup this downloader with rule `JSON` data.
///
/// - Note: Decoded values that have unknown types are filtered out
func setup(with urlSanitizerService: URLSanitizerService) {
self.urlSanitizerService = urlSanitizerService
}

/// Cleanup the url using the stored matcher
func cleanup(url: URL) -> URL {
return urlSanitizerService?.sanitizedURL(url) ?? url
}
}
8 changes: 8 additions & 0 deletions Sources/BraveStrings/BraveStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,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 108596d

Please sign in to comment.