Merge branch 'main' into localize_strings

# Conflicts:
#	Shared/Timer/AccountRefreshTimer.swift
This commit is contained in:
Stuart Breckenridge
2023-01-04 15:17:14 +08:00
22 changed files with 593 additions and 142 deletions

View File

@@ -653,6 +653,22 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
fetchUnreadCounts(for: webFeeds, completion: completion)
}
public func fetchUnreadArticlesBetween(limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
return try fetchUnreadArticlesBetween(forContainer: self, limit: limit, before: before, after: after)
}
public func fetchUnreadArticlesBetween(folder: Folder, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
return try fetchUnreadArticlesBetween(forContainer: folder, limit: limit, before: before, after: after)
}
public func fetchUnreadArticlesBetween(webFeeds: Set<WebFeed>, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
return try fetchUnreadArticlesBetween(feeds: webFeeds, limit: limit, before: before, after: after)
}
public func fetchArticlesBetween(articleIDs: Set<String>, before: Date?, after: Date?) throws -> Set<Article> {
return try database.fetchArticlesBetween(articleIDs: articleIDs, before: before, after: after)
}
public func fetchArticles(_ fetchType: FetchType) throws -> Set<Article> {
switch fetchType {
case .starred(let limit):
@@ -1150,6 +1166,17 @@ private extension Account {
return articles
}
func fetchUnreadArticlesBetween(forContainer container: Container, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
let feeds = container.flattenedWebFeeds()
let articles = try database.fetchUnreadArticlesBetween(feeds.webFeedIDs(), limit, before, after)
return articles
}
func fetchUnreadArticlesBetween(feeds: Set<WebFeed>, limit: Int?, before: Date?, after: Date?) throws -> Set<Article> {
let articles = try database.fetchUnreadArticlesBetween(feeds.webFeedIDs(), limit, before, after)
return articles
}
func fetchUnreadArticlesAsync(forContainer container: Container, limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
let webFeeds = container.flattenedWebFeeds()
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs(), limit) { [weak self] (articleSetResult) in

View File

@@ -368,6 +368,16 @@ public final class AccountManager: UnreadCountProvider {
}
}
}
public func fetchUnreadArticlesBetween(limit: Int? = nil, before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
precondition(Thread.isMainThread)
var articles = Set<Article>()
for account in activeAccounts {
articles.formUnion(try account.fetchUnreadArticlesBetween(limit: limit, before: before, after: after))
}
return articles
}
// MARK: - Fetching Article Counts

View File

@@ -15,6 +15,7 @@ public protocol ArticleFetcher {
func fetchArticles() throws -> Set<Article>
func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock)
func fetchUnreadArticles() throws -> Set<Article>
func fetchUnreadArticlesBetween(before: Date?, after: Date?) throws -> Set<Article>
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock)
}
@@ -37,6 +38,10 @@ extension WebFeed: ArticleFetcher {
return try fetchArticles().unreadArticles()
}
public func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try account?.fetchUnreadArticlesBetween(webFeeds: [self], limit: nil, before: before, after: after) ?? Set<Article>()
}
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
guard let account = account else {
assertionFailure("Expected feed.account, but got nil.")
@@ -81,6 +86,14 @@ extension Folder: ArticleFetcher {
return try account.fetchArticles(.folder(self, true))
}
public func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
guard let account = account else {
assertionFailure("Expected folder.account, but got nil.")
return Set<Article>()
}
return try account.fetchUnreadArticlesBetween(folder: self, limit: nil, before: before, after: after)
}
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
guard let account = account else {
assertionFailure("Expected folder.account, but got nil.")

View File

@@ -31,6 +31,10 @@ public struct SingleArticleFetcher: ArticleFetcher {
public func fetchUnreadArticles() throws -> Set<Article> {
return try account.fetchArticles(.articleIDs(Set([articleID])))
}
public func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try account.fetchArticlesBetween(articleIDs: Set([articleID]), before: before, after: after)
}
public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
return account.fetchArticlesAsync(.articleIDs(Set([articleID])), completion)

View File

@@ -106,6 +106,14 @@ public final class ArticlesDatabase {
return try articlesTable.fetchUnreadArticles(webFeedIDs, limit)
}
public func fetchArticlesBetween(articleIDs: Set<String>, before: Date?, after: Date?) throws -> Set<Article> {
return try articlesTable.fetchArticlesBetween(articleIDs: articleIDs, before: before, after: after)
}
public func fetchUnreadArticlesBetween(_ webFeedIDs: Set<String>, _ limit: Int?, _ before: Date?, _ after: Date?) throws -> Set<Article> {
return try articlesTable.fetchUnreadArticlesBetween(webFeedIDs, limit, before, after)
}
public func fetchTodayArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate(), limit)
}

