diff --git a/Mac/Base.lproj/Main.storyboard b/Mac/Base.lproj/Main.storyboard index a1f665aa2..1de711d58 100644 --- a/Mac/Base.lproj/Main.storyboard +++ b/Mac/Base.lproj/Main.storyboard @@ -157,6 +157,18 @@ + + + + + + + + + + + + diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 59e58c7e4..ba4189921 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -204,6 +204,14 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { public func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { + if item.action == #selector(copyArticleURL(_:)) { + return canCopyArticleURL() + } + + if item.action == #selector(copyExternalURL(_:)) { + return canCopyExternalURL() + } + if item.action == #selector(openArticleInBrowser(_:)) { if let item = item as? NSMenuItem, item.keyEquivalentModifierMask.contains(.shift) { item.title = Browser.titleForOpenInBrowserInverted @@ -302,6 +310,18 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } + @IBAction func copyArticleURL(_ sender: Any?) { + if let link = currentLink { + URLPasteboardWriter.write(urlString: link, to: .general) + } + } + + @IBAction func copyExternalURL(_ sender: Any?) { + if let link = oneSelectedArticle?.externalURL { + URLPasteboardWriter.write(urlString: link, to: .general) + } + } + @IBAction func openArticleInBrowser(_ sender: Any?) { if let link = currentLink { Browser.open(link, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false) @@ -1036,6 +1056,14 @@ private extension MainWindowController { } // MARK: - Command Validation + + func canCopyArticleURL() -> Bool { + return currentLink != nil + } + + func canCopyExternalURL() -> Bool { + return oneSelectedArticle?.externalURL != nil && oneSelectedArticle?.externalURL != currentLink + } func canGoToNextUnread(wrappingToTop wrapping: Bool = false) -> Bool { diff --git a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift index 1df39dca4..da56e31f3 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift @@ -90,6 +90,13 @@ extension TimelineViewController { } Browser.open(urlString, inBackground: false) } + + @objc func copyURLFromContextualMenu(_ sender: Any?) { + guard let menuItem = sender as? NSMenuItem, let urlString = menuItem.representedObject as? String else { + return + } + URLPasteboardWriter.write(urlString: urlString, to: .general) + } @objc func performShareServiceFromContextualMenu(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem, let sharingCommandInfo = menuItem.representedObject as? SharingCommandInfo else { @@ -168,6 +175,12 @@ private extension TimelineViewController { if articles.count == 1, let link = articles.first!.preferredLink { menu.addSeparatorIfNeeded() menu.addItem(openInBrowserMenuItem(link)) + menu.addSeparatorIfNeeded() + menu.addItem(copyArticleURLMenuItem(link)) + + if let externalLink = articles.first?.externalURL, externalLink != link { + menu.addItem(copyExternalURLMenuItem(externalLink)) + } } if let sharingMenu = shareMenu(for: articles) { @@ -260,6 +273,15 @@ private extension TimelineViewController { return menuItem(NSLocalizedString("Open in Browser", comment: "Command"), #selector(openInBrowserFromContextualMenu(_:)), urlString) } + + func copyArticleURLMenuItem(_ urlString: String) -> NSMenuItem { + return menuItem(NSLocalizedString("Copy Article URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString) + } + + func copyExternalURLMenuItem(_ urlString: String) -> NSMenuItem { + return menuItem(NSLocalizedString("Copy External URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString) + } + func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem { diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 9cdd5d120..046d3e6cc 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -377,6 +377,17 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner menuElements.append(UIMenu(title: "", options: .displayInline, children: secondaryActions)) } + var copyActions = [UIAction]() + if let action = self.copyArticleURLAction(article) { + copyActions.append(action) + } + if let action = self.copyExternalURLAction(article) { + copyActions.append(action) + } + if !copyActions.isEmpty { + menuElements.append(UIMenu(title: "", options: .displayInline, children: copyActions)) + } + if let action = self.openInBrowserAction(article) { menuElements.append(UIMenu(title: "", options: .displayInline, children: [action])) } @@ -902,6 +913,25 @@ private extension MasterTimelineViewController { } return action } + + func copyArticleURLAction(_ article: Article) -> UIAction? { + guard let preferredLink = article.preferredLink, let url = URL(string: preferredLink) else { return nil } + let title = NSLocalizedString("Copy Article URL", comment: "Copy Article URL") + let action = UIAction(title: title, image: AppAssets.copyImage) { action in + UIPasteboard.general.url = url + } + return action + } + + func copyExternalURLAction(_ article: Article) -> UIAction? { + guard let externalURL = article.externalURL, externalURL != article.preferredLink, let url = URL(string: externalURL) else { return nil } + let title = NSLocalizedString("Copy External URL", comment: "Copy External URL") + let action = UIAction(title: title, image: AppAssets.copyImage) { action in + UIPasteboard.general.url = url + } + return action + } + func openInBrowserAction(_ article: Article) -> UIAction? { guard let _ = article.preferredURL else { return nil }