diff --git a/Account/Sources/Account/FeedProvider/Twitter/TwitterSymbol.swift b/Account/Sources/Account/FeedProvider/Twitter/TwitterSymbol.swift index 525a1a595..9b1119743 100644 --- a/Account/Sources/Account/FeedProvider/Twitter/TwitterSymbol.swift +++ b/Account/Sources/Account/FeedProvider/Twitter/TwitterSymbol.swift @@ -10,18 +10,18 @@ import Foundation struct TwitterSymbol: Codable, TwitterEntity { - let name: String? + let text: String? let indices: [Int]? enum CodingKeys: String, CodingKey { - case name = "name" + case text = "text" case indices = "indices" } func renderAsHTML() -> String { var html = String() - if let name = name { - html += "$\(name)" + if let text = text { + html += "$\(text)" } return html } diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index 418711849..418c1c81a 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -1259,7 +1259,7 @@ private extension FeedbinAccountDelegate { let parsedItems: [ParsedItem] = entries.map { entry in let authors = Set([ParsedAuthor(name: entry.authorName, url: entry.jsonFeed?.jsonFeedAuthor?.url, avatarURL: entry.jsonFeed?.jsonFeedAuthor?.avatarURL, emailAddress: nil)]) - return ParsedItem(syncServiceID: String(entry.articleID), uniqueID: String(entry.articleID), feedURL: String(entry.feedID), url: entry.url, externalURL: nil, title: entry.title, language: nil, contentHTML: entry.contentHTML, contentText: nil, summary: entry.summary, imageURL: nil, bannerImageURL: nil, datePublished: entry.parsedDatePublished, dateModified: nil, authors: authors, tags: nil, attachments: nil) + return ParsedItem(syncServiceID: String(entry.articleID), uniqueID: String(entry.articleID), feedURL: String(entry.feedID), url: entry.url, externalURL: entry.jsonFeed?.jsonFeedExternalURL, title: entry.title, language: nil, contentHTML: entry.contentHTML, contentText: nil, summary: entry.summary, imageURL: nil, bannerImageURL: nil, datePublished: entry.parsedDatePublished, dateModified: nil, authors: authors, tags: nil, attachments: nil) } return Set(parsedItems) diff --git a/Account/Sources/Account/Feedbin/FeedbinEntry.swift b/Account/Sources/Account/Feedbin/FeedbinEntry.swift index 741d6abfc..27e23a140 100644 --- a/Account/Sources/Account/Feedbin/FeedbinEntry.swift +++ b/Account/Sources/Account/Feedbin/FeedbinEntry.swift @@ -52,9 +52,11 @@ final class FeedbinEntry: Decodable { struct FeedbinEntryJSONFeed: Decodable { let jsonFeedAuthor: FeedbinEntryJSONFeedAuthor? - + let jsonFeedExternalURL: String? + enum CodingKeys: String, CodingKey { case jsonFeedAuthor = "author" + case jsonFeedExternalURL = "external_url" } public init(from decoder: Decoder) throws { @@ -64,6 +66,11 @@ struct FeedbinEntryJSONFeed: Decodable { } catch { jsonFeedAuthor = nil } + do { + jsonFeedExternalURL = try container.decode(String.self, forKey: .jsonFeedExternalURL) + } catch { + jsonFeedExternalURL = nil + } } } diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index ae78a0d4a..e6ccb7b69 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -17,7 +17,7 @@ protocol DetailWebViewControllerDelegate: AnyObject { func mouseDidExit(_: DetailWebViewController) } -final class DetailWebViewController: NSViewController, WKUIDelegate { +final class DetailWebViewController: NSViewController { weak var delegate: DetailWebViewControllerDelegate? var webView: DetailWebView! @@ -184,16 +184,22 @@ extension DetailWebViewController: WKScriptMessageHandler { } } -// MARK: - WKNavigationDelegate +// MARK: - WKNavigationDelegate & WKUIDelegate -extension DetailWebViewController: WKNavigationDelegate { +extension DetailWebViewController: WKNavigationDelegate, WKUIDelegate { + + // Bottleneck through which WebView-based URL opens go + func openInBrowser(_ url: URL, flags: NSEvent.ModifierFlags) { + let invert = flags.contains(.shift) || flags.contains(.command) + Browser.open(url.absoluteString, invertPreference: invert) + } + + // WKNavigationDelegate public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if navigationAction.navigationType == .linkActivated { if let url = navigationAction.request.url { - let flags = navigationAction.modifierFlags - let invert = flags.contains(.shift) || flags.contains(.command) - Browser.open(url.absoluteString, invertPreference: invert) + self.openInBrowser(url, flags: navigationAction.modifierFlags) } decisionHandler(.cancel) return @@ -216,6 +222,20 @@ extension DetailWebViewController: WKNavigationDelegate { } } } + + // WKUIDelegate + + func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { + // This method is reached when WebKit handles a JavaScript based window.open() invocation, for example. One + // example where this is used is in YouTube's embedded video player when a user clicks on the video's title + // or on the "Watch in YouTube" button. For our purposes we'll handle such window.open calls the same way we + // handle clicks on a URL. + if let url = navigationAction.request.url { + self.openInBrowser(url, flags: navigationAction.modifierFlags) + } + + return nil + } } // MARK: - Private diff --git a/Shared/Article Rendering/shared.css b/Shared/Article Rendering/shared.css index 7f4c6a255..713924ce3 100644 --- a/Shared/Article Rendering/shared.css +++ b/Shared/Article Rendering/shared.css @@ -272,7 +272,14 @@ blockquote { border-top: 1px solid var(--header-table-border-color); } +/* Hide the external link at the bottom of Daring Fireball posts */ + +.x-netnewswire-hide { + display: none; +} + /* see removeWpSmiley; this rule is kept in case a wp-smiley is encountered without alt text */ + .wp-smiley { height: 1em; max-height: 1em; diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift index c5b776126..fe6188903 100644 --- a/iOS/Article/WebViewController.swift +++ b/iOS/Article/WebViewController.swift @@ -344,16 +344,7 @@ extension WebViewController: WKNavigationDelegate { let components = URLComponents(url: url, resolvingAgainstBaseURL: false) if components?.scheme == "http" || components?.scheme == "https" { decisionHandler(.cancel) - - // If the resource cannot be opened with an installed app, present the web view. - UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { didOpen in - assert(Thread.isMainThread) - guard didOpen == false else { - return - } - let vc = SFSafariViewController(url: url) - self.present(vc, animated: true) - } + openURL(url) } else if components?.scheme == "mailto" { decisionHandler(.cancel) @@ -392,12 +383,23 @@ extension WebViewController: WKNavigationDelegate { // MARK: WKUIDelegate extension WebViewController: WKUIDelegate { + func webView(_ webView: WKWebView, contextMenuForElement elementInfo: WKContextMenuElementInfo, willCommitWithAnimator animator: UIContextMenuInteractionCommitAnimating) { // We need to have at least an unimplemented WKUIDelegate assigned to the WKWebView. This makes the // link preview launch Safari when the link preview is tapped. In theory, you shoud be able to get // the link from the elementInfo above and transition to SFSafariViewController instead of launching // Safari. As the time of this writing, the link in elementInfo is always nil. ¯\_(ツ)_/¯ } + + func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { + guard let url = navigationAction.request.url else { + return nil + } + + openURL(url) + return nil + } + } // MARK: WKScriptMessageHandler @@ -745,7 +747,19 @@ private extension WebViewController { self?.showActivityDialog() } } - + + // If the resource cannot be opened with an installed app, present the web view. + func openURL(_ url: URL) { + UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { didOpen in + assert(Thread.isMainThread) + guard didOpen == false else { + return + } + let vc = SFSafariViewController(url: url) + self.present(vc, animated: true) + } + } + } // MARK: Find in Article