View File

@@ -70,6 +70,10 @@ final class ArticlesTable: DatabaseTable {
return try fetchArticles{ self.fetchArticles(articleIDs: articleIDs, $0) }
}
func fetchArticlesBetween(articleIDs: Set<String>, before: Date?, after: Date?) throws -> Set<Article> {
return try fetchArticles{ self.fetchArticlesBetween(articleIDs: articleIDs, before: before, after: after, $0) }
}
func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
return fetchArticlesAsync({ self.fetchArticles(articleIDs: articleIDs, $0) }, completion)
}
@@ -80,6 +84,10 @@ final class ArticlesTable: DatabaseTable {
return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, limit, $0) }
}
func fetchUnreadArticlesBetween(_ webFeedIDs: Set<String>, _ limit: Int?, _ before: Date?, _ after: Date?) throws -> Set<Article> {
return try fetchArticles{ self.fetchUnreadArticlesBetween(webFeedIDs, limit, $0, before, after) }
}
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, limit, $0) }, completion)
}
@@ -845,6 +853,30 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchUnreadArticlesBetween(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase, _ before: Date?, _ after: Date?) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
if webFeedIDs.isEmpty {
return Set<Article>()
}
var parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
var whereClause = "feedID in \(placeholders) and read=0"
if let before = before {
whereClause.append(" and (datePublished < ? or (datePublished is null and dateArrived < ?))")
parameters = parameters + [before as AnyObject, before as AnyObject]
}
if let after = after {
whereClause.append(" and (datePublished > ? or (datePublished is null and dateArrived > ?))")
parameters = parameters + [after as AnyObject, after as AnyObject]
}
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
}
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchArticlesForFeedID(_ webFeedID: String, _ database: FMDatabase) -> Set<Article> {
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject])
}
@@ -859,6 +891,26 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchArticlesBetween(articleIDs: Set<String>, before: Date?, after: Date?, _ database: FMDatabase) -> Set<Article> {
if articleIDs.isEmpty {
return Set<Article>()
}
var parameters = articleIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))!
var whereClause = "articleID in \(placeholders)"
if let before = before {
whereClause.append(" and (datePublished < ? or (datePublished is null and dateArrived < ?))")
parameters = parameters + [before as AnyObject, before as AnyObject]
}
if let after = after {
whereClause.append(" and (datePublished > ? or (datePublished is null and dateArrived > ?))")
parameters = parameters + [after as AnyObject, after as AnyObject]
}
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and (datePublished > ? || (datePublished is null and dateArrived > ?)
//

View File

@@ -703,35 +703,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
Browser.open("https://netnewswire.com/", inBackground: false)
}
@IBAction func openReleaseNotes(_ sender: Any?) {
Browser.open(URL.releaseNotes.absoluteString, inBackground: false)
}
@IBAction func openHowToSupport(_ sender: Any?) {
Browser.open("https://github.com/brentsimmons/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown", inBackground: false)
}
@IBAction func openRepository(_ sender: Any?) {
Browser.open("https://github.com/brentsimmons/NetNewsWire", inBackground: false)
}
@IBAction func openBugTracker(_ sender: Any?) {
Browser.open("https://github.com/brentsimmons/NetNewsWire/issues", inBackground: false)
}
@IBAction func openSlackGroup(_ sender: Any?) {
Browser.open("https://netnewswire.com/slack", inBackground: false)
}
@IBAction func openTechnotes(_ sender: Any?) {
Browser.open("https://github.com/brentsimmons/NetNewsWire/tree/main/Technotes", inBackground: false)
}
@IBAction func showHelp(_ sender: Any?) {
Browser.open("https://netnewswire.com/help/mac/6.1/en/", inBackground: false)

View File

@@ -646,48 +646,12 @@
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="rK6-cL-4Vj"/>
<menuItem title="Website" id="q2Z-9K-GBd">
<menuItem title="NetNewsWire Website" id="q2Z-9K-GBd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="openWebsite:" target="Ady-hI-5gd" id="rmL-lt-p8g"/>
</connections>
</menuItem>
<menuItem title="Release Notes" id="b5s-xg-B1w" userLabel="Release Notes">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="openReleaseNotes:" target="Ady-hI-5gd" id="7LS-uW-Yp0"/>
</connections>
</menuItem>
<menuItem title="How To Support NetNewsWire" id="kfC-NQ-g3E">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="openHowToSupport:" target="Ady-hI-5gd" id="SIw-Ug-A4D"/>
</connections>
</menuItem>
<menuItem title="GitHub Repository" id="QfD-Xw-sdF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="openRepository:" target="Ady-hI-5gd" id="7xZ-V2-iPD"/>
</connections>
</menuItem>
<menuItem title="Bug Tracker" id="mE2-pM-rQF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="openBugTracker:" target="Ady-hI-5gd" id="fZQ-ng-gIm"/>
</connections>
</menuItem>
<menuItem title="Technotes" id="Ou5-Cc-iCb">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="openTechnotes:" target="Ady-hI-5gd" id="M7A-Qg-mH8"/>
</connections>
</menuItem>
<menuItem title="Slack Group" id="4eb-qF-n9S">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="openSlackGroup:" target="Ady-hI-5gd" id="YX2-gA-pgV"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>

View File

@@ -84,6 +84,56 @@ extension SidebarViewController {
runCommand(markReadCommand)
}
@objc func markObjectsReadOlderThanOneDayFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .day, value: -1, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanTwoDaysFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .day, value: -2, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanThreeDaysFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .day, value: -3, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanOneWeekFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanTwoWeeksFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .weekOfYear, value: -2, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanOneMonthFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .month, value: -1, to: Date()), after: nil, sender: sender)
}
@objc func markObjectsReadOlderThanOneYearFromContextualMenu(_ sender: Any?) {
return markObjectsReadBetweenDatesFromContextualMenu(before: Calendar.current.date(byAdding: .year, value: -1, to: Date()), after: nil, sender: sender)
}
func markObjectsReadBetweenDatesFromContextualMenu(before: Date?, after: Date?, sender: Any?) {
guard let menuItem = sender as? NSMenuItem, let objects = menuItem.representedObject as? [Any] else {
return
}
var markableArticles = unreadArticlesBetween(for: objects, before: before, after: after)
if let directlyMarkedAsUnreadArticles = delegate?.directlyMarkedAsUnreadArticles {
markableArticles = markableArticles.subtracting(directlyMarkedAsUnreadArticles)
}
guard let undoManager = undoManager,
let markReadCommand = MarkStatusCommand(initialArticles: markableArticles,
markingRead: true,
directlyMarked: false,
undoManager: undoManager) else {
return
}
runCommand(markReadCommand)
}
@objc func deleteFromContextualMenu(_ sender: Any?) {
guard let menuItem = sender as? NSMenuItem, let objects = menuItem.representedObject as? [AnyObject] else {
return
@@ -220,6 +270,12 @@ private extension SidebarViewController {
if webFeed.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([webFeed]))
let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark as Read Older Than", comment: "Command Submenu"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu([webFeed])
menu.addItem(catchUpMenuItem)
menu.setSubmenu(catchUpSubMenu, for: catchUpMenuItem)
menu.addItem(NSMenuItem.separator())
}
@@ -276,6 +332,11 @@ private extension SidebarViewController {
if folder.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([folder]))
let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark as Read Older Than", comment: "Command Submenu"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu([folder])
menu.addItem(catchUpMenuItem)
menu.setSubmenu(catchUpSubMenu, for: catchUpMenuItem)
menu.addItem(NSMenuItem.separator())
}
@@ -291,6 +352,18 @@ private extension SidebarViewController {
if smartFeed.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([smartFeed]))
// Doesn't make sense to mark articles newer than a day with catch up with first option being older than a day
if let maybeSmartFeed = smartFeed as? SmartFeed {
if maybeSmartFeed.delegate is TodayFeedDelegate {
return menu
}
}
let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark as Read Older Than", comment: "Command Submenu"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu([smartFeed])
menu.addItem(catchUpMenuItem)
menu.setSubmenu(catchUpSubMenu, for: catchUpMenuItem)
}
return menu.numberOfItems > 0 ? menu : nil
}
@@ -301,6 +374,11 @@ private extension SidebarViewController {
if anyObjectInArrayHasNonZeroUnreadCount(objects) {
menu.addItem(markAllReadMenuItem(objects))
let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark as Read Older Than", comment: "Command Submenu"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu(objects)
menu.addItem(catchUpMenuItem)
menu.setSubmenu(catchUpSubMenu, for: catchUpMenuItem)
}
if allObjectsAreFeedsAndOrFolders(objects) {
@@ -316,6 +394,20 @@ private extension SidebarViewController {
return menuItem(NSLocalizedString("button.title.mark-all-as-read.titlecase", comment: "Mark All as Read"), #selector(markObjectsReadFromContextualMenu(_:)), objects)
}
func catchUpSubMenu(_ objects: [Any]) -> NSMenu {
let menu = NSMenu(title: "Catch up to articles older than...")
menu.addItem(menuItem(NSLocalizedString("1 day", comment: "Command"), #selector(markObjectsReadOlderThanOneDayFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("2 days", comment: "Command"), #selector(markObjectsReadOlderThanTwoDaysFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("3 days", comment: "Command"), #selector(markObjectsReadOlderThanThreeDaysFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("1 week", comment: "Command"), #selector(markObjectsReadOlderThanOneWeekFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("2 weeks", comment: "Command"), #selector(markObjectsReadOlderThanTwoWeeksFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("1 month", comment: "Command"), #selector(markObjectsReadOlderThanOneMonthFromContextualMenu(_:)), objects))
menu.addItem(menuItem(NSLocalizedString("1 year", comment: "Command"), #selector(markObjectsReadOlderThanOneYearFromContextualMenu(_:)), objects))
return menu
}
func deleteMenuItem(_ objects: [Any]) -> NSMenuItem {
return menuItem(NSLocalizedString("button.title.delete", comment: "Delete"), #selector(deleteFromContextualMenu(_:)), objects)
@@ -373,5 +465,18 @@ private extension SidebarViewController {
}
return articles
}
func unreadArticlesBetween(for objects: [Any], before: Date?, after: Date?) -> Set<Article> {
var articles = Set<Article>()
for object in objects {
if let articleFetcher = object as? ArticleFetcher {
if let unreadArticles = try? articleFetcher.fetchUnreadArticlesBetween(before: before, after: after) {
articles.formUnion(unreadArticles)
}
}
}
return articles
}
}

View File

@@ -35,5 +35,10 @@ struct SearchFeedDelegate: SmartFeedDelegate {
func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) {
// TODO: after 5.0
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
fatalError("Function not implemented.")
}
}

View File

@@ -35,4 +35,9 @@ struct SearchTimelineFeedDelegate: SmartFeedDelegate {
func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) {
// TODO: after 5.0
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
fatalError("Function not implemented.")
}
}

View File

@@ -46,7 +46,7 @@ final class SmartFeed: PseudoFeed {
}
#endif
private let delegate: SmartFeedDelegate
public let delegate: SmartFeedDelegate
private var unreadCounts = [String: Int]()
init(delegate: SmartFeedDelegate) {
@@ -95,6 +95,10 @@ extension SmartFeed: ArticleFetcher {
return try delegate.fetchUnreadArticles()
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try delegate.fetchUnreadArticlesBetween(before: before, after: after)
}
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
delegate.fetchUnreadArticlesAsync(completion)
}

View File

@@ -31,6 +31,10 @@ extension SmartFeedDelegate {
return try fetchArticles().unreadArticles()
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
}
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync{ articleSetResult in
switch articleSetResult {

View File

@@ -29,4 +29,8 @@ struct StarredFeedDelegate: SmartFeedDelegate {
func fetchUnreadCount(for account: Account, completion: @escaping SingleUnreadCountCompletionBlock) {
account.fetchUnreadCountForStarredArticles(completion)
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after).filter({ $0.status.starred })
}
}

View File

@@ -78,6 +78,10 @@ extension UnreadFeed: ArticleFetcher {
return try AccountManager.shared.fetchArticles(fetchType)
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
}
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
AccountManager.shared.fetchArticlesAsync(fetchType, completion)
}

View File

@@ -73,8 +73,7 @@ class AccountRefreshTimer {
lastTimedRefresh = Date()
update()
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log, completion: nil)
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
}
}

View File

@@ -35,16 +35,24 @@ public class AddWebFeedIntentHandler: NSObject, AddWebFeedIntentHandling {
completion(.success(with: url))
}
public func provideAccountNameOptions(for intent: AddWebFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) {
public func resolveTitle(for intent: AddWebFeedIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
guard let title = intent.title else {
completion(INStringResolutionResult.notRequired())
return
}
completion(.success(with: title))
}
public func provideAccountNameOptionsCollection(for intent: AddWebFeedIntent, with completion: @escaping (INObjectCollection<NSString>?, Error?) -> Void) {
guard let extensionContainers = ExtensionContainersFile.read() else {
completion(nil, AddWebFeedIntentHandlerError.communicationFailure)
return
}
let accountNames = extensionContainers.accounts.map { $0.name }
completion(accountNames, nil)
completion(INObjectCollection(items: accountNames as [NSString]), nil)
}
public func resolveAccountName(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedAccountNameResolutionResult) -> Void) {
guard let accountName = intent.accountName else {
completion(AddWebFeedAccountNameResolutionResult.notRequired())
@@ -78,6 +86,21 @@ public class AddWebFeedIntentHandler: NSObject, AddWebFeedIntentHandling {
completion(folderNames, nil)
}
public func provideFolderNameOptionsCollection(for intent: AddWebFeedIntent, with completion: @escaping (INObjectCollection<NSString>?, Error?) -> Void) {
guard let extensionContainers = ExtensionContainersFile.read() else {
completion(nil, AddWebFeedIntentHandlerError.communicationFailure)
return
}
guard let accountName = intent.accountName, let account = extensionContainers.findAccount(forName: accountName) else {
completion(INObjectCollection(items: [NSString]()), nil)
return
}
let folderNames = account.folders.map { $0.name }
completion(INObjectCollection(items: folderNames as [NSString]), nil)
}
public func resolveFolderName(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedFolderNameResolutionResult) -> Void) {
guard let accountName = intent.accountName, let folderName = intent.folderName else {
completion(AddWebFeedFolderNameResolutionResult.notRequired())
@@ -135,7 +158,7 @@ public class AddWebFeedIntentHandler: NSObject, AddWebFeedIntentHandling {
return
}
let request = ExtensionFeedAddRequest(name: nil, feedURL: url, destinationContainerID: containerID)
let request = ExtensionFeedAddRequest(name: intent.title, feedURL: url, destinationContainerID: containerID)
ExtensionFeedAddRequestFile.save(request)
completion(AddWebFeedIntentResponse(code: .success, userActivity: nil))
}

View File

@@ -5,15 +5,15 @@
<key>INEnums</key>
<array/>
<key>INIntentDefinitionModelVersion</key>
<string>1.1</string>
<string>1.2</string>
<key>INIntentDefinitionNamespace</key>
<string>U6u7RF</string>
<key>INIntentDefinitionSystemVersion</key>
<string>19D76</string>
<string>22A400</string>
<key>INIntentDefinitionToolsBuildVersion</key>
<string>11B53</string>
<string>14B47b</string>
<key>INIntentDefinitionToolsVersion</key>
<string>11.2.1</string>
<string>14.1</string>
<key>INIntents</key>
<array>
<dict>
@@ -32,21 +32,10 @@
<key>INIntentKeyParameter</key>
<string>url</string>
<key>INIntentLastParameterTag</key>
<integer>4</integer>
<integer>5</integer>
<key>INIntentManagedParameterCombinations</key>
<dict>
<key>url,accountName</key>
<dict>
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
<true/>
<key>INIntentParameterCombinationTitle</key>
<string>Add ${url} to ${accountName}</string>
<key>INIntentParameterCombinationTitleID</key>
<string>kaKsEY</string>
<key>INIntentParameterCombinationUpdatesLinked</key>
<true/>
</dict>
<key>url,accountName,folderName</key>
<key>url,accountName,folderName,title</key>
<dict>
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
<true/>
@@ -57,12 +46,25 @@
<key>INIntentParameterCombinationUpdatesLinked</key>
<true/>
</dict>
<key>url,accountName,title</key>
<dict>
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
<true/>
<key>INIntentParameterCombinationTitle</key>
<string>Add ${url} to ${accountName}</string>
<key>INIntentParameterCombinationTitleID</key>
<string>kaKsEY</string>
<key>INIntentParameterCombinationUpdatesLinked</key>
<true/>
</dict>
</dict>
<key>INIntentName</key>
<string>AddWebFeed</string>
<key>INIntentParameters</key>
<array>
<dict>
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterDisplayName</key>
<string>URL</string>
<key>INIntentParameterDisplayNameID</key>
@@ -105,6 +107,52 @@
</array>
</dict>
<dict>
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterDisplayName</key>
<string>Title</string>
<key>INIntentParameterDisplayNameID</key>
<string>Ac5RHN</string>
<key>INIntentParameterDisplayPriority</key>
<integer>2</integer>
<key>INIntentParameterMetadata</key>
<dict>
<key>INIntentParameterMetadataCapitalization</key>
<string>Words</string>
<key>INIntentParameterMetadataDefaultValueID</key>
<string>SVcvQb</string>
</dict>
<key>INIntentParameterName</key>
<string>title</string>
<key>INIntentParameterPromptDialogs</key>
<array>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogType</key>
<string>Configuration</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>What is the ${title}of the feed?</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>IGNcSh</string>
<key>INIntentParameterPromptDialogType</key>
<string>Primary</string>
</dict>
</array>
<key>INIntentParameterSupportsResolution</key>
<true/>
<key>INIntentParameterTag</key>
<integer>5</integer>
<key>INIntentParameterType</key>
<string>String</string>
</dict>
<dict>
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterCustomDisambiguation</key>
<true/>
<key>INIntentParameterDisplayName</key>
@@ -112,7 +160,7 @@
<key>INIntentParameterDisplayNameID</key>
<string>CSrgUY</string>
<key>INIntentParameterDisplayPriority</key>
<integer>2</integer>
<integer>3</integer>
<key>INIntentParameterMetadata</key>
<dict>
<key>INIntentParameterMetadataCapitalization</key>
@@ -138,14 +186,6 @@
<key>INIntentParameterPromptDialogType</key>
<string>DisambiguationIntroduction</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogFormatString</key>
<string>Which one?</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>fWs3li</string>
<key>INIntentParameterPromptDialogType</key>
<string>DisambiguationSelection</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
@@ -190,6 +230,8 @@
</array>
</dict>
<dict>
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterCustomDisambiguation</key>
<true/>
<key>INIntentParameterDisplayName</key>
@@ -197,7 +239,7 @@
<key>INIntentParameterDisplayNameID</key>
<string>zXhMPF</string>
<key>INIntentParameterDisplayPriority</key>
<integer>3</integer>
<integer>4</integer>
<key>INIntentParameterMetadata</key>
<dict>
<key>INIntentParameterMetadataCapitalization</key>
@@ -223,14 +265,6 @@
<key>INIntentParameterPromptDialogType</key>
<string>DisambiguationIntroduction</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogFormatString</key>
<string>Which one?</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>gEzXaM</string>
<key>INIntentParameterPromptDialogType</key>
<string>DisambiguationSelection</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>

View File

@@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>OrganizationIdentifier</key>
<string>$(ORGANIZATION_IDENTIFIER)</string>
<key>AppGroup</key>
<string>group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
<key>AppIdentifierPrefix</key>
@@ -44,5 +42,7 @@
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).IntentHandler</string>
</dict>
<key>OrganizationIdentifier</key>
<string>$(ORGANIZATION_IDENTIFIER)</string>
</dict>
</plist>

View File

@@ -693,6 +693,9 @@ extension MasterFeedViewController: UIContextMenuInteractionDelegate {
menuElements.append(UIMenu(title: "", options: .displayInline, children: [markAllAction]))
}
if let catchUpAction = self.catchUpActionMenu(account: account, contentView: interaction.view) {
menuElements.append(catchUpAction)
}
menuElements.append(UIMenu(title: "", options: .displayInline, children: [self.deactivateAccountAction(account: account)]))
return UIMenu(title: "", children: menuElements)
@@ -921,6 +924,11 @@ private extension MasterFeedViewController {
if let markAllAction = self.markAllAsReadAction(indexPath: indexPath) {
menuElements.append(UIMenu(title: "", options: .displayInline, children: [markAllAction]))
}
if let catchUpAction = self.catchUpActionMenu(indexPath: indexPath) {
menuElements.append(catchUpAction)
}
if includeDeleteRename {
@@ -948,6 +956,10 @@ private extension MasterFeedViewController {
if let markAllAction = self.markAllAsReadAction(indexPath: indexPath) {
menuElements.append(UIMenu(title: "", options: .displayInline, children: [markAllAction]))
}
if let catchUpAction = self.catchUpActionMenu(indexPath: indexPath) {
menuElements.append(catchUpAction)
}
menuElements.append(UIMenu(title: "",
options: .displayInline,
@@ -961,13 +973,22 @@ private extension MasterFeedViewController {
})
}
func makePseudoFeedContextMenu(indexPath: IndexPath) -> UIContextMenuConfiguration? {
guard let markAllAction = self.markAllAsReadAction(indexPath: indexPath) else {
return nil
}
func makePseudoFeedContextMenu(indexPath: IndexPath) -> UIContextMenuConfiguration {
return UIContextMenuConfiguration(identifier: MasterFeedRowIdentifier(indexPath: indexPath), previewProvider: nil, actionProvider: { [weak self] suggestedActions in
return UIContextMenuConfiguration(identifier: MasterFeedRowIdentifier(indexPath: indexPath), previewProvider: nil, actionProvider: { suggestedActions in
return UIMenu(title: "", children: [markAllAction])
guard let self = self else { return nil }
var menuElements = [UIMenuElement]()
if let markAllAction = self.markAllAsReadAction(indexPath: indexPath) {
menuElements.append(UIMenu(title: "", options: .displayInline, children: [markAllAction]))
}
if let catchUpAction = self.catchUpActionMenu(indexPath: indexPath) {
menuElements.append(catchUpAction)
}
return UIMenu(title: "", children: menuElements)
})
}
@@ -1151,6 +1172,97 @@ private extension MasterFeedViewController {
return action
}
func catchUpActionMenu(indexPath: IndexPath) -> UIMenu? {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let contentView = self.tableView.cellForRow(at: indexPath)?.contentView,
feed.unreadCount > 0 else {
return nil
}
// Doesn't make sense to mark articles newer than a day with catch up with first option being older than a day
if let maybeSmartFeed = feed as? SmartFeed {
if maybeSmartFeed.delegate is TodayFeedDelegate {
return nil
}
}
let title = NSLocalizedString("Mark as Read Older Than", comment: "Command")
let oneDayAction = UIAction(title: "1 Day") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Day as Read", sourceType: contentView) { [weak self] in
let cutoff = Calendar.current.date(byAdding: .day, value: -1, to: Date())
if let articles = try? feed.fetchUnreadArticlesBetween(before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
let twoDayAction = UIAction(title: "2 Days") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 2 Days as Read", sourceType: contentView) { [weak self] in
let cutoff = Calendar.current.date(byAdding: .day, value: -2, to: Date())
if let articles = try? feed.fetchUnreadArticlesBetween(before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
let threeDayAction = UIAction(title: "3 Days") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 3 Days as Read", sourceType: contentView) { [weak self] in
let cutoff = Calendar.current.date(byAdding: .day, value: -3, to: Date())
if let articles = try? feed.fetchUnreadArticlesBetween(before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
let oneWeekAction = UIAction(title: "1 Week") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Week as Read", sourceType: contentView) { [weak self] in
let cutoff = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())
if let articles = try? feed.fetchUnreadArticlesBetween(before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
let twoWeekAction = UIAction(title: "2 Weeks") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 2 Weeks as Read", sourceType: contentView) { [weak self] in
let cutoff = Calendar.current.date(byAdding: .weekOfYear, value: -2, to: Date())
if let articles = try? feed.fetchUnreadArticlesBetween(before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
let oneMonthAction = UIAction(title: "1 Month") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Month as Read", sourceType: contentView) { [weak self] in
let cutoff = Calendar.current.date(byAdding: .month, value: -1, to: Date())
if let articles = try? feed.fetchUnreadArticlesBetween(before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
let oneYearAction = UIAction(title: "1 Year") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Year as Read", sourceType: contentView) { [weak self] in
let cutoff = Calendar.current.date(byAdding: .year, value: -1, to: Date())
if let articles = try? feed.fetchUnreadArticlesBetween(before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
var markActions = [UIAction]()
markActions.append(oneDayAction)
markActions.append(twoDayAction)
markActions.append(threeDayAction)
markActions.append(oneWeekAction)
markActions.append(twoWeekAction)
markActions.append(oneMonthAction)
markActions.append(oneYearAction)
let majorMenu = UIMenu(title: title, image: getMarkOlderImageDirection(), children: markActions)
return majorMenu
}
func getMarkOlderImageDirection() -> UIImage {
if AppDefaults.shared.timelineSortDirection == .orderedDescending {
return AppAssets.markBelowAsReadImage
} else {
return AppAssets.markAboveAsReadImage
}
}
func markAllAsReadAction(account: Account, contentView: UIView?) -> UIAction? {
guard account.unreadCount > 0, let contentView = contentView else {
return nil
@@ -1171,6 +1283,102 @@ private extension MasterFeedViewController {
return action
}
func catchUpActionMenu(account: Account, contentView: UIView?) -> UIMenu? {
guard account.unreadCount > 0, let contentView = contentView else {
return nil
}
let title = NSLocalizedString("Mark as Read Older Than", comment: "Command")
let oneDayAction = UIAction(title: "1 Day") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Day as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .day, value: -1, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let twoDayAction = UIAction(title: "2 Days") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 2 Days as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .day, value: -2, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let threeDayAction = UIAction(title: "3 Days") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 3 Days as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .day, value: -3, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let oneWeekAction = UIAction(title: "1 Week") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Week as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let twoWeekAction = UIAction(title: "2 Weeks") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 2 Weeks as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .weekOfYear, value: -2, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let oneMonthAction = UIAction(title: "1 Month") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Month as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .month, value: -1, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
let oneYearAction = UIAction(title: "1 Year") { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: "Mark Older Than 1 Year as Read", sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let cutoff = Calendar.current.date(byAdding: .year, value: -1, to: Date())
if let articles = try? account.fetchUnreadArticlesBetween(limit: nil, before: cutoff, after: nil) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
var markActions = [UIAction]()
markActions.append(oneDayAction)
markActions.append(twoDayAction)
markActions.append(threeDayAction)
markActions.append(oneWeekAction)
markActions.append(twoWeekAction)
markActions.append(oneMonthAction)
markActions.append(oneYearAction)
let majorMenu = UIMenu(title: title, image: getMarkOlderImageDirection(), children: markActions)
return majorMenu
}
func rename(indexPath: IndexPath) {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { return }

View File

@@ -6,14 +6,33 @@
<string>group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.ranchero.NetNewsWire.FeedRefresh</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>nnwtheme</string>
</array>
<key>CFBundleTypeName</key>
<string>NetNewsWire Theme</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.ranchero.netnewswire.theme</string>
</array>
<key>LSTypeIsPackage</key>
<true/>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -54,6 +73,8 @@
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>DeveloperEntitlements</key>
<string>$(DEVELOPER_ENTITLEMENTS)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>mailto</string>
@@ -61,6 +82,8 @@
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
@@ -195,29 +218,6 @@
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>nnwtheme</string>
</array>
<key>CFBundleTypeName</key>
<string>NetNewsWire Theme</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.ranchero.netnewswire.theme</string>
</array>
<key>LSTypeIsPackage</key>
<true/>
</dict>
</array>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>UserAgent</key>
<string>NetNewsWire (RSS Reader; https://netnewswire.com/)</string>
</dict>

View File

@@ -352,6 +352,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
}
}
}
if let isSidebarHidden = windowState[UserInfoKey.isSidebarHidden] as? Bool, isSidebarHidden {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.rootSplitViewController.preferredDisplayMode = .secondaryOnly
}
}
rebuildBackingStores(initialLoad: true)
@@ -2124,7 +2130,8 @@ private extension SceneCoordinator {
return [
UserInfoKey.readFeedsFilterState: isReadFeedsFiltered,
UserInfoKey.containerExpandedWindowState: containerExpandedWindowState,
UserInfoKey.readArticlesFilterState: readArticlesFilterState
UserInfoKey.readArticlesFilterState: readArticlesFilterState,
UserInfoKey.isSidebarHidden: rootSplitViewController.displayMode == .secondaryOnly
]
}