From 948998382d60133724141cd478e825e9080743a2 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 3 Nov 2020 17:08:56 -0600 Subject: [PATCH] Make article view text adjustable via the app preferences. Fixes #42 --- Mac/AppDefaults.swift | 11 + Mac/Base.lproj/Preferences.storyboard | 289 ++++++++++-------- .../Detail/DetailWebViewController.swift | 14 +- Mac/MainWindow/Detail/styleSheet.css | 2 +- .../GeneralPrefencesViewController.swift | 143 --------- NetNewsWire.xcodeproj/project.pbxproj | 20 +- .../Article Rendering/ArticleRenderer.swift | 7 +- .../Article Rendering/ArticleTextSize.swift | 52 ++++ 8 files changed, 257 insertions(+), 281 deletions(-) create mode 100644 Shared/Article Rendering/ArticleTextSize.swift diff --git a/Mac/AppDefaults.swift b/Mac/AppDefaults.swift index caed0f08c..7be80dee5 100644 --- a/Mac/AppDefaults.swift +++ b/Mac/AppDefaults.swift @@ -30,6 +30,7 @@ final class AppDefaults { static let timelineGroupByFeed = "timelineGroupByFeed" static let detailFontSize = "detailFontSize" static let openInBrowserInBackground = "openInBrowserInBackground" + static let articleTextSize = "articleTextSize" static let refreshInterval = "refreshInterval" static let addWebFeedAccountID = "addWebFeedAccountID" static let addWebFeedFolderName = "addWebFeedFolderName" @@ -244,6 +245,16 @@ final class AppDefaults { return AppDefaults.bool(for: Key.timelineShowsSeparators) } + var articleTextSize: ArticleTextSize { + get { + let rawValue = UserDefaults.standard.integer(forKey: Key.articleTextSize) + return ArticleTextSize(rawValue: rawValue) ?? ArticleTextSize.large + } + set { + UserDefaults.standard.set(newValue.rawValue, forKey: Key.articleTextSize) + } + } + var refreshInterval: RefreshInterval { get { let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval) diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard index 4fbf41e07..2ad8500d2 100644 --- a/Mac/Base.lproj/Preferences.storyboard +++ b/Mac/Base.lproj/Preferences.storyboard @@ -25,21 +25,115 @@ - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -47,7 +141,7 @@ - + @@ -81,51 +175,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -133,7 +184,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + + + - - + + + - + - - + - + - + + - @@ -219,29 +242,29 @@ + - - + - + - + - + @@ -249,7 +272,7 @@ - + @@ -267,7 +290,7 @@ - + @@ -298,7 +321,7 @@ - + @@ -316,7 +339,7 @@ + + + + + + @@ -354,6 +383,8 @@ + + @@ -363,15 +394,19 @@ + + + - + + @@ -407,22 +442,22 @@ - + - - - + + + - - + + - + @@ -435,7 +470,7 @@ - + @@ -447,7 +482,7 @@ - + @@ -523,7 +558,7 @@ - + @@ -578,22 +613,22 @@ - + - - - + + + - - + + - + @@ -606,7 +641,7 @@ - + @@ -618,7 +653,7 @@ - + @@ -690,7 +725,7 @@ - + diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 6d2bdb2c9..9e13e9129 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -39,6 +39,14 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { return nil } } + + private var articleTextSize = AppDefaults.shared.articleTextSize { + didSet { + if articleTextSize != oldValue { + reloadHTML() + } + } + } #if !MAC_APP_STORE private var webInspectorEnabled: Bool { @@ -118,7 +126,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) webView.loadFileURL(ArticleRenderer.blank.url, allowingReadAccessTo: ArticleRenderer.blank.baseURL) } @@ -137,6 +145,10 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { reloadArticleImage() } + @objc func userDefaultsDidChange(_ note: Notification) { + self.articleTextSize = AppDefaults.shared.articleTextSize + } + // MARK: Media Functions func stopMediaPlayback() { diff --git a/Mac/MainWindow/Detail/styleSheet.css b/Mac/MainWindow/Detail/styleSheet.css index 2caaa30eb..64625a4d7 100644 --- a/Mac/MainWindow/Detail/styleSheet.css +++ b/Mac/MainWindow/Detail/styleSheet.css @@ -4,7 +4,7 @@ body { padding-left: 64px; padding-right: 64px; font-family: -apple-system; - font-size: 18px; + font-size: [[font-size]]px; } :root { diff --git a/Mac/Preferences/General/GeneralPrefencesViewController.swift b/Mac/Preferences/General/GeneralPrefencesViewController.swift index 9d4b0c20b..623052044 100644 --- a/Mac/Preferences/General/GeneralPrefencesViewController.swift +++ b/Mac/Preferences/General/GeneralPrefencesViewController.swift @@ -15,10 +15,8 @@ final class GeneralPreferencesViewController: NSViewController { private var userNotificationSettings: UNNotificationSettings? - @IBOutlet var defaultRSSReaderPopup: NSPopUpButton! @IBOutlet var defaultBrowserPopup: NSPopUpButton! @IBOutlet weak var showUnreadCountCheckbox: NSButton! - private var rssReaderInfo = RSSReaderInfo() public override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) @@ -44,17 +42,6 @@ final class GeneralPreferencesViewController: NSViewController { // MARK: - Actions - @IBAction func rssReaderPopupDidChangeValue(_ sender: Any?) { - guard let menuItem = defaultRSSReaderPopup.selectedItem else { - return - } - guard let bundleID = menuItem.representedObject as? String else { - return - } - registerAppWithBundleID(bundleID) - updateUI() - } - @IBAction func browserPopUpDidChangeValue(_ sender: Any?) { guard let menuItem = defaultBrowserPopup.selectedItem else { return @@ -115,69 +102,10 @@ private extension GeneralPreferencesViewController { } func updateUI() { - rssReaderInfo = RSSReaderInfo() updateBrowserPopup() - updateRSSReaderPopup() updateHideUnreadCountCheckbox() } - func updateRSSReaderPopup() { - // Top item should always be: NetNewsWire (this app) - // Additional items should be sorted alphabetically. - // Any older versions of NetNewsWire should be listed as: NetNewsWire (old version) - - let menu = NSMenu(title: "RSS Readers") - - let netNewsWireBundleID = Bundle.main.bundleIdentifier! - let thisAppParentheticalComment = NSLocalizedString("(this app)", comment: "Preferences default RSS Reader popup") - let thisAppName = "NetNewsWire \(thisAppParentheticalComment)" - let netNewsWireMenuItem = NSMenuItem(title: thisAppName, action: nil, keyEquivalent: "") - netNewsWireMenuItem.representedObject = netNewsWireBundleID - menu.addItem(netNewsWireMenuItem) - - let readersToList = rssReaderInfo.rssReaders.filter { $0.bundleID != netNewsWireBundleID } - let sortedReaders = readersToList.sorted { (reader1, reader2) -> Bool in - return reader1.nameMinusAppSuffix.localizedStandardCompare(reader2.nameMinusAppSuffix) == .orderedAscending - } - - let oldVersionParentheticalComment = NSLocalizedString("(old version)", comment: "Preferences default RSS Reader popup") - for rssReader in sortedReaders { - var appName = rssReader.nameMinusAppSuffix - if appName.contains("NetNewsWire") { - appName = "\(appName) \(oldVersionParentheticalComment)" - } - let menuItem = NSMenuItem(title: appName, action: nil, keyEquivalent: "") - menuItem.representedObject = rssReader.bundleID - menu.addItem(menuItem) - } - - defaultRSSReaderPopup.menu = menu - - func insertAndSelectNoneMenuItem() { - let noneTitle = NSLocalizedString("None", comment: "Preferences default RSS Reader popup") - let menuItem = NSMenuItem(title: noneTitle, action: nil, keyEquivalent: "") - defaultRSSReaderPopup.menu!.insertItem(menuItem, at: 0) - defaultRSSReaderPopup.selectItem(at: 0) - } - - guard let defaultRSSReaderBundleID = rssReaderInfo.defaultRSSReaderBundleID else { - insertAndSelectNoneMenuItem() - return - } - - for menuItem in defaultRSSReaderPopup.menu!.items { - guard let bundleID = menuItem.representedObject as? String else { - continue - } - if bundleID == defaultRSSReaderBundleID { - defaultRSSReaderPopup.select(menuItem) - return - } - } - - insertAndSelectNoneMenuItem() - } - func registerAppWithBundleID(_ bundleID: String) { NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feed", to: bundleID) NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feeds", to: bundleID) @@ -245,74 +173,3 @@ private extension GeneralPreferencesViewController { } } - -// MARK: - RSSReaderInfo - -private struct RSSReaderInfo { - - let defaultRSSReaderBundleID: String? - let rssReaders: Set - static let feedURLScheme = "feed:" - - init() { - let defaultRSSReaderBundleID = NSWorkspace.shared.defaultAppBundleID(forURLScheme: RSSReaderInfo.feedURLScheme) - self.defaultRSSReaderBundleID = defaultRSSReaderBundleID - self.rssReaders = RSSReaderInfo.fetchRSSReaders(defaultRSSReaderBundleID) - } - - static func fetchRSSReaders(_ defaultRSSReaderBundleID: String?) -> Set { - let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: feedURLScheme) - - var rssReaders = Set() - if let defaultRSSReaderBundleID = defaultRSSReaderBundleID, let defaultReader = RSSReader(bundleID: defaultRSSReaderBundleID) { - rssReaders.insert(defaultReader) - } - rssReaderBundleIDs.forEach { (bundleID) in - if let reader = RSSReader(bundleID: bundleID) { - rssReaders.insert(reader) - } - } - return rssReaders - } -} - - -// MARK: - RSSReader - -private struct RSSReader: Hashable { - - let bundleID: String - let name: String - let nameMinusAppSuffix: String - let path: String - - init?(bundleID: String) { - guard let path = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleID) else { - return nil - } - - self.path = path - self.bundleID = bundleID - - let name = (path as NSString).lastPathComponent - self.name = name - if name.hasSuffix(".app") { - self.nameMinusAppSuffix = name.stripping(suffix: ".app") - } - else { - self.nameMinusAppSuffix = name - } - } - - // MARK: - Hashable - - func hash(into hasher: inout Hasher) { - hasher.combine(bundleID) - } - - // MARK: - Equatable - - static func ==(lhs: RSSReader, rhs: RSSReader) -> Bool { - return lhs.bundleID == rhs.bundleID - } -} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 6ce989f16..938e2013b 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -495,6 +495,12 @@ 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */; }; 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; + 51DC07982552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; + 51DC07992552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; + 51DC079A2552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; + 51DC079B2552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; + 51DC079C2552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; + 51DC07AC255209E200A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; 51DC37072402153E0095D371 /* UpdateSelectionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC37062402153E0095D371 /* UpdateSelectionOperation.swift */; }; 51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC37082402F1470095D371 /* MasterFeedDataSourceOperation.swift */; }; 51DC370B2405BC9A0095D371 /* PreloadedWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC370A2405BC9A0095D371 /* PreloadedWebView.swift */; }; @@ -1730,6 +1736,7 @@ 51CE1C0A23622006005548FC /* RefreshProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshProgressView.swift; sourceTree = ""; }; 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineDataSource.swift; sourceTree = ""; }; 51D87EE02311D34700E63F03 /* ActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityType.swift; sourceTree = ""; }; + 51DC07972552083500A3F79F /* ArticleTextSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleTextSize.swift; sourceTree = ""; }; 51DC37062402153E0095D371 /* UpdateSelectionOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateSelectionOperation.swift; sourceTree = ""; }; 51DC37082402F1470095D371 /* MasterFeedDataSourceOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedDataSourceOperation.swift; sourceTree = ""; }; 51DC370A2405BC9A0095D371 /* PreloadedWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreloadedWebView.swift; sourceTree = ""; }; @@ -2804,11 +2811,12 @@ 51C452A822650DA100C03939 /* Article Rendering */ = { isa = PBXGroup; children = ( - 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */, + B27EEBDF244D15F2000932E6 /* shared.css */, + 848362FE2262A30E00DA1D35 /* template.html */, 517630032336215100E15FFF /* main.js */, 49F40DEF2335B71000552BF4 /* newsfoot.js */, - 848362FE2262A30E00DA1D35 /* template.html */, - B27EEBDF244D15F2000932E6 /* shared.css */, + 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */, + 51DC07972552083500A3F79F /* ArticleTextSize.swift */, ); path = "Article Rendering"; sourceTree = ""; @@ -4424,6 +4432,7 @@ 511B149A24E5DC5400C919BD /* RefreshInterval.swift in Sources */, 510C418324E5D1B4008226FD /* ExtensionContainersFile.swift in Sources */, 510C418524E5D1B4008226FD /* ExtensionContainers.swift in Sources */, + 51DC07AC255209E200A3F79F /* ArticleTextSize.swift in Sources */, 511B149824E5DC2300C919BD /* ShareDefaultContainer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4485,6 +4494,7 @@ 51E4996A24A8762D00B667CB /* ExtractedArticle.swift in Sources */, 51919FF124AB864A00541E64 /* TimelineModel.swift in Sources */, 5194736E24BBB937001A2939 /* HiddenModifier.swift in Sources */, + 51DC079B2552083500A3F79F /* ArticleTextSize.swift in Sources */, 51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */, 51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */, 51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */, @@ -4668,6 +4678,7 @@ 51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */, 51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */, 51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, + 51DC079C2552083500A3F79F /* ArticleTextSize.swift in Sources */, 51B80F1F24BE531200C6C32D /* SharingServiceView.swift in Sources */, 17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */, 51A8005224CC453C00F41F1D /* ReplaySubject.swift in Sources */, @@ -4799,6 +4810,7 @@ 65ED3FC2235DEF6C0081F399 /* Browser.swift in Sources */, 65ED3FC3235DEF6C0081F399 /* DetailWebViewController.swift in Sources */, 65ED3FC4235DEF6C0081F399 /* OPMLExporter.swift in Sources */, + 51DC07992552083500A3F79F /* ArticleTextSize.swift in Sources */, 65ED3FC5235DEF6C0081F399 /* MainWindowController.swift in Sources */, 65ED3FC6235DEF6C0081F399 /* UnreadFeed.swift in Sources */, 65ED3FC8235DEF6C0081F399 /* SidebarCellLayout.swift in Sources */, @@ -5049,6 +5061,7 @@ 51C452AE2265104D00C03939 /* ArticleStringFormatter.swift in Sources */, 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */, 51A9A60A2382FD240033AADF /* PoppableGestureRecognizerDelegate.swift in Sources */, + 51DC079A2552083500A3F79F /* ArticleTextSize.swift in Sources */, 51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */, 51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */, 51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */, @@ -5223,6 +5236,7 @@ 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */, 84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */, 515A5148243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */, + 51DC07982552083500A3F79F /* ArticleTextSize.swift in Sources */, 5117715524E1EA0F00A2A836 /* ArticleExtractorButton.swift in Sources */, 5103A9F724225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */, 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */, diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 7d15c6482..16e601462 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -276,12 +276,7 @@ private extension ArticleRenderer { #else func styleSubstitutions() -> [String: String] { var d = [String: String]() - - if #available(macOS 11.0, *) { - let bodyFont = NSFont.preferredFont(forTextStyle: .body) - d["font-size"] = String(describing: Int(round(bodyFont.pointSize * 1.33))) - } - + d["font-size"] = String(describing: AppDefaults.shared.articleTextSize.fontSize) return d } #endif diff --git a/Shared/Article Rendering/ArticleTextSize.swift b/Shared/Article Rendering/ArticleTextSize.swift new file mode 100644 index 000000000..6911e8ba1 --- /dev/null +++ b/Shared/Article Rendering/ArticleTextSize.swift @@ -0,0 +1,52 @@ +// +// ArticleTextSize.swift +// NetNewsWire +// +// Created by Maurice Parker on 11/3/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation + +enum ArticleTextSize: Int, CaseIterable, Identifiable { + case small = 1 + case medium = 2 + case large = 3 + case xlarge = 4 + case xxlarge = 5 + + #if os(macOS) + var fontSize: Int { + switch self { + case .small: + return 14 + case .medium: + return 16 + case .large: + return 18 + case .xlarge: + return 20 + case .xxlarge: + return 22 + } + } + #endif + + var id: String { description() } + + func description() -> String { + switch self { + case .small: + return NSLocalizedString("Small", comment: "Small") + case .medium: + return NSLocalizedString("Medium", comment: "Medium") + case .large: + return NSLocalizedString("Large", comment: "Large") + case .xlarge: + return NSLocalizedString("X-Large", comment: "X-Large") + case .xxlarge: + return NSLocalizedString("XX-Large", comment: "XX-Large") + } + } + +}