From cdf643c2a6f951aad8f36778b2d24dc3e7ff19b0 Mon Sep 17 00:00:00 2001 From: Ben Ubois Date: Fri, 20 Sep 2019 17:21:01 -0700 Subject: [PATCH 1/7] Make iFrames responsive. --- Mac/MainWindow/Detail/styleSheet.css | 14 ++++ .../Article Rendering/ArticleRenderer.swift | 72 +++++++------------ iOS/Resources/styleSheet.css | 14 ++++ 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/Mac/MainWindow/Detail/styleSheet.css b/Mac/MainWindow/Detail/styleSheet.css index 97ab0e019..107a7cb7e 100644 --- a/Mac/MainWindow/Detail/styleSheet.css +++ b/Mac/MainWindow/Detail/styleSheet.css @@ -133,6 +133,20 @@ figcaption { line-height: 1.3em; } +.iframeWrap { + position: relative; + display: block; + padding-top: 56.25%; +} + +.iframeWrap iframe { + position: absolute; + top: 0; + left: 0; + height: 100% !important; + width: 100% !important; +} + /*Block ads and junk*/ iframe[src*="feedads"], diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 2fac026d9..b849ba206 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -312,8 +312,6 @@ private extension ArticleRenderer { return dateFormatter.string(from: date) } - #if os(macOS) - func renderHTML(withBody body: String) -> String { var s = "\n\n" @@ -326,18 +324,32 @@ private extension ArticleRenderer { s += """ - - """ - - s += "\n\n\n\n" - s += body - s += "\n\n" - - return s - - } - - #endif - } // MARK: - Article extension diff --git a/iOS/Resources/styleSheet.css b/iOS/Resources/styleSheet.css index f46b94deb..90c9b7647 100644 --- a/iOS/Resources/styleSheet.css +++ b/iOS/Resources/styleSheet.css @@ -136,6 +136,20 @@ figcaption { line-height: 1.3em; } +.iframeWrap { + position: relative; + display: block; + padding-top: 56.25%; +} + +.iframeWrap iframe { + position: absolute; + top: 0; + left: 0; + height: 100% !important; + width: 100% !important; +} + /*Block ads and junk*/ iframe[src*="feedads"], From 0c0015084991fbc2ed706d10ae04f37769386203 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 21 Sep 2019 04:34:11 -0500 Subject: [PATCH 2/7] Externalize javascript code into separate main.js file --- .../Detail/DetailWebViewController.swift | 13 +++--- Mac/MainWindow/Detail/page.html | 28 +----------- NetNewsWire.xcodeproj/project.pbxproj | 6 +++ Shared/Article Rendering/main.js | 43 +++++++++++++++++++ iOS/Detail/DetailViewController.swift | 12 +++--- iOS/Resources/page.html | 23 +--------- 6 files changed, 62 insertions(+), 63 deletions(-) create mode 100644 Shared/Article Rendering/main.js diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 43d9ed835..b81ca8712 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -104,13 +104,12 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil) #endif - webView.loadHTMLString(template(), baseURL: nil) - } - - func template() -> String { - let path = Bundle.main.path(forResource: "page", ofType: "html")! - let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) - return s as String + let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! + let page = try! String(contentsOf: pageURL) + let baseURL = pageURL.deletingLastPathComponent() + + webView.loadHTMLString(page, baseURL: baseURL) + } // MARK: Scrolling diff --git a/Mac/MainWindow/Detail/page.html b/Mac/MainWindow/Detail/page.html index 8178a7548..97e7c4541 100644 --- a/Mac/MainWindow/Detail/page.html +++ b/Mac/MainWindow/Detail/page.html @@ -2,33 +2,7 @@ - + diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index ffcdd8a1a..6070aa612 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -76,6 +76,8 @@ 515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; }; 51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; }; 5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; }; + 517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; + 517630052336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */; }; @@ -810,6 +812,7 @@ 515D4FCD2325909200EE1167 /* NetNewsWire_iOS_ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_ShareExtension.entitlements; sourceTree = ""; }; 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSshareextension_target.xcconfig; sourceTree = ""; }; 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = ""; }; + 517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProgressView.swift; sourceTree = ""; }; @@ -1389,6 +1392,7 @@ children = ( 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */, 848362FE2262A30E00DA1D35 /* template.html */, + 517630032336215100E15FFF /* main.js */, ); path = "Article Rendering"; sourceTree = ""; @@ -2498,6 +2502,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 517630052336215100E15FFF /* main.js in Resources */, 511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */, 51C452862265093600C03939 /* Add.storyboard in Resources */, 511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */, @@ -2532,6 +2537,7 @@ 51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */, 84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */, 84C9FC7D22629E1200D921D6 /* AccountsDetail.xib in Resources */, + 517630042336215100E15FFF /* main.js in Resources */, 5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */, 84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */, 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */, diff --git a/Shared/Article Rendering/main.js b/Shared/Article Rendering/main.js new file mode 100644 index 000000000..e33d725da --- /dev/null +++ b/Shared/Article Rendering/main.js @@ -0,0 +1,43 @@ +function mouseDidEnterLink(anchor) { + window.webkit.messageHandlers.mouseDidEnter.postMessage(anchor.href); +} + +function mouseDidExitLink(anchor) { + window.webkit.messageHandlers.mouseDidExit.postMessage(anchor.href); +} + +function wrapFrames() { + document.querySelectorAll("iframe").forEach(element => { + var wrapper = document.createElement("div"); + wrapper.classList.add("iframeWrap"); + element.parentNode.insertBefore(wrapper, element); + wrapper.appendChild(element); + }); +} + +function stripStyles() { + document.getElementsByTagName("body")[0].querySelectorAll("style, link[rel=stylesheet]").forEach(element => element.remove()); + document.getElementsByTagName("body")[0].querySelectorAll("[style]").forEach(element => element.removeAttribute("style")); +} + +function linkHover() { + var anchors = document.getElementsByTagName("a"); + for (var i = 0; i < anchors.length; i++) { + anchors[i].addEventListener("mouseenter", function() { mouseDidEnterLink(this) }); + anchors[i].addEventListener("mouseleave", function() { mouseDidExitLink(this) }); + } +} + +function error() { + document.body.innerHTML = "error"; +} + +function render(data) { + document.getElementsByTagName("style")[0].innerHTML = data.style; + document.body.innerHTML = data.body; + window.scrollTo(0, 0); + + wrapFrames() + stripStyles() + linkHover() +} diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 521a17938..c6a8c1cc7 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -271,12 +271,6 @@ class DetailViewControllerWebViewProvider { static var shared = DetailViewControllerWebViewProvider() - static let template: String = { - let path = Bundle.main.path(forResource: "page", ofType: "html")! - let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) - return s as String - }() - func dequeueWebView() -> WKWebView { if let webView = queue.popLast() { replenishQueueIfNeeded() @@ -296,7 +290,11 @@ class DetailViewControllerWebViewProvider { webView.uiDelegate = nil webView.navigationDelegate = nil - webView.loadHTMLString(DetailViewControllerWebViewProvider.template, baseURL: nil) + let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! + let page = try! String(contentsOf: pageURL) + let baseURL = pageURL.deletingLastPathComponent() + + webView.loadHTMLString(page, baseURL: baseURL) queue.insert(webView, at: 0) } diff --git a/iOS/Resources/page.html b/iOS/Resources/page.html index 7665aa643..4baf2cfee 100644 --- a/iOS/Resources/page.html +++ b/iOS/Resources/page.html @@ -6,29 +6,8 @@ color-scheme: light dark; } - + - From 99be955edec0a60feeac263c20ab29c9677e3164 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 21 Sep 2019 10:37:21 -0500 Subject: [PATCH 3/7] Make sure first webview is fully loaded before callling javascript on it --- NetNewsWire.xcodeproj/project.pbxproj | 4 + iOS/Detail/DetailViewController.swift | 83 +++-------------- .../DetailViewControllerWebViewProvider.swift | 88 +++++++++++++++++++ submodules/RSCore | 2 +- 4 files changed, 104 insertions(+), 73 deletions(-) create mode 100644 iOS/Detail/DetailViewControllerWebViewProvider.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 6070aa612..6cfd8fde3 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ 5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; }; 517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630052336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; + 517630232336657E00E15FFF /* DetailViewControllerWebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */; }; @@ -813,6 +814,7 @@ 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSshareextension_target.xcconfig; sourceTree = ""; }; 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = ""; }; 517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = ""; }; + 517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewControllerWebViewProvider.swift; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProgressView.swift; sourceTree = ""; }; @@ -1370,6 +1372,7 @@ isa = PBXGroup; children = ( 51C4527E2265092C00C03939 /* DetailViewController.swift */, + 517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */, 515ADE3F22E11FAE006B2460 /* SystemMessageViewController.swift */, ); path = Detail; @@ -2702,6 +2705,7 @@ 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */, 51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */, 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, + 517630232336657E00E15FFF /* DetailViewControllerWebViewProvider.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */, 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */, diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index c6a8c1cc7..23d1bcd0f 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -40,27 +40,21 @@ class DetailViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - webView = DetailViewControllerWebViewProvider.shared.dequeueWebView() - webView.translatesAutoresizingMaskIntoConstraints = false - webView.navigationDelegate = self - - webViewContainer.addSubview(webView) - - let constraints: [NSLayoutConstraint] = [ - webView.leadingAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.leadingAnchor), - webView.trailingAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.trailingAnchor), - webView.topAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.topAnchor), - webView.bottomAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.bottomAnchor), - ] - - NSLayoutConstraint.activate(constraints) - - updateArticleSelection() - NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) + + DetailViewControllerWebViewProvider.shared.dequeueWebView() { webView in + + self.webView = webView + webView.navigationDelegate = self + + self.webViewContainer.addChildAndPin(webView) + self.updateArticleSelection() + + } + } override func viewDidAppear(_ animated: Bool) { @@ -261,58 +255,3 @@ private struct TemplateData: Codable { let style: String let body: String } - - -// MARK: - - -/// WKWebView has an awful behavior of a flash to white on first load when in dark mode. -/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle. -class DetailViewControllerWebViewProvider { - - static var shared = DetailViewControllerWebViewProvider() - - func dequeueWebView() -> WKWebView { - if let webView = queue.popLast() { - replenishQueueIfNeeded() - return webView - } - - assertionFailure("Creating WKWebView in \(#function); queue has run dry.") - let webView = WKWebView(frame: .zero) - return webView - } - - func enqueueWebView(_ webView: WKWebView) { - guard queue.count < maximumQueueDepth else { - return - } - - webView.uiDelegate = nil - webView.navigationDelegate = nil - - let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! - let page = try! String(contentsOf: pageURL) - let baseURL = pageURL.deletingLastPathComponent() - - webView.loadHTMLString(page, baseURL: baseURL) - - queue.insert(webView, at: 0) - } - - // MARK: Private - - private let minimumQueueDepth = 3 - private let maximumQueueDepth = 6 - private var queue: [WKWebView] = [] - - private init() { - replenishQueueIfNeeded() - } - - private func replenishQueueIfNeeded() { - while queue.count < minimumQueueDepth { - let webView = WKWebView(frame: .zero) - enqueueWebView(webView) - } - } -} diff --git a/iOS/Detail/DetailViewControllerWebViewProvider.swift b/iOS/Detail/DetailViewControllerWebViewProvider.swift new file mode 100644 index 000000000..1b3796bc1 --- /dev/null +++ b/iOS/Detail/DetailViewControllerWebViewProvider.swift @@ -0,0 +1,88 @@ +// +// DetailViewControllerWebViewProvider.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/21/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import WebKit + +/// WKWebView has an awful behavior of a flash to white on first load when in dark mode. +/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle. +class DetailViewControllerWebViewProvider: NSObject, WKNavigationDelegate { + + static let shared = DetailViewControllerWebViewProvider() + + private let minimumQueueDepth = 3 + private let maximumQueueDepth = 6 + private var queue: [WKWebView] = [] + + private var waitingForFirstLoad = true + private var waitingCompletionHandler: ((WKWebView) -> ())? + + func dequeueWebView(completion: @escaping (WKWebView) -> ()) { + if waitingForFirstLoad { + waitingCompletionHandler = completion + } else { + completeRequest(completion: completion) + } + } + + func enqueueWebView(_ webView: WKWebView) { + guard queue.count < maximumQueueDepth else { + return + } + + webView.navigationDelegate = self + queue.insert(webView, at: 0) + + let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! + let page = try! String(contentsOf: pageURL) + let baseURL = pageURL.deletingLastPathComponent() + + webView.loadHTMLString(page, baseURL: baseURL) + + } + + // MARK: WKNavigationDelegate + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + if waitingForFirstLoad { + waitingForFirstLoad = false + if let completion = waitingCompletionHandler { + completeRequest(completion: completion) + waitingCompletionHandler = nil + } + } + } + + // MARK: Private + + private override init() { + super.init() + replenishQueueIfNeeded() + } + + private func replenishQueueIfNeeded() { + while queue.count < minimumQueueDepth { + let webView = WKWebView(frame: .zero) + enqueueWebView(webView) + } + } + + private func completeRequest(completion: @escaping (WKWebView) -> ()) { + if let webView = queue.popLast() { + webView.navigationDelegate = nil + replenishQueueIfNeeded() + completion(webView) + return + } + + assertionFailure("Creating WKWebView in \(#function); queue has run dry.") + let webView = WKWebView(frame: .zero) + completion(webView) + } + +} diff --git a/submodules/RSCore b/submodules/RSCore index 4dbd31b09..fd55bace3 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 4dbd31b090ab15c3966e9810a65edbf4abdbdd33 +Subproject commit fd55bace3c2ef2c9e5af8110c0fd742e2135cad1 From b49aeca577f2121afeae1f8d4f4be274e6d130a0 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 21 Sep 2019 12:36:35 -0500 Subject: [PATCH 4/7] Moved page.html location information to ArticleRenderer --- .../Detail/DetailWebViewController.swift | 8 ++------ .../Article Rendering/ArticleRenderer.swift | 20 +++++++++++++------ .../DetailViewControllerWebViewProvider.swift | 6 +----- submodules/RSCore | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index b81ca8712..263f6d4df 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -104,11 +104,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil) #endif - let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! - let page = try! String(contentsOf: pageURL) - let baseURL = pageURL.deletingLastPathComponent() - - webView.loadHTMLString(page, baseURL: baseURL) + webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL) } @@ -181,7 +177,7 @@ private extension DetailWebViewController { func reloadHTML() { let style = ArticleStylesManager.shared.currentStyle - let rendering: ArticleRendering + let rendering: ArticleRenderer.Rendering switch state { case .noSelection: diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 5903b69f1..88af1aa11 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -11,10 +11,18 @@ import RSCore import Articles import Account -typealias ArticleRendering = (style: String, html: String) - struct ArticleRenderer { + typealias Rendering = (style: String, html: String) + typealias Page = (html: String, baseURL: URL) + + static var page: Page = { + let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! + let html = try! String(contentsOf: pageURL) + let baseURL = pageURL.deletingLastPathComponent() + return Page(html: html, baseURL: baseURL) + }() + private let article: Article? private let extractedArticle: ExtractedArticle? private let articleStyle: ArticleStyle @@ -38,22 +46,22 @@ struct ArticleRenderer { // MARK: - API - static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle) -> ArticleRendering { + static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle) -> Rendering { let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style) return (renderer.styleString(), renderer.articleHTML) } - static func multipleSelectionHTML(style: ArticleStyle) -> ArticleRendering { + static func multipleSelectionHTML(style: ArticleStyle) -> Rendering { let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) return (renderer.styleString(), renderer.multipleSelectionHTML) } - static func noSelectionHTML(style: ArticleStyle) -> ArticleRendering { + static func noSelectionHTML(style: ArticleStyle) -> Rendering { let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) return (renderer.styleString(), renderer.noSelectionHTML) } - static func noContentHTML(style: ArticleStyle) -> ArticleRendering { + static func noContentHTML(style: ArticleStyle) -> Rendering { let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) return (renderer.styleString(), renderer.noContentHTML) } diff --git a/iOS/Detail/DetailViewControllerWebViewProvider.swift b/iOS/Detail/DetailViewControllerWebViewProvider.swift index 1b3796bc1..35bf79b04 100644 --- a/iOS/Detail/DetailViewControllerWebViewProvider.swift +++ b/iOS/Detail/DetailViewControllerWebViewProvider.swift @@ -38,11 +38,7 @@ class DetailViewControllerWebViewProvider: NSObject, WKNavigationDelegate { webView.navigationDelegate = self queue.insert(webView, at: 0) - let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! - let page = try! String(contentsOf: pageURL) - let baseURL = pageURL.deletingLastPathComponent() - - webView.loadHTMLString(page, baseURL: baseURL) + webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL) } diff --git a/submodules/RSCore b/submodules/RSCore index fd55bace3..960eaf603 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit fd55bace3c2ef2c9e5af8110c0fd742e2135cad1 +Subproject commit 960eaf60336f592306fb1bf6f5a62800a9c5050f From 4e7ef7271ae70636da2fc52d93d4b53aa9e8b248 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 21 Sep 2019 12:43:15 -0500 Subject: [PATCH 5/7] Fix content offset bug in detail view --- iOS/Detail/DetailViewController.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 23d1bcd0f..9878be601 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -48,11 +48,13 @@ class DetailViewController: UIViewController { DetailViewControllerWebViewProvider.shared.dequeueWebView() { webView in self.webView = webView + self.webViewContainer.addChildAndPin(webView) webView.navigationDelegate = self - self.webViewContainer.addChildAndPin(webView) - self.updateArticleSelection() - + // Even though page.html should be loaded into this webview, we have to do it again + // to work around this bug: http://www.openradar.me/22855188 + webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL) + } } @@ -237,6 +239,11 @@ extension DetailViewController: WKNavigationDelegate { } } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + self.updateArticleSelection() + } + } // MARK: Private From 9d54a1de748473a24ee596aee886785824fdb2e8 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 21 Sep 2019 15:03:42 -0500 Subject: [PATCH 6/7] Enable always on Reader View for feeds --- .../Detail/DetailViewController.swift | 1 + .../Detail/DetailWebViewController.swift | 2 ++ Mac/MainWindow/MainWindowController.swift | 30 ++++++++++++------- .../Article Rendering/ArticleRenderer.swift | 10 +++++++ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Mac/MainWindow/Detail/DetailViewController.swift b/Mac/MainWindow/Detail/DetailViewController.swift index 592322b0d..0e3def9e9 100644 --- a/Mac/MainWindow/Detail/DetailViewController.swift +++ b/Mac/MainWindow/Detail/DetailViewController.swift @@ -15,6 +15,7 @@ import RSWeb enum DetailState: Equatable { case noSelection case multipleSelection + case loading case article(Article) case extracted(Article, ExtractedArticle) } diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 263f6d4df..cdcd744fd 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -184,6 +184,8 @@ private extension DetailWebViewController { rendering = ArticleRenderer.noSelectionHTML(style: style) case .multipleSelection: rendering = ArticleRenderer.multipleSelectionHTML(style: style) + case .loading: + rendering = ArticleRenderer.loadingHTML(style: style) case .article(let article): rendering = ArticleRenderer.articleHTML(article: article, style: style) case .extracted(let article, let extractedArticle): diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 21cf866ff..f863b0160 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -329,11 +329,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { detailViewController?.setState(detailState, mode: timelineSourceMode) } } else { - if let extractor = ArticleExtractor(currentLink) { - extractor.delegate = self - extractor.process() - articleExtractor = extractor - } + startArticleExtractorForCurrentLink() } } @@ -453,18 +449,24 @@ extension MainWindowController: SidebarDelegate { extension MainWindowController: TimelineContainerViewControllerDelegate { func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode) { - articleExtractor?.cancel() articleExtractor = nil isShowingExtractedArticle = false - makeToolbarValidate() let detailState: DetailState if let articles = articles { - detailState = articles.count == 1 ? .article(articles.first!) : .multipleSelection - } - else { + if articles.count == 1 { + if articles.first?.feed?.isArticleExtractorAlwaysOn ?? false { + detailState = .loading + startArticleExtractorForCurrentLink() + } else { + detailState = .article(articles.first!) + } + } else { + detailState = .multipleSelection + } + } else { detailState = .noSelection } @@ -833,6 +835,14 @@ private extension MainWindowController { } } + + func startArticleExtractorForCurrentLink() { + if let link = currentLink, let extractor = ArticleExtractor(link) { + extractor.delegate = self + extractor.process() + articleExtractor = extractor + } + } func saveSplitViewState() { // TODO: Update this for multiple windows. diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 88af1aa11..37f90a440 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -56,6 +56,11 @@ struct ArticleRenderer { return (renderer.styleString(), renderer.multipleSelectionHTML) } + static func loadingHTML(style: ArticleStyle) -> Rendering { + let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) + return (renderer.styleString(), renderer.loadingHTML) + } + static func noSelectionHTML(style: ArticleStyle) -> Rendering { let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) return (renderer.styleString(), renderer.noSelectionHTML) @@ -81,6 +86,11 @@ private extension ArticleRenderer { return renderHTML(withBody: body) } + private var loadingHTML: String { + let body = "

