mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge pull request #3772 from bryanculver/bsc-662-catch-up
Catch Up Command
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 > ?)
|
||||
//
|
||||
|
||||
@@ -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 Older Than as Read...", 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 Older Than as Read...", 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 Older Than as Read...", 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 Older Than as Read...", 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("Mark All as Read", comment: "Command"), #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("Delete", comment: "Command"), #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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
// TODO FILTER BY SEARCH
|
||||
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
// TODO FILTER BY SEARCH
|
||||
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 Older Than as Read...", 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 Older Than as Read...", 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 }
|
||||
|
||||
Reference in New Issue
Block a user