From 016143e3063fb4917e74cd7ad8cdd56e4c225719 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 18 Oct 2020 18:25:30 -0700 Subject: [PATCH 01/42] Bump version and build number for 5.0.5 TestFlight build. --- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index 5069c364b..186ab57f8 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it -MARKETING_VERSION = 5.0.4 -CURRENT_PROJECT_VERSION = 53 +MARKETING_VERSION = 5.0.5 +CURRENT_PROJECT_VERSION = 54 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From def5d84647a17f3f771fa1a8da2ae47cebf9e434 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 18 Oct 2020 18:39:22 -0700 Subject: [PATCH 02/42] Remove no-longer-used variable and fix warning. --- Frameworks/Account/LocalAccount/LocalAccountRefresher.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift index 3ed75b47a..cd088d720 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift @@ -98,7 +98,7 @@ extension LocalAccountRefresher: DownloadSessionDelegate { } func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool { - guard !isSuspended, let feed = representedObject as? WebFeed else { + guard !isSuspended else { return false } From 8dbe5691e3c6efe45701527e7f567f445f55ff4a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 20 Oct 2020 13:45:28 -0500 Subject: [PATCH 03/42] Update the user interface style on the main thread. --- iOS/SceneDelegate.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index 0a4ffdf80..49c541090 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -111,13 +111,15 @@ private extension SceneDelegate { } func updateUserInterfaceStyle() { - switch AppDefaults.userInterfaceColorPalette { - case .automatic: - window!.overrideUserInterfaceStyle = .unspecified - case .light: - window!.overrideUserInterfaceStyle = .light - case .dark: - window!.overrideUserInterfaceStyle = .dark + DispatchQueue.main.async { + switch AppDefaults.userInterfaceColorPalette { + case .automatic: + self.window?.overrideUserInterfaceStyle = .unspecified + case .light: + self.window?.overrideUserInterfaceStyle = .light + case .dark: + self.window?.overrideUserInterfaceStyle = .dark + } } } From 64bb2e58e9b14a9dd4be9b3df2cc43b94afc655d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 20 Oct 2020 13:51:24 -0500 Subject: [PATCH 04/42] Update to RSCore with image scaling fix --- submodules/RSCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/RSCore b/submodules/RSCore index a175db500..d685c33ff 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit a175db5009f8222fcbaa825d9501305e8727da6f +Subproject commit d685c33ffbc8a5350d376f44384c0ca3592507db From 6d9dcecd81faf4e88a3b0f25021f5bb10ed83f68 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 24 Oct 2020 18:15:59 -0500 Subject: [PATCH 05/42] Prevent backgrounds from showing up when displaying symbol icons. Fixes #2523 --- Shared/Extensions/IconImage.swift | 6 ++++-- iOS/AppAssets.swift | 10 +++++----- iOS/IconView.swift | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Shared/Extensions/IconImage.swift b/Shared/Extensions/IconImage.swift index 1ddc2935d..1839b4abf 100644 --- a/Shared/Extensions/IconImage.swift +++ b/Shared/Extensions/IconImage.swift @@ -25,9 +25,11 @@ final class IconImage { }() let image: RSImage - - init(_ image: RSImage) { + let isSymbol: Bool + + init(_ image: RSImage, isSymbol: Bool = false) { self.image = image + self.isSymbol = isSymbol } } diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 287e27b23..b982bc677 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -126,7 +126,7 @@ struct AppAssets { }() static var masterFolderImage: IconImage = { - return IconImage(UIImage(systemName: "folder.fill")!) + return IconImage(UIImage(systemName: "folder.fill")!, isSymbol: true) }() static var moreImage: UIImage = { @@ -158,7 +158,7 @@ struct AppAssets { }() static var searchFeedImage: IconImage = { - return IconImage(UIImage(systemName: "magnifyingglass")!) + return IconImage(UIImage(systemName: "magnifyingglass")!, isSymbol: true) }() static var secondaryAccentColor: UIColor = { @@ -190,7 +190,7 @@ struct AppAssets { }() static var starredFeedImage: IconImage = { - return IconImage(UIImage(systemName: "star.fill")!) + return IconImage(UIImage(systemName: "star.fill")!, isSymbol: true) }() static var tickMarkColor: UIColor = { @@ -203,7 +203,7 @@ struct AppAssets { }() static var todayFeedImage: IconImage = { - return IconImage(UIImage(systemName: "sun.max.fill")!) + return IconImage(UIImage(systemName: "sun.max.fill")!, isSymbol: true) }() static var trashImage: UIImage = { @@ -211,7 +211,7 @@ struct AppAssets { }() static var unreadFeedImage: IconImage = { - return IconImage(UIImage(systemName: "largecircle.fill.circle")!) + return IconImage(UIImage(systemName: "largecircle.fill.circle")!, isSymbol: true) }() static var vibrantTextColor: UIColor = { diff --git a/iOS/IconView.swift b/iOS/IconView.swift index 7ce9faf3f..0c3885a5b 100644 --- a/iOS/IconView.swift +++ b/iOS/IconView.swift @@ -52,7 +52,7 @@ final class IconView: UIView { } private var isSymbolImage: Bool { - return imageView.image?.isSymbolImage ?? false + return iconImage?.isSymbol ?? false } override init(frame: CGRect) { From f0f86953ae3ea5ee60ac6c128e8ca47497ca08bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kiel=20Gillard=20=F0=9F=A4=AA?= Date: Mon, 26 Oct 2020 10:27:36 +1100 Subject: [PATCH 06/42] iOS: Ingest a change of feed name on Feedly into NNW. #2520 --- ...yCreateFeedsForCollectionFoldersOperation.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Frameworks/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift b/Frameworks/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift index 264c046a1..b166d1e48 100644 --- a/Frameworks/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift +++ b/Frameworks/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift @@ -59,6 +59,20 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { // find an existing feed previously added to the account if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) { + + // If the feed was renamed on Feedly, ensure we ingest the new name. + if feed.nameForDisplay != collectionFeed.title { + feed.name = collectionFeed.title + + // Let the rest of the app (e.g.: the sidebar) know the feed name changed + // `editedName` would post this if its value is changing. + // Setting the `name` property has no side effects like this. + if feed.editedName != nil { + feed.editedName = nil + } else { + feed.postDisplayNameDidChangeNotification() + } + } return (feed, folder) } else { // find an existing feed we created below in an earlier value From 61a86a5ad4f4bc78f82bf685cd031697b7a55493 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 29 Oct 2020 12:01:27 -0500 Subject: [PATCH 07/42] Allow Feedbin credentials to be updated. Fixes #2533 --- iOS/Account/FeedbinAccountViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Account/FeedbinAccountViewController.swift b/iOS/Account/FeedbinAccountViewController.swift index c9c60d5e0..a8489abbd 100644 --- a/iOS/Account/FeedbinAccountViewController.swift +++ b/iOS/Account/FeedbinAccountViewController.swift @@ -81,7 +81,7 @@ class FeedbinAccountViewController: UITableViewController { // When you fill in the email address via auto-complete it adds extra whitespace let trimmedEmail = email.trimmingCharacters(in: .whitespaces) - guard !AccountManager.shared.duplicateServiceAccount(type: .feedbin, username: trimmedEmail) else { + guard account != nil || !AccountManager.shared.duplicateServiceAccount(type: .feedbin, username: trimmedEmail) else { showError(NSLocalizedString("There is already a Feedbin account with that username created.", comment: "Duplicate Error")) return } From 1d7af8e4b84528b5970e67909f9fc83bacc84b95 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Thu, 29 Oct 2020 19:41:51 -0700 Subject: [PATCH 08/42] Bump build number. --- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index 186ab57f8..a4d788c03 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it MARKETING_VERSION = 5.0.5 -CURRENT_PROJECT_VERSION = 54 +CURRENT_PROJECT_VERSION = 55 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From 5739c819af143bf78ae3f48d5e4194bd8d9e8540 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 30 Oct 2020 03:52:57 -0500 Subject: [PATCH 09/42] Fix logic test to determine if an image is symbol or not. Fixes #2523 --- iOS/IconView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/IconView.swift b/iOS/IconView.swift index 0c3885a5b..a7d9f3e28 100644 --- a/iOS/IconView.swift +++ b/iOS/IconView.swift @@ -75,7 +75,7 @@ final class IconView: UIView { override func layoutSubviews() { imageView.setFrameIfNotEqual(rectForImageView()) - if (iconImage != nil && isVerticalBackgroundExposed && !isSymbolImage) || !isDisconcernable { + if !isSymbolImage && ((iconImage != nil && isVerticalBackgroundExposed) || !isDisconcernable) { backgroundColor = AppAssets.iconBackgroundColor } else { backgroundColor = nil From 9621021b9ca26737dc4a3f91f9c02826a8eaa022 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Fri, 30 Oct 2020 11:14:47 -0700 Subject: [PATCH 10/42] Bump build to 56. --- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index a4d788c03..7a5663a23 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it MARKETING_VERSION = 5.0.5 -CURRENT_PROJECT_VERSION = 55 +CURRENT_PROJECT_VERSION = 56 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon From bf534d979cdcc81ce8d08d014d236e3786067853 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 1 Nov 2020 10:55:07 -0600 Subject: [PATCH 11/42] Update externalID for the folder when changing its name --- Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 89bfa3ea4..9beadb958 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -235,6 +235,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { switch result { case .success: DispatchQueue.main.async { + folder.externalID = "user/-/label/\(name)" folder.name = name completion(.success(())) } From 2229811df0e077070ac33286ebc6a0082721d949 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 1 Nov 2020 11:35:26 -0600 Subject: [PATCH 12/42] Encode names so that the plus sign will work when renaming folders and feeds --- .../Account/ReaderAPI/ReaderAPICaller.swift | 62 ++++++++++++------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift b/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift index 714403ebd..b83b58f64 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift @@ -49,20 +49,22 @@ final class ReaderAPICaller: NSObject { } private var transport: Transport! - - var variant: ReaderAPIVariant = .generic - var credentials: Credentials? + private let uriComponentAllowed: CharacterSet + private var accessToken: String? weak var accountMetadata: AccountMetadata? + var variant: ReaderAPIVariant = .generic + var credentials: Credentials? + var server: String? { get { - return APIBaseURL?.host + return apiBaseURL?.host } } - private var APIBaseURL: URL? { + private var apiBaseURL: URL? { get { switch variant { case .generic, .freshRSS: @@ -77,8 +79,12 @@ final class ReaderAPICaller: NSObject { } init(transport: Transport) { - super.init() self.transport = transport + + var urlHostAllowed = CharacterSet.urlHostAllowed + urlHostAllowed.remove("+") + uriComponentAllowed = urlHostAllowed + super.init() } func cancelAll() { @@ -170,7 +176,7 @@ final class ReaderAPICaller: NSObject { func retrieveTags(completion: @escaping (Result<[ReaderAPITag]?, Error>) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -199,7 +205,7 @@ final class ReaderAPICaller: NSObject { } func renameTag(oldName: String, newName: String, completion: @escaping (Result) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -212,8 +218,13 @@ final class ReaderAPICaller: NSObject { request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" - let oldTagName = "user/-/label/\(oldName)" - let newTagName = "user/-/label/\(newName)" + guard let encodedOldName = self.encodeForURLPath(oldName), let encodedNewName = self.encodeForURLPath(newName) else { + completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) + return + } + + let oldTagName = "user/-/label/\(encodedOldName)" + let newTagName = "user/-/label/\(encodedNewName)" let postData = "T=\(token)&s=\(oldTagName)&dest=\(newTagName)".data(using: String.Encoding.utf8) self.transport.send(request: request, method: HTTPMethod.post, payload: postData!, completion: { (result) in @@ -235,7 +246,7 @@ final class ReaderAPICaller: NSObject { } func deleteTag(folder: Folder, completion: @escaping (Result) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -274,7 +285,7 @@ final class ReaderAPICaller: NSObject { } func retrieveSubscriptions(completion: @escaping (Result<[ReaderAPISubscription]?, Error>) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -302,7 +313,7 @@ final class ReaderAPICaller: NSObject { } func createSubscription(url: String, name: String?, folder: Folder?, completion: @escaping (Result) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -411,7 +422,7 @@ final class ReaderAPICaller: NSObject { } func renameSubscription(subscriptionID: String, newName: String, completion: @escaping (Result) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -424,7 +435,12 @@ final class ReaderAPICaller: NSObject { request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" - let postData = "T=\(token)&s=\(subscriptionID)&ac=edit&t=\(newName)".data(using: String.Encoding.utf8) + guard let encodedNewName = self.encodeForURLPath(newName) else { + completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) + return + } + + let postData = "T=\(token)&s=\(subscriptionID)&ac=edit&t=\(encodedNewName)".data(using: String.Encoding.utf8) self.transport.send(request: request, method: HTTPMethod.post, payload: postData!, completion: { (result) in switch result { @@ -445,7 +461,7 @@ final class ReaderAPICaller: NSObject { } func deleteSubscription(subscriptionID: String, completion: @escaping (Result) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -479,7 +495,7 @@ final class ReaderAPICaller: NSObject { func createTagging(subscriptionID: String, tagName: String, completion: @escaping (Result) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -514,7 +530,7 @@ final class ReaderAPICaller: NSObject { } func deleteTagging(subscriptionID: String, tagName: String, completion: @escaping (Result) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -554,7 +570,7 @@ final class ReaderAPICaller: NSObject { return } - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -603,7 +619,7 @@ final class ReaderAPICaller: NSObject { } func retrieveItemIDs(type: ItemIDType, webFeedID: String? = nil, completion: @escaping ((Result<[String], Error>) -> Void)) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } @@ -734,6 +750,10 @@ final class ReaderAPICaller: NSObject { private extension ReaderAPICaller { + func encodeForURLPath(_ pathComponent: String) -> String? { + return pathComponent.addingPercentEncoding(withAllowedCharacters: uriComponentAllowed) + } + func storeConditionalGet(key: String, headers: [AnyHashable : Any]) { if var conditionalGet = accountMetadata?.conditionalGetInfo { conditionalGet[key] = HTTPConditionalGetInfo(headers: headers) @@ -749,7 +769,7 @@ private extension ReaderAPICaller { } private func updateStateToEntries(entries: [String], state: ReaderState, add: Bool, completion: @escaping (Result) -> Void) { - guard let baseURL = APIBaseURL else { + guard let baseURL = apiBaseURL else { completion(.failure(CredentialsError.incompleteCredentials)) return } From 7c4c2de7a08bedacd2e53864957a5cbca8863b77 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 1 Nov 2020 14:15:44 -0600 Subject: [PATCH 13/42] Make folder restores work for Reader API --- .../ReaderAPI/ReaderAPIAccountDelegate.swift | 21 ++++++++++++------- .../Account/ReaderAPI/ReaderAPICaller.swift | 18 ++++++++++------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 9beadb958..1602469a0 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -299,13 +299,18 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } group.notify(queue: DispatchQueue.main) { - self.caller.deleteTag(folder: folder) { result in - switch result { - case .success: - account.removeFolder(folder) - completion(.success(())) - case .failure(let error): - completion(.failure(error)) + if self.variant == .theOldReader { + account.removeFolder(folder) + completion(.success(())) + } else { + self.caller.deleteTag(folder: folder) { result in + switch result { + case .success: + account.removeFolder(folder) + completion(.success(())) + case .failure(let error): + completion(.failure(error)) + } } } } @@ -858,7 +863,7 @@ private extension ReaderAPIAccountDelegate { switch result { case .success: if let name = name { - account.renameWebFeed(feed, to: name) { result in + self.renameWebFeed(for: account, with: feed, to: name) { result in switch result { case .success: self.initialFeedDownload(account: account, feed: feed, completion: completion) diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift b/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift index b83b58f64..48784e9f8 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift @@ -83,6 +83,7 @@ final class ReaderAPICaller: NSObject { var urlHostAllowed = CharacterSet.urlHostAllowed urlHostAllowed.remove("+") + urlHostAllowed.remove("&") uriComponentAllowed = urlHostAllowed super.init() } @@ -387,10 +388,10 @@ final class ReaderAPICaller: NSObject { request.httpMethod = "POST" var postString = "T=\(token)&ac=subscribe&s=\(streamId)" - if let folder = folder { - postString += "&a=user/-/label/\(folder.nameForDisplay)" + if let folderName = self.encodeForURLPath(folder?.nameForDisplay) { + postString += "&a=user/-/label/\(folderName)" } - if let name = name { + if let name = self.encodeForURLPath(name) { postString += "&t=\(name)" } @@ -508,8 +509,12 @@ final class ReaderAPICaller: NSObject { request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" - let tagName = "user/-/label/\(tagName)" - let postData = "T=\(token)&s=\(subscriptionID)&ac=edit&a=\(tagName)".data(using: String.Encoding.utf8) + guard let tagName = self.encodeForURLPath(tagName) else { + completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) + return + } + + let postData = "T=\(token)&s=\(subscriptionID)&ac=edit&a=user/-/label/\(tagName)".data(using: String.Encoding.utf8) self.transport.send(request: request, method: HTTPMethod.post, payload: postData!, completion: { (result) in switch result { @@ -750,7 +755,8 @@ final class ReaderAPICaller: NSObject { private extension ReaderAPICaller { - func encodeForURLPath(_ pathComponent: String) -> String? { + func encodeForURLPath(_ pathComponent: String?) -> String? { + guard let pathComponent = pathComponent else { return nil } return pathComponent.addingPercentEncoding(withAllowedCharacters: uriComponentAllowed) } From 1ced4448eae84f14da804bdaeb91cb9add0a5986 Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Sun, 1 Nov 2020 17:33:48 -0500 Subject: [PATCH 14/42] Support a new secret user default JalkutRespectFolderExpansionOnNextUnread, and revise the "next unread" strategy so that whether the search for a next unread wraps around to the top or not is parameterized. --- Mac/MainWindow/MainWindowController.swift | 25 ++++---- .../Sidebar/SidebarViewController.swift | 58 ++++++++++++------- .../Timeline/TimelineViewController.swift | 10 ++-- Shared/Timeline/ArticleArray.swift | 19 +++--- 4 files changed, 67 insertions(+), 45 deletions(-) diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 1bb206d19..0b1be8851 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -323,13 +323,16 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { NSCursor.setHiddenUntilMouseMoves(true) // TODO: handle search mode - if timelineViewController.canGoToNextUnread() { - goToNextUnreadInTimeline() + if timelineViewController.canGoToNextUnread(wrappingToTop: false) { + goToNextUnreadInTimeline(wrappingToTop: false) } - else if sidebarViewController.canGoToNextUnread() { - sidebarViewController.goToNextUnread() - if timelineViewController.canGoToNextUnread() { - goToNextUnreadInTimeline() + else if sidebarViewController.canGoToNextUnread(wrappingToTop: true) { + sidebarViewController.goToNextUnread(wrappingToTop: true) + + // If we ended up on the same timelineViewController, we may need to wrap + // around to the top of its contents. + if timelineViewController.canGoToNextUnread(wrappingToTop: true) { + goToNextUnreadInTimeline(wrappingToTop: true) } } } @@ -995,13 +998,13 @@ private extension MainWindowController { // MARK: - Command Validation - func canGoToNextUnread() -> Bool { + func canGoToNextUnread(wrappingToTop wrapping: Bool = false) -> Bool { guard let timelineViewController = currentTimelineViewController, let sidebarViewController = sidebarViewController else { return false } // TODO: handle search mode - return timelineViewController.canGoToNextUnread() || sidebarViewController.canGoToNextUnread() + return timelineViewController.canGoToNextUnread(wrappingToTop: wrapping) || sidebarViewController.canGoToNextUnread(wrappingToTop: wrapping) } func canMarkAllAsRead() -> Bool { @@ -1188,14 +1191,14 @@ private extension MainWindowController { // MARK: - Misc. - func goToNextUnreadInTimeline() { + func goToNextUnreadInTimeline(wrappingToTop wrapping: Bool) { guard let timelineViewController = currentTimelineViewController else { return } - if timelineViewController.canGoToNextUnread() { - timelineViewController.goToNextUnread() + if timelineViewController.canGoToNextUnread(wrappingToTop: wrapping) { + timelineViewController.goToNextUnread(wrappingToTop: wrapping) makeTimelineViewFirstResponder() } } diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index 22f097268..e7dcf8cd3 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -294,15 +294,15 @@ protocol SidebarDelegate: class { // MARK: - Navigation - func canGoToNextUnread() -> Bool { - if let _ = nextSelectableRowWithUnreadArticle() { + func canGoToNextUnread(wrappingToTop wrapping: Bool = false) -> Bool { + if let _ = nextSelectableRowWithUnreadArticle(wrappingToTop: wrapping) { return true } return false } - func goToNextUnread() { - guard let row = nextSelectableRowWithUnreadArticle() else { + func goToNextUnread(wrappingToTop wrapping: Bool = false) { + guard let row = nextSelectableRowWithUnreadArticle(wrappingToTop: wrapping) else { assertionFailure("goToNextUnread called before checking if there is a next unread.") return } @@ -685,26 +685,42 @@ private extension SidebarViewController { return false } - func nextSelectableRowWithUnreadArticle() -> Int? { - // Skip group items, because they should never be selected. - - let selectedRow = outlineView.selectedRow - let numberOfRows = outlineView.numberOfRows - var row = selectedRow + 1 - - while (row < numberOfRows) { - if rowHasAtLeastOneUnreadArticle(row) && !rowIsGroupItem(row) { - return row - } - row += 1 + func rowIsExpandedFolder(_ row: Int) -> Bool { + if let node = nodeForRow(row), outlineView.isItemExpanded(node) { + return true } - - row = 0 - while (row <= selectedRow) { - if rowHasAtLeastOneUnreadArticle(row) && !rowIsGroupItem(row) { + return false + } + + func shouldSkipRow(_ row: Int) -> Bool { + let skipExpandedFolders = UserDefaults.standard.bool(forKey: "JalkutRespectFolderExpansionOnNextUnread") + + // Skip group items, because they should never be selected. + // Skip expanded folders only if Jalkut's pref is enabled. + if rowIsGroupItem(row) || (skipExpandedFolders && rowIsExpandedFolder(row)) { + return true + } + return false + } + + func nextSelectableRowWithUnreadArticle(wrappingToTop wrapping: Bool = false) -> Int? { + let numberOfRows = outlineView.numberOfRows + let startRow = outlineView.selectedRow + 1 + + let orderedRows: [Int] + if startRow == numberOfRows { + // Last item is selected, so start at the beginning if we allow wrapping + orderedRows = wrapping ? Array(0.. Bool { - guard let _ = indexOfNextUnreadArticle() else { + func canGoToNextUnread(wrappingToTop wrapping: Bool = false) -> Bool { + guard let _ = indexOfNextUnreadArticle(wrappingToTop: wrapping) else { return false } return true } - func indexOfNextUnreadArticle() -> Int? { - return articles.rowOfNextUnreadArticle(tableView.selectedRow) + func indexOfNextUnreadArticle(wrappingToTop wrapping: Bool = false) -> Int? { + return articles.rowOfNextUnreadArticle(tableView.selectedRow, wrappingToTop: wrapping) } func focus() { diff --git a/Shared/Timeline/ArticleArray.swift b/Shared/Timeline/ArticleArray.swift index 391963424..104f06f8c 100644 --- a/Shared/Timeline/ArticleArray.swift +++ b/Shared/Timeline/ArticleArray.swift @@ -20,18 +20,21 @@ extension Array where Element == Article { return self[row] } - func rowOfNextUnreadArticle(_ selectedRow: Int) -> Int? { + func orderedRowIndexes(fromIndex startIndex: Int, wrappingToTop wrapping: Bool) -> [Int] { + if startIndex >= self.count { + // Wrap around to the top if specified + return wrapping ? Array(0..(startIndex..(0.. Int? { if isEmpty { return nil } - var rowIndex = selectedRow - while(true) { - - rowIndex = rowIndex + 1 - if rowIndex >= count { - break - } + for rowIndex in orderedRowIndexes(fromIndex: selectedRow + 1, wrappingToTop: wrapping) { let article = articleAtRow(rowIndex)! if !article.status.read { return rowIndex From d8f9cf59ddbd508e2786be1b7033d12a7cdac28a Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Sun, 1 Nov 2020 18:29:13 -0500 Subject: [PATCH 15/42] Change double-spaces after sentence ends to single space. Change pluralization for URL to URLs. --- Shared/ExtensionPoints/RedditFeedProvider-Extensions.swift | 2 +- Shared/ExtensionPoints/SendToMarsEditCommand.swift | 2 +- Shared/ExtensionPoints/SendToMicroBlogCommand.swift | 2 +- Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Shared/ExtensionPoints/RedditFeedProvider-Extensions.swift b/Shared/ExtensionPoints/RedditFeedProvider-Extensions.swift index 86981f547..0f4731e01 100644 --- a/Shared/ExtensionPoints/RedditFeedProvider-Extensions.swift +++ b/Shared/ExtensionPoints/RedditFeedProvider-Extensions.swift @@ -16,7 +16,7 @@ extension RedditFeedProvider: ExtensionPoint { static var title = NSLocalizedString("Reddit", comment: "Reddit") static var image = AppAssets.extensionPointReddit static var description: NSAttributedString = { - return RedditFeedProvider.makeAttrString("This extension enables you to subscribe to Reddit URL's as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.") + return RedditFeedProvider.makeAttrString("This extension enables you to subscribe to Reddit URLs as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.") }() var extensionPointID: ExtensionPointIdentifer { diff --git a/Shared/ExtensionPoints/SendToMarsEditCommand.swift b/Shared/ExtensionPoints/SendToMarsEditCommand.swift index 298ff5746..12f1eda2c 100644 --- a/Shared/ExtensionPoints/SendToMarsEditCommand.swift +++ b/Shared/ExtensionPoints/SendToMarsEditCommand.swift @@ -17,7 +17,7 @@ final class SendToMarsEditCommand: ExtensionPoint, SendToCommand { static var title = NSLocalizedString("MarsEdit", comment: "MarsEdit") static var image = AppAssets.extensionPointMarsEdit static var description: NSAttributedString = { - let attrString = SendToMarsEditCommand.makeAttrString("This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work.") + let attrString = SendToMarsEditCommand.makeAttrString("This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work.") let range = NSRange(location: 81, length: 8) attrString.beginEditing() attrString.addAttribute(NSAttributedString.Key.link, value: "https://red-sweater.com/marsedit/", range: range) diff --git a/Shared/ExtensionPoints/SendToMicroBlogCommand.swift b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift index c4ebeddfe..1c8bc031b 100644 --- a/Shared/ExtensionPoints/SendToMicroBlogCommand.swift +++ b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift @@ -19,7 +19,7 @@ final class SendToMicroBlogCommand: ExtensionPoint, SendToCommand { static var title: String = NSLocalizedString("Micro.blog", comment: "Micro.blog") static var image = AppAssets.extensionPointMicroblog static var description: NSAttributedString = { - let attrString = SendToMicroBlogCommand.makeAttrString("This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work.") + let attrString = SendToMicroBlogCommand.makeAttrString("This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work.") let range = NSRange(location: 81, length: 10) attrString.beginEditing() attrString.addAttribute(NSAttributedString.Key.link, value: "https://micro.blog", range: range) diff --git a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift index d38917b66..0e538066d 100644 --- a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift +++ b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift @@ -16,7 +16,7 @@ extension TwitterFeedProvider: ExtensionPoint { static var title = NSLocalizedString("Twitter", comment: "Twitter") static var image = AppAssets.extensionPointTwitter static var description: NSAttributedString = { - return TwitterFeedProvider.makeAttrString("This extension enables you to subscribe to Twitter URL's as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.") + return TwitterFeedProvider.makeAttrString("This extension enables you to subscribe to Twitter URLs as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.") }() var extensionPointID: ExtensionPointIdentifer { From aa73b242912b25f881dd2e61d73290fd276ab509 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 1 Nov 2020 20:31:53 -0600 Subject: [PATCH 16/42] Take order on the page into consideration when searching for the best feed identifier --- .../Account/FeedFinder/FeedFinder.swift | 8 +++---- .../Account/FeedFinder/FeedSpecifier.swift | 6 ++++- .../Account/FeedFinder/HTMLFeedFinder.swift | 18 +++++++------- .../Feedbin/FeedbinAccountDelegate.swift | 13 ++++------ .../ReaderAPI/ReaderAPIAccountDelegate.swift | 24 ------------------- 5 files changed, 23 insertions(+), 46 deletions(-) diff --git a/Account/Sources/Account/FeedFinder/FeedFinder.swift b/Account/Sources/Account/FeedFinder/FeedFinder.swift index d743b629a..e81c5160c 100644 --- a/Account/Sources/Account/FeedFinder/FeedFinder.swift +++ b/Account/Sources/Account/FeedFinder/FeedFinder.swift @@ -20,7 +20,7 @@ class FeedFinder { if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), urlComponents.host == "micro.blog" { urlComponents.path = "\(urlComponents.path).json" if let newURLString = urlComponents.url?.absoluteString { - let microblogFeedSpecifier = FeedSpecifier(title: nil, urlString: newURLString, source: .HTMLLink) + let microblogFeedSpecifier = FeedSpecifier(title: nil, urlString: newURLString, source: .HTMLLink, orderFound: 1) completion(.success(Set([microblogFeedSpecifier]))) } } else { @@ -45,7 +45,7 @@ class FeedFinder { } if FeedFinder.isFeed(data, url.absoluteString) { - let feedSpecifier = FeedSpecifier(title: nil, urlString: url.absoluteString, source: .UserEntered) + let feedSpecifier = FeedSpecifier(title: nil, urlString: url.absoluteString, source: .UserEntered, orderFound: 1) completion(.success(Set([feedSpecifier]))) return } @@ -120,11 +120,11 @@ private extension FeedFinder { // It’s also fairly common for /index.xml to work. if let url = URL(string: urlString) { let feedURL = url.appendingPathComponent("feed", isDirectory: true) - let wordpressFeedSpecifier = FeedSpecifier(title: nil, urlString: feedURL.absoluteString, source: .HTMLLink) + let wordpressFeedSpecifier = FeedSpecifier(title: nil, urlString: feedURL.absoluteString, source: .HTMLLink, orderFound: 1) feedSpecifiers.insert(wordpressFeedSpecifier) let indexXMLURL = url.appendingPathComponent("index.xml", isDirectory: false) - let indexXMLFeedSpecifier = FeedSpecifier(title: nil, urlString: indexXMLURL.absoluteString, source: .HTMLLink) + let indexXMLFeedSpecifier = FeedSpecifier(title: nil, urlString: indexXMLURL.absoluteString, source: .HTMLLink, orderFound: 1) feedSpecifiers.insert(indexXMLFeedSpecifier) } } diff --git a/Account/Sources/Account/FeedFinder/FeedSpecifier.swift b/Account/Sources/Account/FeedFinder/FeedSpecifier.swift index 5b48b1b98..2957b011f 100644 --- a/Account/Sources/Account/FeedFinder/FeedSpecifier.swift +++ b/Account/Sources/Account/FeedFinder/FeedSpecifier.swift @@ -21,6 +21,7 @@ struct FeedSpecifier: Hashable { public let title: String? public let urlString: String public let source: Source + public let orderFound: Int public var score: Int { return calculatedScore() } @@ -30,8 +31,9 @@ struct FeedSpecifier: Hashable { let mergedTitle = title ?? feedSpecifier.title let mergedSource = source.equalToOrBetterThan(feedSpecifier.source) ? source : feedSpecifier.source + let mergedOrderFound = orderFound >= feedSpecifier.orderFound ? orderFound : feedSpecifier.orderFound - return FeedSpecifier(title: mergedTitle, urlString: urlString, source: mergedSource) + return FeedSpecifier(title: mergedTitle, urlString: urlString, source: mergedSource, orderFound: mergedOrderFound) } public static func bestFeed(in feedSpecifiers: Set) -> FeedSpecifier? { @@ -69,6 +71,8 @@ private extension FeedSpecifier { score = score + 50 } + score = score - ((orderFound - 1) * 5) + if urlString.caseInsensitiveContains("comments") { score = score - 10 } diff --git a/Account/Sources/Account/FeedFinder/HTMLFeedFinder.swift b/Account/Sources/Account/FeedFinder/HTMLFeedFinder.swift index 1592afa66..7e776f6c0 100644 --- a/Account/Sources/Account/FeedFinder/HTMLFeedFinder.swift +++ b/Account/Sources/Account/FeedFinder/HTMLFeedFinder.swift @@ -21,22 +21,24 @@ class HTMLFeedFinder { init(parserData: ParserData) { let metadata = RSHTMLMetadataParser.htmlMetadata(with: parserData) - + var orderFound = 0 + for oneFeedLink in metadata.feedLinks { if let oneURLString = oneFeedLink.urlString?.normalizedURL { - let oneFeedSpecifier = FeedSpecifier(title: oneFeedLink.title, urlString: oneURLString, source: .HTMLHead) + orderFound = orderFound + 1 + let oneFeedSpecifier = FeedSpecifier(title: oneFeedLink.title, urlString: oneURLString, source: .HTMLHead, orderFound: orderFound) addFeedSpecifier(oneFeedSpecifier) } } let bodyLinks = RSHTMLLinkParser.htmlLinks(with: parserData) - for oneBodyLink in bodyLinks { - - if linkMightBeFeed(oneBodyLink), let normalizedURL = oneBodyLink.urlString?.normalizedURL { - let oneFeedSpecifier = FeedSpecifier(title: oneBodyLink.text, urlString: normalizedURL, source: .HTMLLink) - addFeedSpecifier(oneFeedSpecifier) - } + for oneBodyLink in bodyLinks { + if linkMightBeFeed(oneBodyLink), let normalizedURL = oneBodyLink.urlString?.normalizedURL { + orderFound = orderFound + 1 + let oneFeedSpecifier = FeedSpecifier(title: oneBodyLink.text, urlString: normalizedURL, source: .HTMLLink, orderFound: orderFound) + addFeedSpecifier(oneFeedSpecifier) } + } } } diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index d934b0162..4b51cc5c6 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -986,27 +986,22 @@ private extension FeedbinAccountDelegate { } func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result) -> Void) { + var orderFound = 0 let feedSpecifiers: [FeedSpecifier] = choices.map { choice in let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink - let specifier = FeedSpecifier(title: choice.name, urlString: choice.url, source: source) + orderFound = orderFound + 1 + let specifier = FeedSpecifier(title: choice.name, urlString: choice.url, source: source, orderFound: orderFound) return specifier } if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) { - if let bestSubscription = choices.filter({ bestSpecifier.urlString == $0.url }).first { - createWebFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion) - } else { - DispatchQueue.main.async { - completion(.failure(FeedbinAccountDelegateError.invalidParameter)) - } - } + createWebFeed(for: account, url: bestSpecifier.urlString, name: name, container: container, completion: completion) } else { DispatchQueue.main.async { completion(.failure(FeedbinAccountDelegateError.invalidParameter)) } } - } func createFeed( account: Account, subscription sub: FeedbinSubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 1602469a0..b2164481a 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -827,30 +827,6 @@ private extension ReaderAPIAccountDelegate { feed.folderRelationship = [folderExternalID: feedExternalID] } } - - func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [ReaderAPISubscriptionChoice], completion: @escaping (Result) -> Void) { - - let feedSpecifiers: [FeedSpecifier] = choices.map { choice in - let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink - let specifier = FeedSpecifier(title: choice.name, urlString: choice.url, source: source) - return specifier - } - - if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) { - if let bestSubscription = choices.filter({ bestSpecifier.urlString == $0.url }).first { - createWebFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion) - } else { - DispatchQueue.main.async { - completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) - } - } - } else { - DispatchQueue.main.async { - completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) - } - } - - } func createFeed( account: Account, subscription sub: ReaderAPISubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { From 38dbfa6f4a2091100518a35a5816538b17634c4b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 1 Nov 2020 20:46:56 -0600 Subject: [PATCH 17/42] Correct found order merge --- Account/Sources/Account/FeedFinder/FeedSpecifier.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Account/Sources/Account/FeedFinder/FeedSpecifier.swift b/Account/Sources/Account/FeedFinder/FeedSpecifier.swift index 2957b011f..4487065bb 100644 --- a/Account/Sources/Account/FeedFinder/FeedSpecifier.swift +++ b/Account/Sources/Account/FeedFinder/FeedSpecifier.swift @@ -31,7 +31,7 @@ struct FeedSpecifier: Hashable { let mergedTitle = title ?? feedSpecifier.title let mergedSource = source.equalToOrBetterThan(feedSpecifier.source) ? source : feedSpecifier.source - let mergedOrderFound = orderFound >= feedSpecifier.orderFound ? orderFound : feedSpecifier.orderFound + let mergedOrderFound = orderFound < feedSpecifier.orderFound ? orderFound : feedSpecifier.orderFound return FeedSpecifier(title: mergedTitle, urlString: urlString, source: mergedSource, orderFound: mergedOrderFound) } From 3b5dfb38ec17324443ee9d0f63614b7e64d1afdb Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Mon, 2 Nov 2020 11:08:54 +0800 Subject: [PATCH 18/42] Explainer text for accounts and extensions Explainer text will display when no account/extension is selected. --- .../AccountsPreferencesViewController.swift | 17 ++++++++++- ...ensionPointPreferencesViewController.swift | 30 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift index 2153f13d3..ab2393748 100644 --- a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift +++ b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift @@ -226,7 +226,7 @@ private extension AccountsPreferencesViewController { func showController(_ controller: NSViewController) { hideController() - + addChild(controller) controller.view.translatesAutoresizingMaskIntoConstraints = false detailView.addSubview(controller.view) @@ -239,6 +239,21 @@ private extension AccountsPreferencesViewController { children.removeAll() controller.view.removeFromSuperview() } + + if tableView.selectedRow == -1 { + var helpText = "" + if sortedAccounts.count == 0 { + helpText = NSLocalizedString("Add an account by clicking the + button.", comment: "Add Account Explainer") + } else { + helpText = NSLocalizedString("Select an account or add a new account by clicking the + button.", comment: "Add Account Explainer") + } + + let textHostingController = NSHostingController(rootView: Text(helpText).multilineTextAlignment(.center)) + addChild(textHostingController) + textHostingController.view.translatesAutoresizingMaskIntoConstraints = false + detailView.addSubview(textHostingController.view) + detailView.addFullSizeConstraints(forSubview: textHostingController.view) + } } } diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift index d07249fba..eca8dc36a 100644 --- a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift @@ -179,6 +179,21 @@ private extension ExtensionPointPreferencesViewController { func showDefaultView() { activeExtensionPoints = Array(ExtensionPointManager.shared.activeExtensionPoints.values).sorted(by: { $0.title < $1.title }) tableView.reloadData() + + if tableView.selectedRow == -1 { + var helpText = "" + if activeExtensionPoints.count == 0 { + helpText = NSLocalizedString("Add an extension point by clicking the + button.", comment: "Extension Explainer") + } else { + helpText = NSLocalizedString("Select an extension point or add a new extension point by clicking the + button.", comment: "Extension Explainer") + } + + let textHostingController = NSHostingController(rootView: Text(helpText).multilineTextAlignment(.center)) + addChild(textHostingController) + textHostingController.view.translatesAutoresizingMaskIntoConstraints = false + detailView.addSubview(textHostingController.view) + detailView.addFullSizeConstraints(forSubview: textHostingController.view) + } } func showController(_ controller: NSViewController) { @@ -195,6 +210,21 @@ private extension ExtensionPointPreferencesViewController { children.removeAll() controller.view.removeFromSuperview() } + + if tableView.selectedRow == -1 { + var helpText = "" + if activeExtensionPoints.count == 0 { + helpText = NSLocalizedString("Add an extension point by clicking the + button.", comment: "Extension Explainer") + } else { + helpText = NSLocalizedString("Select an extension point or add a new extension point by clicking the + button.", comment: "Extension Explainer") + } + + let textHostingController = NSHostingController(rootView: Text(helpText).multilineTextAlignment(.center)) + addChild(textHostingController) + textHostingController.view.translatesAutoresizingMaskIntoConstraints = false + detailView.addSubview(textHostingController.view) + detailView.addFullSizeConstraints(forSubview: textHostingController.view) + } } func enableOauth1(_ provider: OAuth1SwiftProvider.Type, extensionPointType: ExtensionPoint.Type) { From 574239568633e291817e770628a6382a55cf0f5a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 2 Nov 2020 13:58:21 -0600 Subject: [PATCH 19/42] Change FreshRSS URL feed so that it uses the URL keyboard and stops trying to change spellings --- iOS/Account/Account.storyboard | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Account/Account.storyboard b/iOS/Account/Account.storyboard index 316eabcfc..5d42f4d88 100644 --- a/iOS/Account/Account.storyboard +++ b/iOS/Account/Account.storyboard @@ -627,7 +627,7 @@ - + From 93cd9dd14b957e7ac3b691a6c03d03b9e75077c5 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 2 Nov 2020 14:35:48 -0600 Subject: [PATCH 20/42] Enable dropping onto empty accounts. Fixes #2024 --- .../MasterFeedViewController+Drop.swift | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController+Drop.swift b/iOS/MasterFeed/MasterFeedViewController+Drop.swift index 32fab4f7d..95e47c01b 100644 --- a/iOS/MasterFeed/MasterFeedViewController+Drop.swift +++ b/iOS/MasterFeed/MasterFeedViewController+Drop.swift @@ -18,13 +18,16 @@ extension MasterFeedViewController: UITableViewDropDelegate { } func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { - guard let destIndexPath = destinationIndexPath, - destIndexPath.section > 0, - tableView.hasActiveDrag, - let destIdentifier = dataSource.itemIdentifier(for: destIndexPath), - let destAccount = destIdentifier.account, - let destCell = tableView.cellForRow(at: destIndexPath) else { - return UITableViewDropProposal(operation: .forbidden) + guard let destIndexPath = destinationIndexPath, destIndexPath.section > 0, tableView.hasActiveDrag else { + return UITableViewDropProposal(operation: .forbidden) + } + + guard let destIdentifier = dataSource.itemIdentifier(for: destIndexPath) else { + return UITableViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath) + } + + guard let destAccount = destIdentifier.account, let destCell = tableView.cellForRow(at: destIndexPath) else { + return UITableViewDropProposal(operation: .forbidden) } // Validate account specific behaviors... @@ -90,7 +93,8 @@ extension MasterFeedViewController: UITableViewDropDelegate { if let containerID = destIdentifier?.containerID ?? destIdentifier?.parentContainerID { return AccountManager.shared.existingContainer(with: containerID) } else { - return nil + // If we got here, we are trying to drop on an empty section header. Go and find the Account for this section + return coordinator.rootNode.childAtIndex(destIndexPath.section)?.representedObject as? Account } }() From f391eb166952a0545f3dc7f20ab5fd6484a80c49 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 2 Nov 2020 14:53:00 -0600 Subject: [PATCH 21/42] Allow users to drop onto expanded folders. Fixes #2022 --- iOS/MasterFeed/MasterFeedViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 775968271..6630bea81 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -337,8 +337,8 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { return proposedDestinationIndexPath } - // If this is a folder and isn't expanded or doesn't have any entries, let the users drop on it - if destNode.representedObject is Folder && (destNode.numberOfChildNodes == 0 || !coordinator.isExpanded(destNode)) { + // If this is a folder, let the users drop on it + if destNode.representedObject is Folder { return proposedDestinationIndexPath } From 75ff9f92d8ace8be6f8ffd61d22c3b32e5ada9cf Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 2 Nov 2020 15:59:09 -0600 Subject: [PATCH 22/42] Update to the latest RSParser that has the Atom feed fix in it. Fixes #954 --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index eee47f843..8e5c04af6 100644 --- a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -69,8 +69,8 @@ "repositoryURL": "https://github.com/Ranchero-Software/RSParser.git", "state": { "branch": null, - "revision": "21d57ffb7ae744cf70bf6ddfb7ad8b7c102e05cf", - "version": "2.0.0-beta2" + "revision": "a4467cb6ab32d67fa8b09fcef8b234c7f96b7f9c", + "version": "2.0.0-beta3" } }, { From 5900b748be2e33902ee9be575cbce06b4e1e5d89 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 2 Nov 2020 16:27:13 -0600 Subject: [PATCH 23/42] Filter the NetNewsWire share menu item from the NetNewsWire share menu --- Mac/MainWindow/SharingServicePickerDelegate.swift | 3 ++- Multiplatform/macOS/Article/SharingServicePickerDelegate.swift | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Mac/MainWindow/SharingServicePickerDelegate.swift b/Mac/MainWindow/SharingServicePickerDelegate.swift index 1a5349a42..13c989460 100644 --- a/Mac/MainWindow/SharingServicePickerDelegate.swift +++ b/Mac/MainWindow/SharingServicePickerDelegate.swift @@ -18,7 +18,8 @@ import RSCore } func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] { - return proposedServices + SharingServicePickerDelegate.customSharingServices(for: items) + let filteredServices = proposedServices.filter { $0.menuItemTitle != "NetNewsWire" } + return filteredServices + SharingServicePickerDelegate.customSharingServices(for: items) } func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, delegateFor sharingService: NSSharingService) -> NSSharingServiceDelegate? { diff --git a/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift b/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift index 19ea8782c..bc6659530 100644 --- a/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift +++ b/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift @@ -20,7 +20,8 @@ import RSCore } func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] { - return proposedServices + SharingServicePickerDelegate.customSharingServices(for: items) + let filteredServices = proposedServices.filter { $0.menuItemTitle != "NetNewsWire" } + return filteredServices + SharingServicePickerDelegate.customSharingServices(for: items) } func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, delegateFor sharingService: NSSharingService) -> NSSharingServiceDelegate? { From caeebfc89ef66a78ee96849a2fcb5d913a031729 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 2 Nov 2020 19:33:56 -0600 Subject: [PATCH 24/42] Extend Article with PasteboardWriterOwner so that timeline copying works as it was intended to. Issue #963 --- Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift index a405a8be2..272c55e16 100644 --- a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift +++ b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift @@ -10,6 +10,11 @@ import AppKit import Articles import RSCore +extension Article: PasteboardWriterOwner { + public var pasteboardWriter: NSPasteboardWriting { + return ArticlePasteboardWriter(article: self) + } +} @objc final class ArticlePasteboardWriter: NSObject, NSPasteboardWriting { From 3580739b8d6e66162238d6f9d2a34eec3d3b2662 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 3 Nov 2020 09:41:34 +0800 Subject: [PATCH 25/42] Amends contsraints on HostingControllers This has the effect of pushing the explainer text up to the centre alignment of the tableviews. --- .../Accounts/AccountsPreferencesViewController.swift | 8 ++++++-- .../ExtensionPointPreferencesViewController.swift | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift index ab2393748..d28ff36e7 100644 --- a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift +++ b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift @@ -231,7 +231,6 @@ private extension AccountsPreferencesViewController { controller.view.translatesAutoresizingMaskIntoConstraints = false detailView.addSubview(controller.view) detailView.addFullSizeConstraints(forSubview: controller.view) - } func hideController() { @@ -252,7 +251,12 @@ private extension AccountsPreferencesViewController { addChild(textHostingController) textHostingController.view.translatesAutoresizingMaskIntoConstraints = false detailView.addSubview(textHostingController.view) - detailView.addFullSizeConstraints(forSubview: textHostingController.view) + detailView.addConstraints([ + NSLayoutConstraint(item: textHostingController.view, attribute: .top, relatedBy: .equal, toItem: detailView, attribute: .top, multiplier: 1, constant: 1), + NSLayoutConstraint(item: textHostingController.view, attribute: .bottom, relatedBy: .equal, toItem: detailView, attribute: .bottom, multiplier: 1, constant: -deleteButton.frame.height), + NSLayoutConstraint(item: textHostingController.view, attribute: .width, relatedBy: .equal, toItem: detailView, attribute: .width, multiplier: 1, constant: 1) + ]) + } } diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift index eca8dc36a..8250a185a 100644 --- a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift @@ -192,7 +192,11 @@ private extension ExtensionPointPreferencesViewController { addChild(textHostingController) textHostingController.view.translatesAutoresizingMaskIntoConstraints = false detailView.addSubview(textHostingController.view) - detailView.addFullSizeConstraints(forSubview: textHostingController.view) + detailView.addConstraints([ + NSLayoutConstraint(item: textHostingController.view, attribute: .top, relatedBy: .equal, toItem: detailView, attribute: .top, multiplier: 1, constant: 1), + NSLayoutConstraint(item: textHostingController.view, attribute: .bottom, relatedBy: .equal, toItem: detailView, attribute: .bottom, multiplier: 1, constant: -deleteButton.frame.height), + NSLayoutConstraint(item: textHostingController.view, attribute: .width, relatedBy: .equal, toItem: detailView, attribute: .width, multiplier: 1, constant: 1) + ]) } } From 0338a3bda400ab66d223716e0355b79f91a3a9b7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 2 Nov 2020 19:43:29 -0600 Subject: [PATCH 26/42] Remove deprecation warning --- Mac/MainWindow/Detail/DetailWebViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 28f85df91..6d2bdb2c9 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -64,7 +64,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate { // Wrap the webview in a box configured with the same background color that the web view uses let box = NSBox(frame: .zero) box.boxType = .custom - box.borderType = .noBorder + box.isTransparent = true box.titlePosition = .noTitle box.contentViewMargins = .zero box.fillColor = NSColor(named: "webviewBackgroundColor")! From 144bf06703d9feb5545a025a01d98e66bc7c0d45 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 3 Nov 2020 09:58:46 +0800 Subject: [PATCH 27/42] Fixes alignment and duplicate text for Extensions --- .../ExtensionPointPreferencesViewController.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift index 8250a185a..71012e938 100644 --- a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift @@ -188,6 +188,11 @@ private extension ExtensionPointPreferencesViewController { helpText = NSLocalizedString("Select an extension point or add a new extension point by clicking the + button.", comment: "Extension Explainer") } + if let controller = children.first { + children.removeAll() + controller.view.removeFromSuperview() + } + let textHostingController = NSHostingController(rootView: Text(helpText).multilineTextAlignment(.center)) addChild(textHostingController) textHostingController.view.translatesAutoresizingMaskIntoConstraints = false @@ -227,7 +232,11 @@ private extension ExtensionPointPreferencesViewController { addChild(textHostingController) textHostingController.view.translatesAutoresizingMaskIntoConstraints = false detailView.addSubview(textHostingController.view) - detailView.addFullSizeConstraints(forSubview: textHostingController.view) + detailView.addConstraints([ + NSLayoutConstraint(item: textHostingController.view, attribute: .top, relatedBy: .equal, toItem: detailView, attribute: .top, multiplier: 1, constant: 1), + NSLayoutConstraint(item: textHostingController.view, attribute: .bottom, relatedBy: .equal, toItem: detailView, attribute: .bottom, multiplier: 1, constant: -deleteButton.frame.height), + NSLayoutConstraint(item: textHostingController.view, attribute: .width, relatedBy: .equal, toItem: detailView, attribute: .width, multiplier: 1, constant: 1) + ]) } } From 3928d0a0255b69c5fe99bcce951d8dffc5ef4046 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 3 Nov 2020 13:02:46 +0800 Subject: [PATCH 28/42] Corrects Extension accessibility labels --- .../ExtensionPoints/EnableExtensionPointView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift index b7c707321..3cd63840d 100644 --- a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift +++ b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift @@ -48,7 +48,7 @@ struct EnableExtensionPointView: View { Text("Cancel") .frame(width: 80) }) - .accessibility(label: Text("Add Account")) + .accessibility(label: Text("Add Extension")) } if #available(OSX 11.0, *) { Button(action: { @@ -58,7 +58,7 @@ struct EnableExtensionPointView: View { Text("Continue") .frame(width: 80) }) - .help("Add Account") + .help("Add Extension") .keyboardShortcut(.defaultAction) } else { From e12bd28ff418b8a15a81f68579765d9576a4f6f8 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 3 Nov 2020 13:04:31 +0800 Subject: [PATCH 29/42] removes force unwrap this causes a crash on dev builds. --- Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift index 3cd63840d..cb058f83b 100644 --- a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift +++ b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift @@ -14,7 +14,7 @@ struct EnableExtensionPointView: View { weak var parent: NSHostingController? // required because presentationMode.dismiss() doesn't work weak var enabler: ExtensionPointPreferencesEnabler? - @State private var extensionPointTypeName = String(describing: Self.feedProviderExtensionPointTypes.first!) + @State private var extensionPointTypeName = String(describing: Self.feedProviderExtensionPointTypes.first) init(enabler: ExtensionPointPreferencesEnabler?) { self.enabler = enabler From c307b32c029702ef8d3de1140a66a26518a0fee8 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 3 Nov 2020 10:09:13 -0600 Subject: [PATCH 30/42] Remove unreachable default compiler warning --- Shared/AccountType+Helpers.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Shared/AccountType+Helpers.swift b/Shared/AccountType+Helpers.swift index ac40239bc..fe61b9043 100644 --- a/Shared/AccountType+Helpers.swift +++ b/Shared/AccountType+Helpers.swift @@ -52,8 +52,6 @@ extension AccountType { return NSLocalizedString("NewsBlur", comment: "Account name") case .theOldReader: return NSLocalizedString("The Old Reader", comment: "Account name") - default: - return "" } } From 948998382d60133724141cd478e825e9080743a2 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 3 Nov 2020 17:08:56 -0600 Subject: [PATCH 31/42] 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") + } + } + +} From 840668452c4abf5b94dd7205781a29ed020ffae2 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 4 Nov 2020 10:35:53 +0800 Subject: [PATCH 32/42] Add Extension/Account Tweaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Tidies up extension language • Adds images to account / extension panels • Click on images will display add account / add extension sheets --- .../AccountsPreferencesViewController.swift | 8 ++-- .../Accounts/AddAccountHelpView.swift | 48 +++++++++++++++++++ .../Accounts/AddAccountsView.swift | 12 ++++- .../EnableExtensionPointHelpView.swift | 48 +++++++++++++++++++ .../EnableExtensionPointView.swift | 7 ++- ...ensionPointPreferencesViewController.swift | 19 ++++---- NetNewsWire.xcodeproj/project.pbxproj | 12 +++++ 7 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 Mac/Preferences/Accounts/AddAccountHelpView.swift create mode 100644 Mac/Preferences/ExtensionPoints/EnableExtensionPointHelpView.swift diff --git a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift index d28ff36e7..5d026882a 100644 --- a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift +++ b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift @@ -45,10 +45,7 @@ final class AccountsPreferencesViewController: NSViewController { rTable.size.width = tableView.superview!.frame.size.width tableView.frame = rTable - // Set initial row selection - if sortedAccounts.count > 0 { - tableView.selectRow(0) - } + hideController() } @IBAction func addAccount(_ sender: Any) { @@ -247,7 +244,8 @@ private extension AccountsPreferencesViewController { helpText = NSLocalizedString("Select an account or add a new account by clicking the + button.", comment: "Add Account Explainer") } - let textHostingController = NSHostingController(rootView: Text(helpText).multilineTextAlignment(.center)) + let textHostingController = NSHostingController(rootView: + AddAccountHelpView(delegate: addAccountDelegate, helpText: helpText)) addChild(textHostingController) textHostingController.view.translatesAutoresizingMaskIntoConstraints = false detailView.addSubview(textHostingController.view) diff --git a/Mac/Preferences/Accounts/AddAccountHelpView.swift b/Mac/Preferences/Accounts/AddAccountHelpView.swift new file mode 100644 index 000000000..fa7675f63 --- /dev/null +++ b/Mac/Preferences/Accounts/AddAccountHelpView.swift @@ -0,0 +1,48 @@ +// +// AddAccountHelpView.swift +// NetNewsWire +// +// Created by Stuart Breckenridge on 4/11/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import Account + +struct AddAccountHelpView: View { + + let accountTypes: [AccountType] = AddAccountSections.allOrdered.sectionContent + var delegate: AccountsPreferencesAddAccountDelegate? + var helpText: String + @State private var hoveringId: String? = nil + + var body: some View { + VStack { + HStack { + ForEach(accountTypes, id: \.self) { account in + account.image() + .resizable() + .frame(width: 20, height: 20, alignment: .center) + .onTapGesture { + delegate?.presentSheetForAccount(account) + hoveringId = nil + } + .onHover(perform: { hovering in + if hovering { + hoveringId = account.localizedAccountName() + } else { + hoveringId = nil + } + }) + .scaleEffect(hoveringId == account.localizedAccountName() ? 1.2 : 1) + .shadow(radius: hoveringId == account.localizedAccountName() ? 0.8 : 0) + } + } + + Text(helpText) + .multilineTextAlignment(.center) + .padding(.top, 8) + + } + } +} diff --git a/Mac/Preferences/Accounts/AddAccountsView.swift b/Mac/Preferences/Accounts/AddAccountsView.swift index 61ccaf3f1..e79828846 100644 --- a/Mac/Preferences/Accounts/AddAccountsView.swift +++ b/Mac/Preferences/Accounts/AddAccountsView.swift @@ -9,11 +9,12 @@ import SwiftUI import Account -private enum AddAccountSections: Int, CaseIterable { +enum AddAccountSections: Int, CaseIterable { case local = 0 case icloud case web case selfhosted + case allOrdered var sectionHeader: String { switch self { @@ -25,6 +26,8 @@ private enum AddAccountSections: Int, CaseIterable { return NSLocalizedString("Web", comment: "Web Account") case .selfhosted: return NSLocalizedString("Self-hosted", comment: "Self hosted Account") + case .allOrdered: + return "" } } @@ -38,6 +41,8 @@ private enum AddAccountSections: Int, CaseIterable { return NSLocalizedString("Web accounts sync your subscriptions across all your devices.", comment: "Web Account") case .selfhosted: return NSLocalizedString("Self-hosted accounts sync your subscriptions across all your devices.", comment: "Self hosted Account") + case .allOrdered: + return "" } } @@ -51,6 +56,11 @@ private enum AddAccountSections: Int, CaseIterable { return [.bazQux, .feedbin, .feedly, .feedWrangler, .inoreader, .newsBlur, .theOldReader] case .selfhosted: return [.freshRSS] + case .allOrdered: + return AddAccountSections.local.sectionContent + + AddAccountSections.icloud.sectionContent + + AddAccountSections.web.sectionContent + + AddAccountSections.selfhosted.sectionContent } } } diff --git a/Mac/Preferences/ExtensionPoints/EnableExtensionPointHelpView.swift b/Mac/Preferences/ExtensionPoints/EnableExtensionPointHelpView.swift new file mode 100644 index 000000000..dfebd08cb --- /dev/null +++ b/Mac/Preferences/ExtensionPoints/EnableExtensionPointHelpView.swift @@ -0,0 +1,48 @@ +// +// EnableExtensionPointHelpView.swift +// NetNewsWire +// +// Created by Stuart Breckenridge on 4/11/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import AppKit +import SwiftUI +import RSCore + +struct EnableExtensionPointHelpView: View { + let imageLiterals = ["extensionPointMarsEdit", "extensionPointMicroblog", "extensionPointReddit", "extensionPointTwitter"] + var helpText: String + weak var preferencesController: ExtensionPointPreferencesViewController? + + @State private var hoveringId: String? + + var body: some View { + VStack { + HStack { + ForEach(imageLiterals, id: \.self) { name in + Image(name) + .resizable() + .frame(width: 20, height: 20, alignment: .center) + .onTapGesture { + preferencesController?.enableExtensionPoints(self) + hoveringId = nil + } + .onHover(perform: { hovering in + if hovering { + hoveringId = name + } else { + hoveringId = nil + } + }) + .scaleEffect(hoveringId == name ? 1.2 : 1) + .shadow(radius: hoveringId == name ? 0.8 : 0) + } + } + + Text(helpText) + .multilineTextAlignment(.center) + .padding(.top, 8) + } + } +} diff --git a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift index cb058f83b..a61649e86 100644 --- a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift +++ b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift @@ -107,7 +107,7 @@ struct EnableExtensionPointView: View { .pickerStyle(RadioGroupPickerStyle()) .offset(x: 7.5, y: 0) - Text("An extension point that makes websites appear to provide RSS feeds for their content.") + Text("An extension that makes websites appear to provide RSS feeds for their content.") .foregroundColor(.gray) .font(.caption) .padding(.horizontal) @@ -144,7 +144,7 @@ struct EnableExtensionPointView: View { .pickerStyle(RadioGroupPickerStyle()) .offset(x: 7.5, y: 0) - Text("An extension point that enables a share menu item that passes article data to a third-party application.") + Text("An extension that enables a share menu item that passes article data to a third-party application.") .foregroundColor(.gray) .font(.caption) .padding(.horizontal) @@ -170,3 +170,6 @@ struct EnableExtensionPointView: View { fatalError() } } + + + diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift index 71012e938..892a65a69 100644 --- a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift @@ -41,10 +41,7 @@ final class ExtensionPointPreferencesViewController: NSViewController { showDefaultView() - // Set initial row selection - if activeExtensionPoints.count > 0 { - tableView.selectRow(0) - } + } @IBAction func enableExtensionPoints(_ sender: Any) { @@ -183,9 +180,9 @@ private extension ExtensionPointPreferencesViewController { if tableView.selectedRow == -1 { var helpText = "" if activeExtensionPoints.count == 0 { - helpText = NSLocalizedString("Add an extension point by clicking the + button.", comment: "Extension Explainer") + helpText = NSLocalizedString("Add an extension by clicking the + button.", comment: "Extension Explainer") } else { - helpText = NSLocalizedString("Select an extension point or add a new extension point by clicking the + button.", comment: "Extension Explainer") + helpText = NSLocalizedString("Select an extension or add a new extension point by clicking the + button.", comment: "Extension Explainer") } if let controller = children.first { @@ -193,7 +190,7 @@ private extension ExtensionPointPreferencesViewController { controller.view.removeFromSuperview() } - let textHostingController = NSHostingController(rootView: Text(helpText).multilineTextAlignment(.center)) + let textHostingController = NSHostingController(rootView: EnableExtensionPointHelpView(helpText: helpText, preferencesController: self)) addChild(textHostingController) textHostingController.view.translatesAutoresizingMaskIntoConstraints = false detailView.addSubview(textHostingController.view) @@ -223,12 +220,12 @@ private extension ExtensionPointPreferencesViewController { if tableView.selectedRow == -1 { var helpText = "" if activeExtensionPoints.count == 0 { - helpText = NSLocalizedString("Add an extension point by clicking the + button.", comment: "Extension Explainer") + helpText = NSLocalizedString("Add an extension by clicking the + button.", comment: "Extension Explainer") } else { - helpText = NSLocalizedString("Select an extension point or add a new extension point by clicking the + button.", comment: "Extension Explainer") + helpText = NSLocalizedString("Select an extension or add a new extension point by clicking the + button.", comment: "Extension Explainer") } - let textHostingController = NSHostingController(rootView: Text(helpText).multilineTextAlignment(.center)) + let textHostingController = NSHostingController(rootView: EnableExtensionPointHelpView(helpText: helpText, preferencesController: self)) addChild(textHostingController) textHostingController.view.translatesAutoresizingMaskIntoConstraints = false detailView.addSubview(textHostingController.view) @@ -297,3 +294,5 @@ private extension ExtensionPointPreferencesViewController { } } + + diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 6ce989f16..119863d69 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -9,6 +9,10 @@ /* Begin PBXBuildFile section */ 1704053424E5985A00A00787 /* SceneNavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1704053324E5985A00A00787 /* SceneNavigationModel.swift */; }; 1704053524E5985A00A00787 /* SceneNavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1704053324E5985A00A00787 /* SceneNavigationModel.swift */; }; + 1710B9132552354E00679C0D /* AddAccountHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B9122552354E00679C0D /* AddAccountHelpView.swift */; }; + 1710B9142552354E00679C0D /* AddAccountHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B9122552354E00679C0D /* AddAccountHelpView.swift */; }; + 1710B929255246F900679C0D /* EnableExtensionPointHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */; }; + 1710B92A255246F900679C0D /* EnableExtensionPointHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */; }; 1717535624BADF33004498C6 /* GeneralPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */; }; 171BCB8C24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */; }; 171BCB8D24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */; }; @@ -1434,6 +1438,8 @@ /* Begin PBXFileReference section */ 1704053324E5985A00A00787 /* SceneNavigationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneNavigationModel.swift; sourceTree = ""; }; + 1710B9122552354E00679C0D /* AddAccountHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountHelpView.swift; sourceTree = ""; }; + 1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointHelpView.swift; sourceTree = ""; }; 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesModel.swift; sourceTree = ""; }; 171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixAccountCredentialView.swift; sourceTree = ""; }; 172199C824AB228900A31D04 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; @@ -2304,6 +2310,7 @@ isa = PBXGroup; children = ( 5183CFAE254C78C8006B83A5 /* EnableExtensionPointView.swift */, + 1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */, 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */, 515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */, 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */, @@ -3328,6 +3335,7 @@ isa = PBXGroup; children = ( 178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */, + 1710B9122552354E00679C0D /* AddAccountHelpView.swift */, 84C9FC7222629E1200D921D6 /* AccountsPreferencesViewController.swift */, 84C9FC7422629E1200D921D6 /* AccountsDetail.xib */, 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */, @@ -4794,6 +4802,7 @@ 65ED3FBD235DEF6C0081F399 /* AppDefaults.swift in Sources */, 65ED3FBE235DEF6C0081F399 /* Account+Scriptability.swift in Sources */, 65ED3FBF235DEF6C0081F399 /* NothingInspectorViewController.swift in Sources */, + 1710B92A255246F900679C0D /* EnableExtensionPointHelpView.swift in Sources */, 65ED3FC0235DEF6C0081F399 /* AppNotifications.swift in Sources */, 65ED3FC1235DEF6C0081F399 /* TimelineKeyboardDelegate.swift in Sources */, 65ED3FC2235DEF6C0081F399 /* Browser.swift in Sources */, @@ -4863,6 +4872,7 @@ 65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */, 65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */, 65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */, + 1710B9142552354E00679C0D /* AddAccountHelpView.swift in Sources */, 65ED3FFA235DEF6C0081F399 /* WebFeedInspectorViewController.swift in Sources */, 65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */, 65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */, @@ -5179,6 +5189,7 @@ D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */, 8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, + 1710B929255246F900679C0D /* EnableExtensionPointHelpView.swift in Sources */, 515A50E6243D07A90089E588 /* ExtensionPointManager.swift in Sources */, 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 515A5177243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */, @@ -5234,6 +5245,7 @@ 511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */, 84C9FC7722629E1200D921D6 /* AdvancedPreferencesViewController.swift in Sources */, 849EE72120391F560082A1EA /* SharingServicePickerDelegate.swift in Sources */, + 1710B9132552354E00679C0D /* AddAccountHelpView.swift in Sources */, 5108F6B62375E612001ABC45 /* CacheCleaner.swift in Sources */, 849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */, 849EE70F203919360082A1EA /* AppAssets.swift in Sources */, From c558731de87ba99e6d0490d24daf6878091bd421 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 4 Nov 2020 12:30:29 -0600 Subject: [PATCH 33/42] Reduce size of article left and right padding --- Mac/MainWindow/Detail/styleSheet.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mac/MainWindow/Detail/styleSheet.css b/Mac/MainWindow/Detail/styleSheet.css index 64625a4d7..1dba70762 100644 --- a/Mac/MainWindow/Detail/styleSheet.css +++ b/Mac/MainWindow/Detail/styleSheet.css @@ -1,8 +1,8 @@ body { margin-top: 20px; margin-bottom: 64px; - padding-left: 64px; - padding-right: 64px; + padding-left: 48px; + padding-right: 48px; font-family: -apple-system; font-size: [[font-size]]px; } From 6f09a6682dc4c29ec9082482d3ba02b706638fe6 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 4 Nov 2020 16:01:33 -0600 Subject: [PATCH 34/42] Update share extension image resources to match the new Big Sur icon --- .../shareExtension.imageset/Contents.json | 4 ++-- .../Icon-MacOS-32x32@1x.png.png | Bin 0 -> 3451 bytes .../Icon-MacOS-32x32@2x.png.png | Bin 0 -> 5981 bytes .../shareExtension.imageset/shareExtensionx1.png | Bin 2714 -> 0 bytes .../shareExtension.imageset/shareExtensionx2.png | Bin 7276 -> 0 bytes 5 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 Mac/Resources/Assets.xcassets/shareExtension.imageset/Icon-MacOS-32x32@1x.png.png create mode 100644 Mac/Resources/Assets.xcassets/shareExtension.imageset/Icon-MacOS-32x32@2x.png.png delete mode 100644 Mac/Resources/Assets.xcassets/shareExtension.imageset/shareExtensionx1.png delete mode 100644 Mac/Resources/Assets.xcassets/shareExtension.imageset/shareExtensionx2.png diff --git a/Mac/Resources/Assets.xcassets/shareExtension.imageset/Contents.json b/Mac/Resources/Assets.xcassets/shareExtension.imageset/Contents.json index c61a427dc..c3628e3ae 100644 --- a/Mac/Resources/Assets.xcassets/shareExtension.imageset/Contents.json +++ b/Mac/Resources/Assets.xcassets/shareExtension.imageset/Contents.json @@ -1,12 +1,12 @@ { "images" : [ { - "filename" : "shareExtensionx1.png", + "filename" : "Icon-MacOS-32x32@1x.png.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "shareExtensionx2.png", + "filename" : "Icon-MacOS-32x32@2x.png.png", "idiom" : "universal", "scale" : "2x" }, diff --git a/Mac/Resources/Assets.xcassets/shareExtension.imageset/Icon-MacOS-32x32@1x.png.png b/Mac/Resources/Assets.xcassets/shareExtension.imageset/Icon-MacOS-32x32@1x.png.png new file mode 100644 index 0000000000000000000000000000000000000000..084985da6c4644f1fbb6b2361ce0717d877fc382 GIT binary patch literal 3451 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 literal 0 HcmV?d00001 diff --git a/Mac/Resources/Assets.xcassets/shareExtension.imageset/Icon-MacOS-32x32@2x.png.png b/Mac/Resources/Assets.xcassets/shareExtension.imageset/Icon-MacOS-32x32@2x.png.png new file mode 100644 index 0000000000000000000000000000000000000000..0c1d6e3b420b79cbd32ecbbfc86c419516f033d6 GIT binary patch literal 5981 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 F{tep6#vlLy diff --git a/Mac/Resources/Assets.xcassets/shareExtension.imageset/shareExtensionx2.png b/Mac/Resources/Assets.xcassets/shareExtension.imageset/shareExtensionx2.png deleted file mode 100644 index c5debdda8fec5675f254158378a5697671a6bfbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7276 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 Date: Wed, 4 Nov 2020 17:12:20 -0600 Subject: [PATCH 35/42] Correct the SwiftUI deployment target --- xcconfig/NetNewsWire_multiplatform_macOSapp_target.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfig/NetNewsWire_multiplatform_macOSapp_target.xcconfig b/xcconfig/NetNewsWire_multiplatform_macOSapp_target.xcconfig index 8f1fc3a62..33cdf6669 100644 --- a/xcconfig/NetNewsWire_multiplatform_macOSapp_target.xcconfig +++ b/xcconfig/NetNewsWire_multiplatform_macOSapp_target.xcconfig @@ -41,6 +41,6 @@ PRODUCT_BUNDLE_IDENTIFIER = $(ORGANIZATION_IDENTIFIER).NetNewsWire-Evergreen PRODUCT_NAME = NetNewsWire // Override NetNewsWire_project.xcconfig until we are ready to only target 10.16 -MACOSX_DEPLOYMENT_TARGET = 10.16 +MACOSX_DEPLOYMENT_TARGET = 11.0 SWIFT_SWIFT3_OBJC_INFERENCE = Off SWIFT_VERSION = 5.3 From 89d0765f9bfa9003ef257659aa74d0d521a6d99b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 4 Nov 2020 17:13:02 -0600 Subject: [PATCH 36/42] Add article text size to the app defaults --- Multiplatform/Shared/AppDefaults.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Multiplatform/Shared/AppDefaults.swift b/Multiplatform/Shared/AppDefaults.swift index 76c682176..7ad949455 100644 --- a/Multiplatform/Shared/AppDefaults.swift +++ b/Multiplatform/Shared/AppDefaults.swift @@ -72,6 +72,7 @@ final class AppDefaults: ObservableObject { static let confirmMarkAllAsRead = "confirmMarkAllAsRead" // macOS Defaults + static let articleTextSize = "articleTextSize" static let openInBrowserInBackground = "openInBrowserInBackground" static let defaultBrowserID = "defaultBrowserID" static let checkForUpdatesAutomatically = "checkForUpdatesAutomatically" @@ -231,6 +232,16 @@ final class AppDefaults: ObservableObject { @AppStorage(wrappedValue: false, Key.articleFullscreenEnabled, store: store) var articleFullscreenEnabled: Bool + @AppStorage(wrappedValue: 3, Key.articleTextSize, store: store) var articleTextSizeTag: Int { + didSet { + objectWillChange.send() + } + } + + var articleTextSize: ArticleTextSize { + ArticleTextSize(rawValue: articleTextSizeTag) ?? ArticleTextSize.large + } + // MARK: Refresh var lastRefresh: Date? { set { From 77743f734de2c8aa3cab0c00027553e3a04699c7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 4 Nov 2020 17:13:58 -0600 Subject: [PATCH 37/42] Fix undo manager compile error --- Multiplatform/Shared/Sidebar/SidebarModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Multiplatform/Shared/Sidebar/SidebarModel.swift b/Multiplatform/Shared/Sidebar/SidebarModel.swift index 2ffd3d8ea..a7f87b52e 100644 --- a/Multiplatform/Shared/Sidebar/SidebarModel.swift +++ b/Multiplatform/Shared/Sidebar/SidebarModel.swift @@ -242,7 +242,7 @@ private extension SidebarModel { /// - Parameter articles: An array of `Article`s. /// - Warning: An `UndoManager` is created here as the `Environment`'s undo manager appears to be `nil`. func markAllAsRead(_ articles: [Article]) { - guard let undoManager = undoManager ?? UndoManager(), + guard let undoManager = undoManager, let markAsReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { return } From 1a3d2987f0d19e79c14e90d0c95132f9fee0f9fd Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 5 Nov 2020 11:36:20 -0600 Subject: [PATCH 38/42] Back date the Feedbin since parameter to pick up updates done within 24 hours of the original post. Fixes #2549 --- .../Account/Feedbin/FeedbinAPICaller.swift | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Account/Sources/Account/Feedbin/FeedbinAPICaller.swift b/Account/Sources/Account/Feedbin/FeedbinAPICaller.swift index f5bd56477..8ff2028ae 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAPICaller.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAPICaller.swift @@ -34,6 +34,7 @@ final class FeedbinAPICaller: NSObject { private let feedbinBaseURL = URL(string: "https://api.feedbin.com/v2/")! private var transport: Transport! private var suspended = false + private var lastBackdateStartTime: Date? var credentials: Credentials? weak var accountMetadata: AccountMetadata? @@ -486,10 +487,26 @@ final class FeedbinAPICaller: NSObject { } func retrieveEntries(completion: @escaping (Result<([FeedbinEntry]?, String?, Date?, Int?), Error>) -> Void) { - + + // If this is an initial sync, go and grab the previous 3 months of entries. If not, use the last + // article fetch to only get the articles **published** since the last article fetch. + // + // We do a backdate fetch every launch or every 24 hours. This will help with + // getting **updated** articles that normally wouldn't be found with a regular fetch. + // https://github.com/Ranchero-Software/NetNewsWire/issues/2549#issuecomment-722341356 let since: Date = { if let lastArticleFetch = accountMetadata?.lastArticleFetchStartTime { - return lastArticleFetch + if let lastBackdateStartTime = lastBackdateStartTime { + if lastBackdateStartTime.byAdding(days: 1) < lastArticleFetch { + self.lastBackdateStartTime = lastArticleFetch + return lastArticleFetch.bySubtracting(days: 1) + } else { + return lastArticleFetch + } + } else { + self.lastBackdateStartTime = lastArticleFetch + return lastArticleFetch.bySubtracting(days: 1) + } } else { return Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date() } From ba5a06c108e993ae5549fec39d39c691ae68efd3 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 6 Nov 2020 19:07:28 +0800 Subject: [PATCH 39/42] Account / Explainers Fixes #2553 Fixes #2554 Introduces Mail-inspired add account sheets --- .../Accounts/AccountsAddCloudKit.xib | 78 ++++----- Mac/Preferences/Accounts/AccountsAddLocal.xib | 122 ++++++------- .../AccountsAddLocalWindowController.swift | 2 +- .../Accounts/AccountsFeedWrangler.xib | 155 ++++++++++------ ...AccountsFeedWranglerWindowController.swift | 13 ++ Mac/Preferences/Accounts/AccountsFeedbin.xib | 160 +++++++++++------ .../AccountsFeedbinWindowController.swift | 14 +- Mac/Preferences/Accounts/AccountsNewsBlur.xib | 154 ++++++++++------ .../AccountsNewsBlurWindowController.swift | 13 ++ .../Accounts/AccountsReaderAPI.xib | 165 +++++++++++------- .../AccountsReaderAPIWindowController.swift | 29 ++- .../Accounts/AddAccountHelpView.swift | 16 +- .../Accounts/AddAccountsView.swift | 2 - .../EnableExtensionPointHelpView.swift | 27 ++- .../EnableExtensionPointView.swift | 23 ++- ...ensionPointPreferencesViewController.swift | 18 +- 16 files changed, 630 insertions(+), 361 deletions(-) diff --git a/Mac/Preferences/Accounts/AccountsAddCloudKit.xib b/Mac/Preferences/Accounts/AccountsAddCloudKit.xib index b6dc5afd1..2e660df44 100644 --- a/Mac/Preferences/Accounts/AccountsAddCloudKit.xib +++ b/Mac/Preferences/Accounts/AccountsAddCloudKit.xib @@ -17,40 +17,14 @@ - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - + + - - + + + + + + + + + + + + + + + + + - + + + - - - + + + + + + + - + diff --git a/Mac/Preferences/Accounts/AccountsAddLocal.xib b/Mac/Preferences/Accounts/AccountsAddLocal.xib index 315c287f2..265de3209 100644 --- a/Mac/Preferences/Accounts/AccountsAddLocal.xib +++ b/Mac/Preferences/Accounts/AccountsAddLocal.xib @@ -20,70 +20,15 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + + + + + + + + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + diff --git a/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift b/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift index 753fc4e75..8aedbccd7 100644 --- a/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift @@ -23,7 +23,7 @@ class AccountsAddLocalWindowController: NSWindowController { override func windowDidLoad() { super.windowDidLoad() - localAccountNameTextField.stringValue = Account.defaultLocalAccountName + localAccountNameTextField.stringValue = NSLocalizedString("Create a local account on your Mac.", comment: "Account Local") } // MARK: API diff --git a/Mac/Preferences/Accounts/AccountsFeedWrangler.xib b/Mac/Preferences/Accounts/AccountsFeedWrangler.xib index d28eadf16..4c7c0b4ff 100644 --- a/Mac/Preferences/Accounts/AccountsFeedWrangler.xib +++ b/Mac/Preferences/Accounts/AccountsFeedWrangler.xib @@ -3,15 +3,19 @@ + - + + + + @@ -28,40 +32,13 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + - @@ -70,7 +47,7 @@ - + @@ -80,11 +57,11 @@ - + - + @@ -93,7 +70,7 @@ - + @@ -103,11 +80,11 @@ - + - + @@ -117,17 +94,6 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + - - + + + + @@ -182,5 +226,8 @@ Gw + + + diff --git a/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift index 5b84ce7af..bab79f8de 100644 --- a/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift @@ -12,6 +12,10 @@ import RSWeb import Secrets class AccountsFeedWranglerWindowController: NSWindowController { + + @IBOutlet weak var signInTextField: NSTextField! + @IBOutlet weak var noAccountTextField: NSTextField! + @IBOutlet weak var createNewAccountButton: NSButton! @IBOutlet weak var progressIndicator: NSProgressIndicator! @IBOutlet weak var usernameTextField: NSTextField! @IBOutlet weak var passwordTextField: NSSecureTextField! @@ -30,8 +34,12 @@ class AccountsFeedWranglerWindowController: NSWindowController { if let account = account, let credentials = try? account.retrieveCredentials(type: .basic) { usernameTextField.stringValue = credentials.username actionButton.title = NSLocalizedString("Update", comment: "Update") + signInTextField.stringValue = NSLocalizedString("Update your Feed Wrangler account credentials.", comment: "SignIn") + noAccountTextField.isHidden = true + createNewAccountButton.isHidden = true } else { actionButton.title = NSLocalizedString("Create", comment: "Create") + signInTextField.stringValue = NSLocalizedString("Sign in to your Feed Wrangler account.", comment: "SignIn") } } @@ -113,4 +121,9 @@ class AccountsFeedWranglerWindowController: NSWindowController { } } } + + @IBAction func createAccountWithProvider(_ sender: Any) { + NSWorkspace.shared.open(URL(string: "https://feedwrangler.net/users/new")!) + } + } diff --git a/Mac/Preferences/Accounts/AccountsFeedbin.xib b/Mac/Preferences/Accounts/AccountsFeedbin.xib index 8edd2ce12..f5f01ae92 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbin.xib +++ b/Mac/Preferences/Accounts/AccountsFeedbin.xib @@ -3,15 +3,19 @@ + - + + + + @@ -28,43 +32,13 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + + - @@ -73,7 +47,7 @@ - + @@ -83,11 +57,11 @@ - + - + @@ -96,7 +70,7 @@ - + @@ -106,11 +80,11 @@ - + - + @@ -120,17 +94,6 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + + + + + + @@ -185,5 +226,8 @@ Gw + + + diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift index b022e75b5..56f0e8d42 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift @@ -13,6 +13,9 @@ import Secrets class AccountsFeedbinWindowController: NSWindowController { + @IBOutlet weak var signInTextField: NSTextField! + @IBOutlet weak var noAccountTextField: NSTextField! + @IBOutlet weak var createNewAccountButton: NSButton! @IBOutlet weak var progressIndicator: NSProgressIndicator! @IBOutlet weak var usernameTextField: NSTextField! @IBOutlet weak var passwordTextField: NSSecureTextField! @@ -31,8 +34,12 @@ class AccountsFeedbinWindowController: NSWindowController { if let account = account, let credentials = try? account.retrieveCredentials(type: .basic) { usernameTextField.stringValue = credentials.username actionButton.title = NSLocalizedString("Update", comment: "Update") + signInTextField.stringValue = NSLocalizedString("Update your Feedbin account credentials.", comment: "SignIn") + noAccountTextField.isHidden = true + createNewAccountButton.isHidden = true } else { - actionButton.title = NSLocalizedString("Add Account", comment: "Add Account") + actionButton.title = NSLocalizedString("Create", comment: "Add Account") + signInTextField.stringValue = NSLocalizedString("Sign in to your Feedbin account.", comment: "SignIn") } } @@ -117,5 +124,10 @@ class AccountsFeedbinWindowController: NSWindowController { } } + + @IBAction func createAccountWithProvider(_ sender: Any) { + NSWorkspace.shared.open(URL(string: "https://feedbin.com/signup")!) + } + } diff --git a/Mac/Preferences/Accounts/AccountsNewsBlur.xib b/Mac/Preferences/Accounts/AccountsNewsBlur.xib index 798f154a1..b6133daa2 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlur.xib +++ b/Mac/Preferences/Accounts/AccountsNewsBlur.xib @@ -3,15 +3,19 @@ + - + + + + @@ -28,40 +32,13 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + - @@ -70,7 +47,7 @@ - + @@ -80,11 +57,11 @@ - + - + @@ -93,7 +70,7 @@ - + @@ -103,11 +80,11 @@ - + - + @@ -117,17 +94,6 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + - - + + + + + + @@ -182,5 +225,8 @@ Gw + + + diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift index e3fc527b2..4ae73333d 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift @@ -12,6 +12,10 @@ import RSWeb import Secrets class AccountsNewsBlurWindowController: NSWindowController { + + @IBOutlet weak var signInTextField: NSTextField! + @IBOutlet weak var noAccountTextField: NSTextField! + @IBOutlet weak var createNewAccountButton: NSButton! @IBOutlet weak var progressIndicator: NSProgressIndicator! @IBOutlet weak var usernameTextField: NSTextField! @IBOutlet weak var passwordTextField: NSSecureTextField! @@ -30,8 +34,12 @@ class AccountsNewsBlurWindowController: NSWindowController { if let account = account, let credentials = try? account.retrieveCredentials(type: .newsBlurBasic) { usernameTextField.stringValue = credentials.username actionButton.title = NSLocalizedString("Update", comment: "Update") + signInTextField.stringValue = NSLocalizedString("Update your NewsBlur account credentials.", comment: "SignIn") + noAccountTextField.isHidden = true + createNewAccountButton.isHidden = true } else { actionButton.title = NSLocalizedString("Create", comment: "Create") + signInTextField.stringValue = NSLocalizedString("Sign in to your NewsBlur account.", comment: "SignIn") } } @@ -113,4 +121,9 @@ class AccountsNewsBlurWindowController: NSWindowController { } } } + + @IBAction func createAccountWithProvider(_ sender: Any) { + NSWorkspace.shared.open(URL(string: "https://newsblur.com")!) + } + } diff --git a/Mac/Preferences/Accounts/AccountsReaderAPI.xib b/Mac/Preferences/Accounts/AccountsReaderAPI.xib index 749dbccdf..b96744cdc 100644 --- a/Mac/Preferences/Accounts/AccountsReaderAPI.xib +++ b/Mac/Preferences/Accounts/AccountsReaderAPI.xib @@ -3,6 +3,7 @@ + @@ -11,8 +12,9 @@ - + + @@ -28,49 +30,19 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + + - @@ -79,7 +51,7 @@ - + @@ -89,11 +61,11 @@ - + - + @@ -102,7 +74,7 @@ - + @@ -112,11 +84,11 @@ - + - + @@ -128,7 +100,7 @@ - + @@ -138,7 +110,7 @@ - + @@ -149,17 +121,6 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + - - + + + + @@ -212,4 +252,9 @@ Gw + + + + + diff --git a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift index ccec1dedd..15ab289fb 100644 --- a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift @@ -23,6 +23,7 @@ class AccountsReaderAPIWindowController: NSWindowController { @IBOutlet weak var passwordTextField: NSSecureTextField! @IBOutlet weak var errorMessageLabel: NSTextField! @IBOutlet weak var actionButton: NSButton! + @IBOutlet weak var noAccountTextField: NSTextField! var account: Account? var accountType: AccountType? @@ -38,19 +39,23 @@ class AccountsReaderAPIWindowController: NSWindowController { switch accountType { case .freshRSS: titleImageView.image = AppAssets.accountFreshRSS - titleLabel.stringValue = NSLocalizedString("FreshRSS", comment: "FreshRSS") + titleLabel.stringValue = NSLocalizedString("Sign in to your FreshRSS account.", comment: "FreshRSS") + noAccountTextField.stringValue = NSLocalizedString("Don't have a FreshRSS account?", comment: "No FreshRSS") case .inoreader: titleImageView.image = AppAssets.accountInoreader - titleLabel.stringValue = NSLocalizedString("InoReader", comment: "InoReader") + titleLabel.stringValue = NSLocalizedString("Sign in to your InoReader account.", comment: "InoReader") gridView.row(at: 2).isHidden = true + noAccountTextField.stringValue = NSLocalizedString("Don't have an InoReader account?", comment: "No InoReader") case .bazQux: titleImageView.image = AppAssets.accountBazQux - titleLabel.stringValue = NSLocalizedString("BazQux", comment: "BazQux") + titleLabel.stringValue = NSLocalizedString("Sign in to your BazQux account.", comment: "BazQux") gridView.row(at: 2).isHidden = true + noAccountTextField.stringValue = NSLocalizedString("Don't have a BazQux account?", comment: "No BazQux") case .theOldReader: titleImageView.image = AppAssets.accountTheOldReader - titleLabel.stringValue = NSLocalizedString("The Old Reader", comment: "The Old Reader") + titleLabel.stringValue = NSLocalizedString("Sign in to your The Old Reader account.", comment: "The Old Reader") gridView.row(at: 2).isHidden = true + noAccountTextField.stringValue = NSLocalizedString("Don't have a The Old Reader account?", comment: "No OldReader") default: break } @@ -172,5 +177,21 @@ class AccountsReaderAPIWindowController: NSWindowController { } } + + @IBAction func createAccountWithProvider(_ sender: Any) { + switch accountType { + case .freshRSS: + NSWorkspace.shared.open(URL(string: "https://freshrss.org")!) + case .inoreader: + NSWorkspace.shared.open(URL(string: "https://www.inoreader.com")!) + case .bazQux: + NSWorkspace.shared.open(URL(string: "https://bazqux.com")!) + case .theOldReader: + NSWorkspace.shared.open(URL(string: "https://theoldreader.com")!) + default: + return + } + } + } diff --git a/Mac/Preferences/Accounts/AddAccountHelpView.swift b/Mac/Preferences/Accounts/AddAccountHelpView.swift index fa7675f63..373fe6edb 100644 --- a/Mac/Preferences/Accounts/AddAccountHelpView.swift +++ b/Mac/Preferences/Accounts/AddAccountHelpView.swift @@ -15,6 +15,7 @@ struct AddAccountHelpView: View { var delegate: AccountsPreferencesAddAccountDelegate? var helpText: String @State private var hoveringId: String? = nil + @State private var iCloudUnavailableError: Bool = false var body: some View { VStack { @@ -24,7 +25,11 @@ struct AddAccountHelpView: View { .resizable() .frame(width: 20, height: 20, alignment: .center) .onTapGesture { - delegate?.presentSheetForAccount(account) + if account == .cloudKit && AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit }) { + iCloudUnavailableError = true + } else { + delegate?.presentSheetForAccount(account) + } hoveringId = nil } .onHover(perform: { hovering in @@ -42,7 +47,14 @@ struct AddAccountHelpView: View { Text(helpText) .multilineTextAlignment(.center) .padding(.top, 8) - + } + .alert(isPresented: $iCloudUnavailableError, content: { + Alert(title: Text(NSLocalizedString("Error", comment: "Error")), + message: Text(NSLocalizedString("You've already set up an iCloud account.", comment: "Error")), + dismissButton: Alert.Button.cancel({ + iCloudUnavailableError = false + })) + }) } } diff --git a/Mac/Preferences/Accounts/AddAccountsView.swift b/Mac/Preferences/Accounts/AddAccountsView.swift index e79828846..664f2c476 100644 --- a/Mac/Preferences/Accounts/AddAccountsView.swift +++ b/Mac/Preferences/Accounts/AddAccountsView.swift @@ -151,8 +151,6 @@ struct AddAccountsView: View { .aspectRatio(contentMode: .fit) .frame(width: 25, height: 25, alignment: .center) .padding(.leading, 4) - - Text(account.localizedAccountName()) } .tag(account) diff --git a/Mac/Preferences/ExtensionPoints/EnableExtensionPointHelpView.swift b/Mac/Preferences/ExtensionPoints/EnableExtensionPointHelpView.swift index dfebd08cb..a9f9f24de 100644 --- a/Mac/Preferences/ExtensionPoints/EnableExtensionPointHelpView.swift +++ b/Mac/Preferences/ExtensionPoints/EnableExtensionPointHelpView.swift @@ -11,7 +11,12 @@ import SwiftUI import RSCore struct EnableExtensionPointHelpView: View { - let imageLiterals = ["extensionPointMarsEdit", "extensionPointMicroblog", "extensionPointReddit", "extensionPointTwitter"] + + var extensionPoints: [ExtensionPoint.Type] { + let types = ExtensionPointManager.shared.availableExtensionPointTypes.filter({ $0 is SendToCommand.Type }) + + ExtensionPointManager.shared.availableExtensionPointTypes.filter({ !($0 is SendToCommand.Type) }) + return types + } var helpText: String weak var preferencesController: ExtensionPointPreferencesViewController? @@ -20,23 +25,31 @@ struct EnableExtensionPointHelpView: View { var body: some View { VStack { HStack { - ForEach(imageLiterals, id: \.self) { name in - Image(name) + ForEach(0..? // required because presentationMode.dismiss() doesn't work weak var enabler: ExtensionPointPreferencesEnabler? - @State private var extensionPointTypeName = String(describing: Self.feedProviderExtensionPointTypes.first) + @State private var extensionPointTypeName = String(describing: Self.sendToCommandExtensionPointTypes.first) + private var selectedType: ExtensionPoint.Type? - init(enabler: ExtensionPointPreferencesEnabler?) { + init(enabler: ExtensionPointPreferencesEnabler?, selectedType: ExtensionPoint.Type? ) { self.enabler = enabler + self.selectedType = selectedType } var body: some View { @@ -60,7 +63,7 @@ struct EnableExtensionPointView: View { }) .help("Add Extension") .keyboardShortcut(.defaultAction) - + .disabled(disableContinue()) } else { Button(action: { enabler?.enable(typeFromName(extensionPointTypeName)) @@ -69,6 +72,7 @@ struct EnableExtensionPointView: View { Text("Continue") .frame(width: 80) }) + .disabled(disableContinue()) } } .padding(.top, 12) @@ -78,6 +82,11 @@ struct EnableExtensionPointView: View { .fixedSize(horizontal: false, vertical: true) .frame(width: 420) .padding() + .onAppear { + if selectedType != nil { + extensionPointTypeName = String(describing: selectedType!) + } + } } var feedProviderExtensionPoints: some View { @@ -101,7 +110,7 @@ struct EnableExtensionPointView: View { Text(extensionPointType.title) } - .tag(extensionPointTypeNames) + .tag(extensionPointTypeName) }) }) .pickerStyle(RadioGroupPickerStyle()) @@ -138,7 +147,7 @@ struct EnableExtensionPointView: View { Text(extensionPointType.title) } - .tag(extensionPointTypeNames) + .tag(extensionPointTypeName) }) }) .pickerStyle(RadioGroupPickerStyle()) @@ -169,6 +178,10 @@ struct EnableExtensionPointView: View { } fatalError() } + + func disableContinue() -> Bool { + ExtensionPointManager.shared.availableExtensionPointTypes.count == 0 + } } diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift index 892a65a69..2a596f66b 100644 --- a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift @@ -45,7 +45,13 @@ final class ExtensionPointPreferencesViewController: NSViewController { } @IBAction func enableExtensionPoints(_ sender: Any) { - let controller = NSHostingController(rootView: EnableExtensionPointView(enabler: self)) + let controller = NSHostingController(rootView: EnableExtensionPointView(enabler: self, selectedType: nil)) + controller.rootView.parent = controller + presentAsSheet(controller) + } + + func enableExtensionPointFromSelection(_ selection: ExtensionPoint.Type) { + let controller = NSHostingController(rootView: EnableExtensionPointView(enabler: self, selectedType: selection)) controller.rootView.parent = controller presentAsSheet(controller) } @@ -179,7 +185,10 @@ private extension ExtensionPointPreferencesViewController { if tableView.selectedRow == -1 { var helpText = "" - if activeExtensionPoints.count == 0 { + if ExtensionPointManager.shared.availableExtensionPointTypes.count == 0 { + helpText = NSLocalizedString("You've added all available extension points.", comment: "Extension Explainer") + } + else if activeExtensionPoints.count == 0 { helpText = NSLocalizedString("Add an extension by clicking the + button.", comment: "Extension Explainer") } else { helpText = NSLocalizedString("Select an extension or add a new extension point by clicking the + button.", comment: "Extension Explainer") @@ -219,7 +228,10 @@ private extension ExtensionPointPreferencesViewController { if tableView.selectedRow == -1 { var helpText = "" - if activeExtensionPoints.count == 0 { + if ExtensionPointManager.shared.availableExtensionPointTypes.count == 0 { + helpText = NSLocalizedString("You've added all available extension points.", comment: "Extension Explainer") + } + else if activeExtensionPoints.count == 0 { helpText = NSLocalizedString("Add an extension by clicking the + button.", comment: "Extension Explainer") } else { helpText = NSLocalizedString("Select an extension or add a new extension point by clicking the + button.", comment: "Extension Explainer") From 29dfbbb28b974fbfb4c6505400c8674ea28b1ed3 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 6 Nov 2020 19:09:52 +0800 Subject: [PATCH 40/42] Fixes autolayout warning that had no impact --- Mac/Preferences/Accounts/AccountsAddLocal.xib | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Mac/Preferences/Accounts/AccountsAddLocal.xib b/Mac/Preferences/Accounts/AccountsAddLocal.xib index 265de3209..36e5232cf 100644 --- a/Mac/Preferences/Accounts/AccountsAddLocal.xib +++ b/Mac/Preferences/Accounts/AccountsAddLocal.xib @@ -24,10 +24,10 @@ - + - + - + @@ -220,7 +220,7 @@ Gw - + @@ -230,8 +230,8 @@ Gw + -