Loading...

" + return renderHTML(withBody: body) + } + private var noSelectionHTML: String { let body = "

No selection

" return renderHTML(withBody: body) From 548eaac5fa67f266c228f4f72e88319c62b4cf58 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 21 Sep 2019 15:16:55 -0500 Subject: [PATCH 7/7] Maid Reader View button more legible --- .../ArticleExtractor.pdf | Bin 7888 -> 3927 bytes .../ArticleExtractorError.pdf | Bin 4005 -> 4022 bytes .../ArticleExtractorProgress1.pdf | Bin 7929 -> 3981 bytes .../ArticleExtractorProgress2.pdf | Bin 7891 -> 3949 bytes .../ArticleExtractorProgress3.pdf | Bin 7893 -> 3944 bytes .../ArticleExtractorProgress4.pdf | Bin 7894 -> 3950 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Mac/Resources/Assets.xcassets/articleExtractor.imageset/ArticleExtractor.pdf b/Mac/Resources/Assets.xcassets/articleExtractor.imageset/ArticleExtractor.pdf index 3dd2e82cc03d3e66b535bc8a4c64c3f2d27d4dd8..4806aaecb7ecd4806fbdc60fb8619b37ef52bd82 100644 GIT binary patch delta 693 zcmca$dtGiqK>gfFj(kl9A}-%;kH#>()n|HpOL)g82A9R1GZeo2&(Rc6J;o9{!LR;h z{oKrInG1&xdqwcx4yl+^a74B)_;J_UI)g3D>8Zl2Vn3}~{^GRAic2BVQ6f_}AGs^r zq_}!^$dO*}${i1EHh#G;HSPDQOOe(Ro~Dd4#N%a?`Pl?D z4He843>1PC?CgMuD>W}AKPijL0w@HcZEO_ueNyw%OEMHJH}B_f<MVec zMmHB|GrE|uF_N1~iV`z(Qj5T$5}a9Im8xK9sGuK|pI@S22-FG;M19Y^w0s3aDBDFL zTEWKDz&I(*+$1g8FgeB2EGfw%G1WZDz{1cV#UwQ`HPy_Hi?9-fSWw&)mn0UIR1~GA SaT%MM8gQ|xs=E5SaRC4bGTqw% delta 4233 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 Yc- z1ryu;y8JzSz=7Fw)j!MCzucexyV+Wsb84MW*SxzkL>woslX<8RUL44}q{i&~l0vyH zeUBp?=5;V}t?T%(l($p&XVIg-(-}5h^=0U_O!#qU=b63TFS4rJT(?|vJEq@s!&D@3 z_E%3`uURt+CKg^0S)47pIfu!HPg|+$@{V4MHSW)Oj|Ls;N=)7pcIs&7E?>^z)(WmU z7sK18ADfVL*Y;SfrSDP8mHRx6yPa>{`o??0;gQAU8b)a*L(9!G8AExrjSP(p3=K_; zqYMnxfk;z9-#0(SC9xz`LBqugC}nA22~#pzlFw4h!cf5g1QhZVxWEhp0~2EtbTMON zQ-jGhe5&;(hNkFp2BwBs)MHa;VT7*N*vJyYJYy4MBsY~5C1&QN7IE3wDCh@gPFCPo zi#ISgNlrCNG)zi0N;5Z2G)*%~vP?5hHZnCgGBHd^F|y;bA*e(lR>97WtGFbwsHCDO SHI2*I%*2#SRn^tsjSB#qU+;hb delta 589 zcmdlczf^ugaDCn(2OgLI-$jZW1J+Nzo3g*UsqQO7$EvALswe&~?Yq9vWo<&}FJXSR zpASDiX%U~J(6P(gBVZ1H#ZmQ?=^q=HKUYoK{~_2uOR6hkzt1J{rCQnfHy3*=iuqY6 z6-`@t^Gby4^stY8+eO0-Od=nuaT`t!S#w5*d45X7PJyJ9|GWC?dw-p_y2&Xedob(p zX4}JTAMGzQX}+6Tc*j`lyUza&J={yJ#UC~Gato%Z+Z{?+mKGf38kZfb8zJ*<3g6J^SjXOj4EhtaEMT4>3!I zuaIbWK4QSU>x4~)?){U}g4&#!Yn{03_PRH|pO{p1WQ&kaxk}Dv-5i@dvA65=cec${Pm&B4(1q~M~ zBLkqcB}~a=Ej~*oGn2^+`GiHy6%0T?Ay0t|%rGzpg2}yns^Z3$7U*&YCYI)tKk^CN zV#=7BnWC#UHZsRhXKZ39NhdhA&Q)kzC*~en-;v<;u;u#O6|0oH z|H~&_mMLD96scRc>f+ChdBxJFpG;MGe6Hs%kLT{y=VqN0So`6%P5Jpdy!}tKe&Q}Jxe+}oK!z2XvAlH2x9WZJ&?<13B)s+YHp{(YGHAtvgo z&)1bjzt!6K@**>=kg3rM`kv0tPKm{-DGC;oXG+CS_Gadt%qlHCSx>s4-pJ6% zz|hddILg349f&j)^nLSFToOxC6*OF|fKrwQmKaJ9E~+R>P2)09Ff`;cfCB|HQ&VG8 zg){}In6Zh8fdW`oArB^IU~Fj&7UMTS2pSt3SWecFQMEBPH^IE3>~F F7XTow4|V_m delta 4336 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 QXz(8hC_LU)-gz17KfDM9i~s-t diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress2.imageset/ArticleExtractorProgress2.pdf b/Mac/Resources/Assets.xcassets/articleExtractorProgress2.imageset/ArticleExtractorProgress2.pdf index f4167ffd28ba3a2cd632f8afea7c9a6d1c3ccffb..939dcc5cc83f08f5710d4b9d04accc506845f44e 100644 GIT binary patch delta 717 zcmca?`&Mp3K>gH7u~~-=1X}KwcI|Yy6nk^h(&uv)_}e;UWGZSd*=JvpChP1}=Oy3c zrk!nn)IBbH|V4_x=f{_J_odw)ajQSrF4&B^w~W^XdWOd${rJn72!zgOL7Spo&!*UN@LWL~)-bk)+ zU9+Jx^`VsgEN}iZYaM>>VLx+AZ_$~~8Me9#Yh#ps@6EflyK|D=`b90P-#Sh1(y0Bo zJh4>0K&JP*yJqpr|K{m;ZcSa*^eRU_>F}}6&aBX1OxcW2n-jk}E~t+d*~q$X?cbjP zdY=2)-FxG?Qu9)ZONvqxb0=;UU^X%_n|Qoz@?R!FO+y891p|d31v@(+0;$SR%HpyB z3V~=F8wGuz)V%bP3B$_@1sq0(Mh1q4CdQK+q@`70oG1eWbs*A2vazBlHI2(a!O(E>erahP zV-o|Q6F@*APk{@}FfcGSGej3NHZnJ#%qF8+Z){?OE@xn3f<-+Rb*2WE=z5Kfj4;eI zHa10aQ%O-`W=?7mID~>T`KnTZPFK(m%Fi!RFf^DPBPS7W0dzo8QmRFwv0#02!N_8*!`njx delta 4303 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 QVW=QN!QgO)=36m;0pvB-O8@`> diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress4.imageset/ArticleExtractorProgress4.pdf b/Mac/Resources/Assets.xcassets/articleExtractorProgress4.imageset/ArticleExtractorProgress4.pdf index 670a4baab7dbab668f0e26bf3a07c3d12d91f4b7..2e27e98c6369435e7e2857482c25441ae10bd61c 100644 GIT binary patch delta 732 zcmca+`%Z2`K>gfFfq91v1YG{tcI{-?@cKyK-?N-QUNHu&4r5Z8_1k;y#!e0=j}`J0 z)n8Z4Pd5vfDQG>&UeT5>Txb8WkvnJh$L70rWisjec!PbCO|NEeT5#Q(38IUQb@qmzq4;_C{9>(YieBK+Q6UtH~dn%kiH^qXH!{9=AKOq zKNhbEtk+@ajps_uODQfXN==-&MUdIZ#C+oMvdJ5`1T_s6%oPk2f)woRfC!{0KPijL z0w@HcZEO_ueNyw%OEMHJH^1d-4JJALn8x2LlfgD0|Rv+(p1p*%};SjEJ;<+aIpePSsGYkC_y-(q9`?u%Rs@*7N$lP$tFf=21a&VHUyO@#De0exFoTt1ROlZrsl?6s;aL3Zd?Ex`SRlc delta 4331 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 OO2*)DmJ;?mFn<81f%pgj