Convert iOS to use Javascript rendering

This commit is contained in:
Maurice Parker 2019-09-20 20:33:28 -05:00
parent 710abf30c7
commit 3decd23c45
7 changed files with 114 additions and 40 deletions

View File

@ -100,6 +100,8 @@
51AF460C23247F11001742EF /* SettingsFeedbinAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */; };
51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AF460D232488C6001742EF /* Account-Extensions.swift */; };
51B62E68233186730085F949 /* MasterTimelineAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* MasterTimelineAvatarView.swift */; };
51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; };
51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; };
51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; };
51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
51C451B9226377C900C03939 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; };
@ -373,7 +375,6 @@
9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; };
9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; };
B528F81E23333C7E00E735DD /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; };
B528F81F23333C7E00E735DD /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; };
D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; };
D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; };
D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */; };
@ -826,6 +827,8 @@
519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = "<group>"; };
51B62E67233186730085F949 /* MasterTimelineAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineAvatarView.swift; sourceTree = "<group>"; };
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = "<group>"; };
51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = "<group>"; };
51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = "<group>"; };
51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = "<group>"; };
51C45254226507D200C03939 /* AppAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = "<group>"; };
@ -1385,7 +1388,6 @@
isa = PBXGroup;
children = (
849A977D1ED9EC42007D329B /* ArticleRenderer.swift */,
B528F81D23333C7E00E735DD /* page.html */,
848362FE2262A30E00DA1D35 /* template.html */,
);
path = "Article Rendering";
@ -1674,6 +1676,7 @@
84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */,
84E8E0EA202F693600562D8F /* DetailWebView.swift */,
84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */,
B528F81D23333C7E00E735DD /* page.html */,
848362FC2262A30800DA1D35 /* styleSheet.css */,
5127B235222B4849006D641D /* Keyboard */,
);
@ -1943,6 +1946,7 @@
51C45254226507D200C03939 /* AppAssets.swift */,
51C45255226507D200C03939 /* AppDefaults.swift */,
51E3EB3C229AB08300645299 /* ErrorHandler.swift */,
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */,
51C4525D226508F600C03939 /* MasterFeed */,
51C4526D2265091600C03939 /* MasterTimeline */,
51C4527D2265092C00C03939 /* Detail */,
@ -1967,6 +1971,7 @@
51F85BEE2272520B00C787DC /* Thanks.rtf */,
51F85BF22272531500C787DC /* Dedication.rtf */,
51C452B72265178500C03939 /* styleSheet.css */,
51BB7C302335ACDE008E8144 /* page.html */,
84C9FC9B2262A1A900D921D6 /* Assets.xcassets */,
84C9FC9C2262A1A900D921D6 /* Info.plist */,
84BB0F812333426400DED65E /* NetNewsWire.entitlements */,
@ -2230,16 +2235,16 @@
TargetAttributes = {
513C5CE5232571C2003D4054 = {
CreatedOnToolsVersion = 11.0;
DevelopmentTeam = DY2XQRVWN9;
DevelopmentTeam = SHJK2V3AJG;
ProvisioningStyle = Automatic;
};
6581C73220CED60000F4AD34 = {
DevelopmentTeam = SHJK2V3AJG;
ProvisioningStyle = Manual;
ProvisioningStyle = Automatic;
};
840D617B2029031C009BC708 = {
CreatedOnToolsVersion = 9.3;
DevelopmentTeam = DY2XQRVWN9;
DevelopmentTeam = SHJK2V3AJG;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes = {
@ -2250,7 +2255,7 @@
849C645F1ED37A5D003D8FC0 = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = SHJK2V3AJG;
ProvisioningStyle = Manual;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.HardenedRuntime = {
enabled = 1;
@ -2259,7 +2264,7 @@
};
849C64701ED37A5D003D8FC0 = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = 9C84TZ7Q6Z;
DevelopmentTeam = SHJK2V3AJG;
ProvisioningStyle = Automatic;
TestTargetID = 849C645F1ED37A5D003D8FC0;
};
@ -2498,7 +2503,7 @@
511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */,
511D43D2231FA62C00FB1562 /* GlobalKeyboardShortcuts.plist in Resources */,
84C9FCA12262A1B300D921D6 /* Main.storyboard in Resources */,
B528F81F23333C7E00E735DD /* page.html in Resources */,
51BB7C312335ACDE008E8144 /* page.html in Resources */,
51F85BF32272531500C787DC /* Dedication.rtf in Resources */,
84C9FCA42262A1B800D921D6 /* LaunchScreenPhone.storyboard in Resources */,
51F85BEB22724CB600C787DC /* About.rtf in Resources */,
@ -2718,6 +2723,7 @@
51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */,
51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */,
519D740823243FEA008BB345 /* SettingsSubscriptionsImportDocumentPickerView.swift in Sources */,
51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */,
51AF460C23247F11001742EF /* SettingsFeedbinAccountView.swift in Sources */,
51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */,
519D740723243FE7008BB345 /* SettingsSubscriptionsExportDocumentPickerView.swift in Sources */,

View File

@ -53,6 +53,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
super.init()
appDelegate = self
// Force lazy initialization of the web view provider so that it can warm up the queue of prepared web views
let _ = DetailViewControllerWebViewProvider.shared
AccountManager.shared = AccountManager(accountsFolder: RSDataSubfolder(nil, "Accounts")!)
registerBackgroundTasks()

View File

@ -0,0 +1,33 @@
//
// ArticleActivityItemSource.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 9/20/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
class ArticleActivityItemSource: NSObject, UIActivityItemSource {
private let url: URL
private let subject: String?
init(url: URL, subject: String?) {
self.url = url
self.subject = subject
}
func activityViewControllerPlaceholderItem(_ : UIActivityViewController) -> Any {
return url
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return url
}
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
return subject ?? ""
}
}

View File

@ -99,14 +99,23 @@ class DetailViewController: UIViewController {
}
func reloadHTML() {
guard let article = coordinator.currentArticle, let webView = webView else {
return
}
let style = ArticleStylesManager.shared.currentStyle
let (styleSheet, html) = ArticleRenderer.articleHTML(article: article, style: style)
webView.loadHTMLString(html, baseURL: nil)
let style = ArticleStylesManager.shared.currentStyle
let rendering = ArticleRenderer.articleHTML(article: article, style: style)
let templateData = TemplateData(style: rendering.style, body: rendering.html)
let encoder = JSONEncoder()
var render = "error();"
if let data = try? encoder.encode(templateData) {
let json = String(data: data, encoding: .utf8)!
render = "render(\(json));"
}
webView.evaluateJavaScript(render)
}
// MARK: Notifications
@ -205,31 +214,8 @@ class DetailViewController: UIViewController {
}
}
//print("\(candidateY) : \(webView.scrollView.contentSize.height)")
class ArticleActivityItemSource: NSObject, UIActivityItemSource {
private let url: URL
private let subject: String?
init(url: URL, subject: String?) {
self.url = url
self.subject = subject
}
func activityViewControllerPlaceholderItem(_ : UIActivityViewController) -> Any {
return url
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return url
}
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
return subject ?? ""
}
}
// MARK: WKNavigationDelegate
extension DetailViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
@ -259,6 +245,8 @@ extension DetailViewController: WKNavigationDelegate {
}
}
// MARK: Private
private extension DetailViewController {
func updateProgressIndicatorIfNeeded() {
@ -269,13 +257,26 @@ private extension DetailViewController {
}
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()
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()
@ -295,8 +296,7 @@ class DetailViewControllerWebViewProvider {
webView.uiDelegate = nil
webView.navigationDelegate = nil
let html = ArticleRenderer.noContentHTML(style: .defaultStyle)
webView.loadHTMLString(html, baseURL: nil)
webView.loadHTMLString(DetailViewControllerWebViewProvider.template, baseURL: nil)
queue.insert(webView, at: 0)
}

34
iOS/Resources/page.html Normal file
View File

@ -0,0 +1,34 @@
<html>
<head>
<meta name="viewport" content="width=device-width">
<style>
:root {
color-scheme: light dark;
}
</style>
<script>
function mouseDidEnterLink(anchor) {
window.webkit.messageHandlers.mouseDidEnter.postMessage(anchor.href);
}
function mouseDidExitLink(anchor) {
window.webkit.messageHandlers.mouseDidExit.postMessage(anchor.href);
}
function render(data) {
document.getElementsByTagName("style")[0].innerHTML = data.style;
document.body.innerHTML = data.body;
window.scrollTo(0, 0);
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 error() {
document.body.innerHTML = "error";
}
</script>
</head>
<body>
</body>
</html>

View File

@ -269,8 +269,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
// Force lazy initialization of the web view provider so that it can warm up the queue of prepared web views
let _ = DetailViewControllerWebViewProvider.shared
}
func start(for size: CGSize) -> UIViewController {