diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift
index c99c39a99..362c3de81 100644
--- a/Mac/MainWindow/Detail/DetailWebViewController.swift
+++ b/Mac/MainWindow/Detail/DetailWebViewController.swift
@@ -8,6 +8,7 @@
import AppKit
import WebKit
+import RSCore
import RSWeb
import Articles
@@ -118,8 +119,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
- webView.loadFileURL(ArticleRenderer.page.url, allowingReadAccessTo: ArticleRenderer.page.baseURL)
-
+ webView.loadFileURL(ArticleRenderer.blank.url, allowingReadAccessTo: ArticleRenderer.blank.baseURL)
}
// MARK: Notifications
@@ -202,12 +202,6 @@ extension DetailWebViewController: WKNavigationDelegate {
}
// MARK: - Private
-struct TemplateData: Codable {
- let style: String
- let body: String
- let title: String
- let baseURL: String
-}
private extension DetailWebViewController {
@@ -242,16 +236,15 @@ private extension DetailWebViewController {
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style)
}
- let templateData = TemplateData(style: rendering.style, body: rendering.html, title: rendering.title, baseURL: rendering.baseURL)
+ let substitutions = [
+ "title": rendering.title,
+ "baseURL": rendering.baseURL,
+ "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), 0);"
- }
-
- webView.evaluateJavaScript(render)
+ let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions)
+ webView.loadHTMLString(html, baseURL: ArticleRenderer.page.baseURL)
}
func fetchScrollInfo(_ completion: @escaping (ScrollInfo?) -> Void) {
diff --git a/Mac/MainWindow/Detail/blank.html b/Mac/MainWindow/Detail/blank.html
new file mode 100644
index 000000000..4ff14e9ce
--- /dev/null
+++ b/Mac/MainWindow/Detail/blank.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Mac/MainWindow/Detail/page.html b/Mac/MainWindow/Detail/page.html
index 5ed1f5e46..6d715390f 100644
--- a/Mac/MainWindow/Detail/page.html
+++ b/Mac/MainWindow/Detail/page.html
@@ -1,13 +1,20 @@
-
-
+ [[title]]
+
+
+ [[body]]
diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj
index 05212949e..a3254266e 100644
--- a/NetNewsWire.xcodeproj/project.pbxproj
+++ b/NetNewsWire.xcodeproj/project.pbxproj
@@ -14,6 +14,9 @@
3B826DCE2385C89600FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; };
49F40DF82335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; };
49F40DF92335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; };
+ 5103A9982421643300410853 /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9972421643300410853 /* blank.html */; };
+ 5103A9992421643300410853 /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9972421643300410853 /* blank.html */; };
+ 5103A9B424216A4200410853 /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9B324216A4200410853 /* blank.html */; };
5108F6B62375E612001ABC45 /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; };
5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; };
5108F6D22375EED2001ABC45 /* TimelineCustomizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */; };
@@ -95,6 +98,7 @@
515D4FC123257A3200EE1167 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; };
515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; };
515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; };
+ 516244E3241E19F000B61C47 /* ColorPaletteTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */; };
51627A6723861DA3007B3B4B /* MasterFeedViewController+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6623861DA3007B3B4B /* MasterFeedViewController+Drag.swift */; };
51627A6923861DED007B3B4B /* MasterFeedViewController+Drop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */; };
51627A6B238629D8007B3B4B /* MasterFeedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */; };
@@ -1249,6 +1253,8 @@
3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = ""; };
3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsFeedWranglerWindowController.swift; sourceTree = ""; };
49F40DEF2335B71000552BF4 /* newsfoot.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newsfoot.js; sourceTree = ""; };
+ 5103A9972421643300410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; };
+ 5103A9B324216A4200410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; };
5108F6B52375E612001ABC45 /* CacheCleaner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheCleaner.swift; sourceTree = ""; };
5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineCustomizerViewController.swift; sourceTree = ""; };
5108F6D32375EEEF001ABC45 /* TimelinePreviewTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePreviewTableViewController.swift; sourceTree = ""; };
@@ -1303,6 +1309,7 @@
515D4FCB2325815A00EE1167 /* SafariExt.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = SafariExt.js; sourceTree = ""; };
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 = ""; };
+ 516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteTableViewController.swift; sourceTree = ""; };
51627A6623861DA3007B3B4B /* MasterFeedViewController+Drag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MasterFeedViewController+Drag.swift"; sourceTree = ""; };
51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MasterFeedViewController+Drop.swift"; sourceTree = ""; };
51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterFeedDataSource.swift; sourceTree = ""; };
@@ -1875,6 +1882,7 @@
51A16990235E10D600EB091F /* Settings.storyboard */,
51A16995235E10D600EB091F /* AboutViewController.swift */,
51A16992235E10D600EB091F /* AddAccountViewController.swift */,
+ 516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */,
516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */,
516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */,
516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */,
@@ -2367,6 +2375,7 @@
84E8E0EA202F693600562D8F /* DetailWebView.swift */,
84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */,
5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */,
+ 5103A9972421643300410853 /* blank.html */,
B528F81D23333C7E00E735DD /* page.html */,
5142194A2353C1CF00E07E2C /* main_mac.js */,
848362FC2262A30800DA1D35 /* styleSheet.css */,
@@ -2674,6 +2683,7 @@
51F85BF02272524100C787DC /* Credits.rtf */,
51F85BEE2272520B00C787DC /* Thanks.rtf */,
51F85BF22272531500C787DC /* Dedication.rtf */,
+ 5103A9B324216A4200410853 /* blank.html */,
51BB7C302335ACDE008E8144 /* page.html */,
514219572353C28900E07E2C /* main_ios.js */,
51C452B72265178500C03939 /* styleSheet.css */,
@@ -3472,6 +3482,7 @@
3B826DCD2385C89600FC1ADB /* AccountsFeedWrangler.xib in Resources */,
65ED4069235DEF6C0081F399 /* AccountsReaderAPI.xib in Resources */,
65ED406A235DEF6C0081F399 /* newsfoot.js in Resources */,
+ 5103A9992421643300410853 /* blank.html in Resources */,
65ED406B235DEF6C0081F399 /* CrashReporterWindow.xib in Resources */,
65ED406C235DEF6C0081F399 /* Credits.rtf in Resources */,
65ED406D235DEF6C0081F399 /* Inspector.storyboard in Resources */,
@@ -3504,6 +3515,7 @@
516A093723609A3600EAE89B /* SettingsAccountTableViewCell.xib in Resources */,
51F85BF32272531500C787DC /* Dedication.rtf in Resources */,
516A09422361248000EAE89B /* Inspector.storyboard in Resources */,
+ 5103A9B424216A4200410853 /* blank.html in Resources */,
84C9FCA42262A1B800D921D6 /* LaunchScreenPhone.storyboard in Resources */,
51F85BEB22724CB600C787DC /* About.rtf in Resources */,
516A093B2360A4A000EAE89B /* SettingsTableViewCell.xib in Resources */,
@@ -3558,6 +3570,7 @@
3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */,
55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */,
49F40DF82335B71000552BF4 /* newsfoot.js in Resources */,
+ 5103A9982421643300410853 /* blank.html in Resources */,
84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */,
84C9FC8E22629E8F00D921D6 /* Credits.rtf in Resources */,
84BBB12D20142A4700F054F5 /* Inspector.storyboard in Resources */,
@@ -3957,6 +3970,7 @@
5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */,
51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */,
5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */,
+ 516244E3241E19F000B61C47 /* ColorPaletteTableViewController.swift in Sources */,
51C45258226508CF00C03939 /* AppAssets.swift in Sources */,
51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */,
51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */,
diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift
index a66568560..0be3508d3 100644
--- a/Shared/Article Rendering/ArticleRenderer.swift
+++ b/Shared/Article Rendering/ArticleRenderer.swift
@@ -17,15 +17,23 @@ import Account
struct ArticleRenderer {
typealias Rendering = (style: String, html: String, title: String, baseURL: String)
- typealias Page = (url: URL, baseURL: URL)
+
+ struct Page {
+ let url: URL
+ let baseURL: URL
+ let html: String
+
+ init(name: String) {
+ url = Bundle.main.url(forResource: name, withExtension: "html")!
+ baseURL = url.deletingLastPathComponent()
+ html = try! NSString(contentsOfFile: url.path, encoding: String.Encoding.utf8.rawValue) as String
+ }
+ }
static var imageIconScheme = "nnwImageIcon"
- static var page: Page = {
- let url = Bundle.main.url(forResource: "page", withExtension: "html")!
- let baseURL = url.deletingLastPathComponent()
- return Page(url: url, baseURL: baseURL)
- }()
+ static var blank = Page(name: "blank")
+ static var page = Page(name: "page")
private let article: Article?
private let extractedArticle: ExtractedArticle?
diff --git a/Shared/Article Rendering/main.js b/Shared/Article Rendering/main.js
index 36a5cb5d7..ea627c54b 100644
--- a/Shared/Article Rendering/main.js
+++ b/Shared/Article Rendering/main.js
@@ -117,25 +117,12 @@ function styleLocalFootnotes() {
}
}
-function render(data, scrollY) {
- document.getElementsByTagName("style")[0].innerHTML = data.style;
-
- let title = document.getElementsByTagName("title")[0];
- title.textContent = data.title
-
- let base = document.getElementsByTagName("base")[0];
- base.href = data.baseURL
-
- document.body.innerHTML = data.body;
-
- window.scrollTo(0, scrollY);
-
- wrapFrames()
- wrapTables()
- stripStyles()
- convertImgSrc()
- flattenPreElements()
- styleLocalFootnotes()
-
- postRenderProcessing()
+function processPage() {
+ wrapFrames();
+ wrapTables();
+ stripStyles();
+ convertImgSrc();
+ flattenPreElements();
+ styleLocalFootnotes();
+ postRenderProcessing();
}
diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift
index 0e646a7e5..f790db2a6 100644
--- a/iOS/AppDefaults.swift
+++ b/iOS/AppDefaults.swift
@@ -8,6 +8,24 @@
import UIKit
+enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable {
+ case automatic = 0
+ case light = 1
+ case dark = 2
+
+ var description: String {
+ switch self {
+ case .automatic:
+ return NSLocalizedString("Automatic", comment: "Automatic")
+ case .light:
+ return NSLocalizedString("Light", comment: "Light")
+ case .dark:
+ return NSLocalizedString("Dark", comment: "Dark")
+ }
+ }
+
+}
+
struct AppDefaults {
static var shared: UserDefaults = {
@@ -17,6 +35,7 @@ struct AppDefaults {
}()
struct Key {
+ static let userInterfaceColorPalette = "userInterfaceColorPalette"
static let lastImageCacheFlushDate = "lastImageCacheFlushDate"
static let firstRunDate = "firstRunDate"
static let timelineGroupByFeed = "timelineGroupByFeed"
@@ -40,6 +59,18 @@ struct AppDefaults {
firstRunDate = Date()
return true
}()
+
+ static var userInterfaceColorPalette: UserInterfaceColorPalette {
+ get {
+ if let result = UserInterfaceColorPalette(rawValue: int(for: Key.userInterfaceColorPalette)) {
+ return result
+ }
+ return .automatic
+ }
+ set {
+ setInt(for: Key.userInterfaceColorPalette, newValue.rawValue)
+ }
+ }
static var addWebFeedAccountID: String? {
get {
@@ -160,7 +191,8 @@ struct AppDefaults {
}
static func registerDefaults() {
- let defaults: [String : Any] = [Key.timelineGroupByFeed: false,
+ let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue,
+ Key.timelineGroupByFeed: false,
Key.refreshClearsReadArticles: false,
Key.timelineNumberOfLines: 2,
Key.timelineIconSize: IconSize.medium.rawValue,
diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift
index c6a2b3123..a1a9bb39a 100644
--- a/iOS/AppDelegate.swift
+++ b/iOS/AppDelegate.swift
@@ -135,7 +135,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func manualRefresh(errorHandler: @escaping (Error) -> ()) {
UIApplication.shared.connectedScenes.compactMap( { $0.delegate as? SceneDelegate } ).forEach {
- $0.refreshInterface()
+ $0.cleanUp()
}
AccountManager.shared.refreshAll(errorHandler: errorHandler)
}
diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift
index 1907dbac1..3768d3441 100644
--- a/iOS/Article/ArticleViewController.swift
+++ b/iOS/Article/ArticleViewController.swift
@@ -103,13 +103,17 @@ class ArticleViewController: UIViewController {
view.bottomAnchor.constraint(equalTo: pageViewController.view.bottomAnchor)
])
- let controller = createWebViewController(article, updateView: false)
+ let controller: WebViewController
if let state = restoreState {
+ controller = createWebViewController(article, updateView: false)
controller.extractedArticle = state.extractedArticle
controller.isShowingExtractedArticle = state.isShowingExtractedArticle
controller.articleExtractorButtonState = state.articleExtractorButtonState
controller.windowScrollY = state.windowScrollY
+ } else {
+ controller = createWebViewController(article, updateView: true)
}
+
articleExtractorButton.buttonState = controller.articleExtractorButtonState
self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil)
diff --git a/iOS/Article/PreloadedWebView.swift b/iOS/Article/PreloadedWebView.swift
index 1573b6a3d..47a8aa29c 100644
--- a/iOS/Article/PreloadedWebView.swift
+++ b/iOS/Article/PreloadedWebView.swift
@@ -11,10 +11,6 @@ import WebKit
class PreloadedWebView: WKWebView {
- private struct MessageName {
- static let domContentLoaded = "domContentLoaded"
- }
-
private var isReady: Bool = false
private var readyCompletion: ((PreloadedWebView) -> Void)?
@@ -38,8 +34,8 @@ class PreloadedWebView: WKWebView {
}
func preload() {
- configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.domContentLoaded)
- loadFileURL(ArticleRenderer.page.url, allowingReadAccessTo: ArticleRenderer.page.baseURL)
+ navigationDelegate = self
+ loadFileURL(ArticleRenderer.blank.url, allowingReadAccessTo: ArticleRenderer.blank.baseURL)
}
func ready(completion: @escaping (PreloadedWebView) -> Void) {
@@ -54,18 +50,16 @@ class PreloadedWebView: WKWebView {
// MARK: WKScriptMessageHandler
-extension PreloadedWebView: WKScriptMessageHandler {
+extension PreloadedWebView: WKNavigationDelegate {
- func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
- if message.name == MessageName.domContentLoaded {
- isReady = true
- if let completion = readyCompletion {
- completeRequest(completion: completion)
- readyCompletion = nil
- }
+ func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+ isReady = true
+ if let completion = readyCompletion {
+ completeRequest(completion: completion)
+ readyCompletion = nil
}
}
-
+
}
// MARK: Private
@@ -74,7 +68,7 @@ private extension PreloadedWebView {
func completeRequest(completion: @escaping (PreloadedWebView) -> Void) {
isReady = false
- configuration.userContentController.removeScriptMessageHandler(forName: MessageName.domContentLoaded)
+ navigationDelegate = nil
completion(self)
}
diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift
index 5e04ea578..6b3fc0926 100644
--- a/iOS/Article/WebViewController.swift
+++ b/iOS/Article/WebViewController.swift
@@ -325,7 +325,7 @@ extension WebViewController: WKNavigationDelegate {
}
}
-
+
}
// MARK: WKUIDelegate
@@ -393,13 +393,6 @@ extension WebViewController: UIScrollViewDelegate {
// MARK: JSON
-private struct TemplateData: Codable {
- let style: String
- let body: String
- let title: String
- let baseURL: String
-}
-
private struct ImageClickMessage: Codable {
let x: Float
let y: Float
@@ -416,11 +409,13 @@ private extension WebViewController {
func loadWebView() {
guard isViewLoaded else { return }
+ if let webView = webView {
+ self.renderPage(webView)
+ return
+ }
+
coordinator.webViewProvider.dequeueWebView() { webView in
- let webViewToRecycle = self.webView
- self.renderPage(webViewToRecycle)
-
// Add the webview
webView.translatesAutoresizingMaskIntoConstraints = false
self.view.insertSubview(webView, at: 0)
@@ -451,9 +446,6 @@ private extension WebViewController {
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown)
self.renderPage(webView)
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
- self.recycleWebView(webViewToRecycle)
- }
}
@@ -498,16 +490,16 @@ private extension WebViewController {
rendering = ArticleRenderer.noSelectionHTML(style: style)
}
- let templateData = TemplateData(style: rendering.style, body: rendering.html, title: rendering.title, baseURL: rendering.baseURL)
-
- let encoder = JSONEncoder()
- var render = "error();"
- if let data = try? encoder.encode(templateData) {
- let json = String(data: data, encoding: .utf8)!
- render = "render(\(json), \(windowScrollY));"
- }
+ let substitutions = [
+ "title": rendering.title,
+ "baseURL": rendering.baseURL,
+ "style": rendering.style,
+ "body": rendering.html,
+ "windowScrollY": String(windowScrollY)
+ ]
- webView.evaluateJavaScript(render)
+ let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions)
+ webView.loadHTMLString(html, baseURL: ArticleRenderer.page.baseURL)
}
diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift
index d8fef2c2c..3c68dd119 100644
--- a/iOS/KeyboardManager.swift
+++ b/iOS/KeyboardManager.swift
@@ -144,6 +144,9 @@ private extension KeyboardManager {
let markAllAsReadTitle = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
keys.append(KeyboardManager.createKeyCommand(title: markAllAsReadTitle, action: "markAllAsRead:", input: "k", modifiers: [.command]))
+ let cleanUp = NSLocalizedString("Clean Up", comment: "Clean Up")
+ keys.append(KeyboardManager.createKeyCommand(title: cleanUp, action: "cleanUp:", input: "h", modifiers: [.command, .shift]))
+
return keys
}
diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift
index 0abe0d939..2e4999a76 100644
--- a/iOS/MasterTimeline/MasterTimelineViewController.swift
+++ b/iOS/MasterTimeline/MasterTimelineViewController.swift
@@ -70,7 +70,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
tableView.dataSource = dataSource
numberOfTextLines = AppDefaults.timelineNumberOfLines
iconSize = AppDefaults.timelineIconSize
- resetEstimatedRowHeight()
+ tableView.rowHeight = calculateEstimatedRowHeight(forId: PrototypeFeedContent.feedId, withTitle: PrototypeFeedContent.longTitle, andFeed: PrototypeFeedContent.feedname)
if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView {
navigationItem.titleView = titleView
@@ -443,7 +443,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
if numberOfTextLines != AppDefaults.timelineNumberOfLines || iconSize != AppDefaults.timelineIconSize {
numberOfTextLines = AppDefaults.timelineNumberOfLines
iconSize = AppDefaults.timelineIconSize
- resetEstimatedRowHeight()
+ tableView.rowHeight = calculateEstimatedRowHeight(forId: PrototypeFeedContent.feedId, withTitle: PrototypeFeedContent.longTitle, andFeed: PrototypeFeedContent.feedname)
reloadAllVisibleCells()
}
}
@@ -487,26 +487,21 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
// MARK: Cell Configuring
- private func resetEstimatedRowHeight() {
+ private func calculateEstimatedRowHeight(forId prototypeID: String, withTitle title: String, andFeed feedName: String) -> CGFloat {
- let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
-
- let prototypeID = "prototype"
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
- let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status)
+ let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: title, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status)
- let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize)
+ let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: feedName, iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize)
if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory {
let layout = MasterTimelineAccessibilityCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData)
- tableView.estimatedRowHeight = layout.height
+ return layout.height
} else {
let layout = MasterTimelineDefaultCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData)
- tableView.estimatedRowHeight = layout.height
+ return layout.height
}
-
}
-
}
// MARK: Searching
@@ -633,7 +628,9 @@ private extension MasterTimelineViewController {
var snapshot = NSDiffableDataSourceSnapshot()
snapshot.appendSections([0])
snapshot.appendItems(coordinator.articles, toSection: 0)
-
+ if coordinator.articles.count == 0 {
+ tableView.rowHeight = calculateEstimatedRowHeight(forId: PrototypeFeedContent.feedId, withTitle: PrototypeFeedContent.longTitle, andFeed: PrototypeFeedContent.feedname)
+ }
dataSource.apply(snapshot, animatingDifferences: animated) { [weak self] in
self?.restoreSelectionIfNecessary(adjustScroll: false)
completion?()
@@ -904,3 +901,10 @@ private extension MasterTimelineViewController {
}
}
+
+fileprivate struct PrototypeFeedContent {
+ static let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
+ static let shortTitle = "prototype"
+ static let feedId = "feedId"
+ static let feedname = "prototype"
+}
diff --git a/iOS/Resources/Info.plist b/iOS/Resources/Info.plist
index 80d20fb24..c2c6bdc76 100644
--- a/iOS/Resources/Info.plist
+++ b/iOS/Resources/Info.plist
@@ -164,5 +164,7 @@
+ UserAgent
+ NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/)
diff --git a/iOS/Resources/blank.html b/iOS/Resources/blank.html
new file mode 100644
index 000000000..6e02cf3a6
--- /dev/null
+++ b/iOS/Resources/blank.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/iOS/Resources/page.html b/iOS/Resources/page.html
index 22bc5881c..edd9953fa 100644
--- a/iOS/Resources/page.html
+++ b/iOS/Resources/page.html
@@ -1,17 +1,22 @@
-
-
+ [[title]]
+
+
+ [[body]]
diff --git a/iOS/Resources/styleSheet.css b/iOS/Resources/styleSheet.css
index 0190e1e69..d9eddcf15 100644
--- a/iOS/Resources/styleSheet.css
+++ b/iOS/Resources/styleSheet.css
@@ -1,4 +1,5 @@
:root {
+ color-scheme: light dark;
font: -apple-system-body;
font-size: [[font-size]]px;
}
diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift
index 93a6e4629..6f02ab425 100644
--- a/iOS/RootSplitViewController.swift
+++ b/iOS/RootSplitViewController.swift
@@ -90,6 +90,10 @@ class RootSplitViewController: UISplitViewController {
coordinator.showAdd(.folder)
}
+ @objc func cleanUp(_ sender: Any?) {
+ coordinator.cleanUp()
+ }
+
@objc func refresh(_ sender: Any?) {
appDelegate.manualRefresh(errorHandler: ErrorHandler.present(self))
}
diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift
index 621d5de73..ea7065f76 100644
--- a/iOS/SceneCoordinator.swift
+++ b/iOS/SceneCoordinator.swift
@@ -572,7 +572,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
fetchRequestQueue.cancelAllRequests()
}
- func refreshInterface() {
+ func cleanUp() {
if isReadFeedsFiltered {
rebuildBackingStores()
}
diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift
index af85dd81a..7fb620f1c 100644
--- a/iOS/SceneDelegate.swift
+++ b/iOS/SceneDelegate.swift
@@ -21,10 +21,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
window = UIWindow(windowScene: scene as! UIWindowScene)
window!.tintColor = AppAssets.primaryAccentColor
+ updateUserInterfaceStyle()
window!.rootViewController = coordinator.start(for: window!.frame.size)
coordinator.restoreWindowState(session.stateRestorationActivity)
+ NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil)
+
if let shortcutItem = connectionOptions.shortcutItem {
window!.makeKeyAndVisible()
handleShortcutItem(shortcutItem)
@@ -81,8 +84,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
coordinator.suspend()
}
- func refreshInterface() {
- coordinator.refreshInterface()
+ func cleanUp() {
+ coordinator.cleanUp()
}
}
@@ -102,4 +105,19 @@ private extension SceneDelegate {
}
}
+ @objc func userDefaultsDidChange() {
+ updateUserInterfaceStyle()
+ }
+
+ func updateUserInterfaceStyle() {
+ switch AppDefaults.userInterfaceColorPalette {
+ case .automatic:
+ window!.overrideUserInterfaceStyle = .unspecified
+ case .light:
+ window!.overrideUserInterfaceStyle = .light
+ case .dark:
+ window!.overrideUserInterfaceStyle = .dark
+ }
+ }
+
}
diff --git a/iOS/Settings/ColorPaletteTableViewController.swift b/iOS/Settings/ColorPaletteTableViewController.swift
new file mode 100644
index 000000000..d64671c6f
--- /dev/null
+++ b/iOS/Settings/ColorPaletteTableViewController.swift
@@ -0,0 +1,42 @@
+//
+// ColorPaletteTableViewController.swift
+// NetNewsWire-iOS
+//
+// Created by Maurice Parker on 3/15/20.
+// Copyright © 2020 Ranchero Software. All rights reserved.
+//
+
+import UIKit
+
+class ColorPaletteTableViewController: UITableViewController {
+
+ // MARK: - Table view data source
+
+ override func numberOfSections(in tableView: UITableView) -> Int {
+ return 1
+ }
+
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return UserInterfaceColorPalette.allCases.count
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+ let rowColorPalette = UserInterfaceColorPalette.allCases[indexPath.row]
+ cell.textLabel?.text = String(describing: rowColorPalette)
+ if rowColorPalette == AppDefaults.userInterfaceColorPalette {
+ cell.accessoryType = .checkmark
+ } else {
+ cell.accessoryType = .none
+ }
+ return cell
+ }
+
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ if let colorPalette = UserInterfaceColorPalette(rawValue: indexPath.row) {
+ AppDefaults.userInterfaceColorPalette = colorPalette
+ }
+ navigationController?.popViewController(animated: true)
+ }
+
+}
diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard
index ed773045c..1ecb90a8e 100644
--- a/iOS/Settings/Settings.storyboard
+++ b/iOS/Settings/Settings.storyboard
@@ -1,8 +1,8 @@
-
+
-
+
@@ -19,7 +19,7 @@
-
+
@@ -40,7 +40,7 @@
-
+
@@ -61,7 +61,7 @@
-
+
@@ -317,136 +317,172 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -471,6 +507,7 @@
+
@@ -968,6 +1005,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift
index 35e2ac431..0f3c47505 100644
--- a/iOS/Settings/SettingsViewController.swift
+++ b/iOS/Settings/SettingsViewController.swift
@@ -21,6 +21,7 @@ class SettingsViewController: UITableViewController {
@IBOutlet weak var refreshClearsReadArticlesSwitch: UISwitch!
@IBOutlet weak var confirmMarkAllAsReadSwitch: UISwitch!
@IBOutlet weak var showFullscreenArticlesSwitch: UISwitch!
+ @IBOutlet weak var colorPaletteDetailLabel: UILabel!
var scrollToArticlesSection = false
weak var presentingParentController: UIViewController?
@@ -33,7 +34,6 @@ class SettingsViewController: UITableViewController {
NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange), name: .UserDidAddAccount, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange), name: .UserDidDeleteAccount, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange), name: .DisplayNameDidChange, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil)
tableView.register(UINib(nibName: "SettingsAccountTableViewCell", bundle: nil), forCellReuseIdentifier: "SettingsAccountTableViewCell")
tableView.register(UINib(nibName: "SettingsTableViewCell", bundle: nil), forCellReuseIdentifier: "SettingsTableViewCell")
@@ -74,6 +74,8 @@ class SettingsViewController: UITableViewController {
} else {
showFullscreenArticlesSwitch.isOn = false
}
+
+ colorPaletteDetailLabel.text = String(describing: AppDefaults.userInterfaceColorPalette)
let buildLabel = NonIntrinsicLabel(frame: CGRect(x: 32.0, y: 0.0, width: 0.0, height: 0.0))
buildLabel.font = UIFont.systemFont(ofSize: 11.0)
@@ -192,6 +194,9 @@ class SettingsViewController: UITableViewController {
break
}
case 5:
+ let colorPalette = UIStoryboard.settings.instantiateController(ofType: ColorPaletteTableViewController.self)
+ self.navigationController?.pushViewController(colorPalette, animated: true)
+ case 6:
switch indexPath.row {
case 0:
openURL("https://ranchero.com/netnewswire/help/ios/5.0/en/")
@@ -304,10 +309,6 @@ class SettingsViewController: UITableViewController {
@objc func displayNameDidChange() {
tableView.reloadData()
}
-
- @objc func userDefaultsDidChange() {
- tableView.reloadData()
- }
}
diff --git a/iOS/UIKit Extensions/UITableView-Extensions.swift b/iOS/UIKit Extensions/UITableView-Extensions.swift
index fd5ff5614..368680ee9 100644
--- a/iOS/UIKit Extensions/UITableView-Extensions.swift
+++ b/iOS/UIKit Extensions/UITableView-Extensions.swift
@@ -17,13 +17,13 @@ extension UITableView {
selectRow(at: indexPath, animated: animations.contains(.select), scrollPosition: .none)
if let visibleIndexPaths = indexPathsForRows(in: safeAreaLayoutGuide.layoutFrame) {
- if !(visibleIndexPaths.contains(indexPath) && cellCompletelyVisable(indexPath)) {
+ if !(visibleIndexPaths.contains(indexPath) && cellCompletelyVisible(indexPath)) {
scrollToRow(at: indexPath, at: .middle, animated: animations.contains(.scroll))
}
}
}
- func cellCompletelyVisable(_ indexPath: IndexPath) -> Bool {
+ func cellCompletelyVisible(_ indexPath: IndexPath) -> Bool {
let rect = rectForRow(at: indexPath)
return safeAreaLayoutGuide.layoutFrame.contains(rect)
}
diff --git a/iOS/UIKit Extensions/VibrantTableViewCell.swift b/iOS/UIKit Extensions/VibrantTableViewCell.swift
index dfa4af493..8a803c171 100644
--- a/iOS/UIKit Extensions/VibrantTableViewCell.swift
+++ b/iOS/UIKit Extensions/VibrantTableViewCell.swift
@@ -71,6 +71,7 @@ class VibrantTableViewCell: UITableViewCell {
class VibrantBasicTableViewCell: VibrantTableViewCell {
@IBOutlet private var label: UILabel!
+ @IBOutlet private var detail: UILabel!
@IBOutlet private var icon: UIImageView!
@IBInspectable var imageNormal: UIImage?
@@ -88,6 +89,7 @@ class VibrantBasicTableViewCell: VibrantTableViewCell {
super.updateVibrancy(animated: animated)
updateIconVibrancy(icon, color: iconTint, image: iconImage, animated: animated)
updateLabelVibrancy(label, color: labelColor, animated: animated)
+ updateLabelVibrancy(detail, color: secondaryLabelColor, animated: animated)
}
private func updateIconVibrancy(_ icon: UIImageView?, color: UIColor, image: UIImage?, animated: Bool) {