mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Add the concept of direct vs. indirect marking of articles. Fixes #3734
This commit is contained in:
@@ -637,6 +637,10 @@ extension MainWindowController: NSWindowDelegate {
|
||||
|
||||
extension MainWindowController: SidebarDelegate {
|
||||
|
||||
var directlyMarkedAsUnreadArticles: Set<Article>? {
|
||||
return timelineContainerViewController?.currentTimelineViewController?.directlyMarkedAsUnreadArticles
|
||||
}
|
||||
|
||||
func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?) {
|
||||
// Don’t update the timeline if it already has those objects.
|
||||
let representedObjectsAreTheSame = timelineContainerViewController?.regularTimelineViewControllerHasRepresentedObjects(selectedObjects) ?? false
|
||||
|
||||
@@ -69,8 +69,16 @@ extension SidebarViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let articles = unreadArticles(for: objects)
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) else {
|
||||
var markableArticles = unreadArticles(for: objects)
|
||||
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)
|
||||
|
||||
@@ -17,6 +17,7 @@ extension Notification.Name {
|
||||
}
|
||||
|
||||
protocol SidebarDelegate: AnyObject {
|
||||
var directlyMarkedAsUnreadArticles: Set<Article>? { get }
|
||||
func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?)
|
||||
func unreadCount(for: AnyObject) -> Int
|
||||
func sidebarInvalidatedRestorationState(_: SidebarViewController)
|
||||
@@ -256,7 +257,11 @@ protocol SidebarDelegate: AnyObject {
|
||||
return
|
||||
}
|
||||
if AppDefaults.shared.feedDoubleClickMarkAsRead, let articles = try? singleSelectedWebFeed?.fetchUnreadArticles() {
|
||||
if let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) {
|
||||
if let undoManager = undoManager,
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: Array(articles),
|
||||
markingRead: true,
|
||||
directlyMarked: false,
|
||||
undoManager: undoManager) {
|
||||
runCommand(markReadCommand)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,12 @@ extension TimelineViewController {
|
||||
|
||||
@objc func markArticlesReadFromContextualMenu(_ sender: Any?) {
|
||||
guard let articles = articles(from: sender) else { return }
|
||||
markArticles(articles, read: true)
|
||||
markArticles(articles, read: true, directlyMarked: true)
|
||||
}
|
||||
|
||||
@objc func markArticlesUnreadFromContextualMenu(_ sender: Any?) {
|
||||
guard let articles = articles(from: sender) else { return }
|
||||
markArticles(articles, read: false)
|
||||
markArticles(articles, read: false, directlyMarked: true)
|
||||
}
|
||||
|
||||
@objc func markAboveArticlesReadFromContextualMenu(_ sender: Any?) {
|
||||
@@ -59,14 +59,14 @@ extension TimelineViewController {
|
||||
|
||||
@objc func markArticlesStarredFromContextualMenu(_ sender: Any?) {
|
||||
guard let articles = articles(from: sender) else { return }
|
||||
markArticles(articles, starred: true)
|
||||
markArticles(articles, starred: true, directlyMarked: true)
|
||||
}
|
||||
|
||||
@objc func markArticlesUnstarredFromContextualMenu(_ sender: Any?) {
|
||||
guard let articles = articles(from: sender) else {
|
||||
return
|
||||
}
|
||||
markArticles(articles, starred: false)
|
||||
markArticles(articles, starred: false, directlyMarked: true)
|
||||
}
|
||||
|
||||
@objc func selectFeedInSidebarFromContextualMenu(_ sender: Any?) {
|
||||
@@ -81,7 +81,11 @@ extension TimelineViewController {
|
||||
return
|
||||
}
|
||||
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: feedArticles, markingRead: true, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager,
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: feedArticles,
|
||||
markingRead: true,
|
||||
directlyMarked: false,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,16 +119,21 @@ extension TimelineViewController {
|
||||
|
||||
private extension TimelineViewController {
|
||||
|
||||
func markArticles(_ articles: [Article], read: Bool) {
|
||||
markArticles(articles, statusKey: .read, flag: read)
|
||||
func markArticles(_ articles: [Article], read: Bool, directlyMarked: Bool) {
|
||||
markArticles(articles, statusKey: .read, flag: read, directlyMarked: directlyMarked)
|
||||
}
|
||||
|
||||
func markArticles(_ articles: [Article], starred: Bool) {
|
||||
markArticles(articles, statusKey: .starred, flag: starred)
|
||||
func markArticles(_ articles: [Article], starred: Bool, directlyMarked: Bool) {
|
||||
markArticles(articles, statusKey: .starred, flag: starred, directlyMarked: directlyMarked)
|
||||
}
|
||||
|
||||
func markArticles(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool) {
|
||||
guard let undoManager = undoManager, let markStatusCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager) else {
|
||||
func markArticles(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool) {
|
||||
guard let undoManager = undoManager,
|
||||
let markStatusCommand = MarkStatusCommand(initialArticles: articles,
|
||||
statusKey: statusKey,
|
||||
flag: flag,
|
||||
directlyMarked: directlyMarked,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -123,10 +123,13 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
showFeedNames = .feed
|
||||
}
|
||||
|
||||
directlyMarkedAsUnreadArticles = Set<Article>()
|
||||
articleRowMap = [String: [Int]]()
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
var directlyMarkedAsUnreadArticles = Set<Article>()
|
||||
|
||||
var unreadCount: Int = 0 {
|
||||
didSet {
|
||||
@@ -219,6 +222,8 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .UserDidDeleteAccount, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidDirectMarking(_:)), name: .MarkStatusCommandDidDirectMarking, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidUndoDirectMarking(_:)), name: .MarkStatusCommandDidUndoDirectMarking, object: nil)
|
||||
didRegisterForNotifications = true
|
||||
}
|
||||
}
|
||||
@@ -230,7 +235,13 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
// MARK: - API
|
||||
|
||||
func markAllAsRead(completion: (() -> Void)? = nil) {
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager, completion: completion) else {
|
||||
let markableArticles = Set(articles).subtracting(directlyMarkedAsUnreadArticles)
|
||||
guard let undoManager = undoManager,
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: markableArticles,
|
||||
markingRead: true,
|
||||
directlyMarked: false,
|
||||
undoManager: undoManager,
|
||||
completion: completion) else {
|
||||
return
|
||||
}
|
||||
runCommand(markReadCommand)
|
||||
@@ -336,14 +347,22 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
@IBAction func markSelectedArticlesAsRead(_ sender: Any?) {
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager,
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles,
|
||||
markingRead: true,
|
||||
directlyMarked: true,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markReadCommand)
|
||||
}
|
||||
|
||||
@IBAction func markSelectedArticlesAsUnread(_ sender: Any?) {
|
||||
guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager,
|
||||
let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles,
|
||||
markingRead: false,
|
||||
directlyMarked: true,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markUnreadCommand)
|
||||
@@ -411,7 +430,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
return
|
||||
}
|
||||
|
||||
guard let undoManager = undoManager, let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: markingRead, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager,
|
||||
let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles,
|
||||
markingRead: markingRead,
|
||||
directlyMarked: true,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -434,7 +457,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
return
|
||||
}
|
||||
|
||||
guard let undoManager = undoManager, let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, markingStarred: starring, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager,
|
||||
let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles,
|
||||
markingStarred: starring,
|
||||
directlyMarked: true,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markStarredCommand)
|
||||
@@ -501,7 +528,12 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
return
|
||||
}
|
||||
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else {
|
||||
let markableArticles = Set(articlesToMark).subtracting(directlyMarkedAsUnreadArticles)
|
||||
guard let undoManager = undoManager,
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: markableArticles,
|
||||
markingRead: true,
|
||||
directlyMarked: false,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markReadCommand)
|
||||
@@ -509,9 +541,16 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
|
||||
func markAboveArticlesRead(_ selectedArticles: [Article]) {
|
||||
guard let first = selectedArticles.first else { return }
|
||||
|
||||
let articlesToMark = articles.articlesAbove(article: first)
|
||||
guard !articlesToMark.isEmpty else { return }
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else {
|
||||
|
||||
let markableArticles = Set(articlesToMark).subtracting(directlyMarkedAsUnreadArticles)
|
||||
guard let undoManager = undoManager,
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: markableArticles,
|
||||
markingRead: true,
|
||||
directlyMarked: false,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markReadCommand)
|
||||
@@ -519,9 +558,16 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
|
||||
func markBelowArticlesRead(_ selectedArticles: [Article]) {
|
||||
guard let last = selectedArticles.last else { return }
|
||||
|
||||
let articlesToMark = articles.articlesBelow(article: last)
|
||||
guard !articlesToMark.isEmpty else { return }
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else {
|
||||
|
||||
let markableArticles = Set(articlesToMark).subtracting(directlyMarkedAsUnreadArticles)
|
||||
guard let undoManager = undoManager,
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: markableArticles,
|
||||
markingRead: true,
|
||||
directlyMarked: false,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markReadCommand)
|
||||
@@ -665,6 +711,28 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
self.groupByFeed = AppDefaults.shared.timelineGroupByFeed
|
||||
}
|
||||
|
||||
@objc func markStatusCommandDidDirectMarking(_ note: Notification) {
|
||||
guard let userInfo = note.userInfo,
|
||||
let articles = userInfo[Account.UserInfoKey.articles] as? Set<Article>,
|
||||
let statusKey = userInfo[Account.UserInfoKey.statusKey] as? ArticleStatus.Key,
|
||||
let flag = userInfo[Account.UserInfoKey.statusFlag] as? Bool else { return }
|
||||
|
||||
if statusKey == .read && flag == false {
|
||||
directlyMarkedAsUnreadArticles.formUnion(articles)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func markStatusCommandDidUndoDirectMarking(_ note: Notification) {
|
||||
guard let userInfo = note.userInfo,
|
||||
let articles = userInfo[Account.UserInfoKey.articles] as? Set<Article>,
|
||||
let statusKey = userInfo[Account.UserInfoKey.statusKey] as? ArticleStatus.Key,
|
||||
let flag = userInfo[Account.UserInfoKey.statusFlag] as? Bool else { return }
|
||||
|
||||
if statusKey == .read && flag == false {
|
||||
directlyMarkedAsUnreadArticles.subtract(articles)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Reloading Data
|
||||
|
||||
private func cellForRowView(_ rowView: NSView) -> NSView? {
|
||||
@@ -899,14 +967,22 @@ extension TimelineViewController: NSTableViewDelegate {
|
||||
}
|
||||
|
||||
private func toggleArticleRead(_ article: Article) {
|
||||
guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: [article], markingRead: !article.status.read, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager,
|
||||
let markUnreadCommand = MarkStatusCommand(initialArticles: [article],
|
||||
markingRead: !article.status.read,
|
||||
directlyMarked: true,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
self.runCommand(markUnreadCommand)
|
||||
}
|
||||
|
||||
|
||||
private func toggleArticleStarred(_ article: Article) {
|
||||
guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: [article], markingStarred: !article.status.starred, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager,
|
||||
let markUnreadCommand = MarkStatusCommand(initialArticles: [article],
|
||||
markingStarred: !article.status.starred,
|
||||
directlyMarked: true,
|
||||
undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
self.runCommand(markUnreadCommand)
|
||||
|
||||
@@ -8,9 +8,20 @@
|
||||
|
||||
import Foundation
|
||||
import RSCore
|
||||
import Account
|
||||
import Articles
|
||||
|
||||
// Mark articles read/unread, starred/unstarred, deleted/undeleted.
|
||||
//
|
||||
// Directly marked articles are ones that were statused by selecting with a cursor or were selected by group.
|
||||
// Indirectly marked articles didn't have any focus and were picked up using a Mark All command like Mark All as Read.
|
||||
//
|
||||
// See discussion for details: https://github.com/Ranchero-Software/NetNewsWire/issues/3734
|
||||
|
||||
public extension Notification.Name {
|
||||
static let MarkStatusCommandDidDirectMarking = Notification.Name("MarkStatusCommandDid√DirectMarking")
|
||||
static let MarkStatusCommandDidUndoDirectMarking = Notification.Name("MarkStatusCommandDidUndoDirectMarking")
|
||||
}
|
||||
|
||||
final class MarkStatusCommand: UndoableCommand {
|
||||
|
||||
@@ -19,10 +30,11 @@ final class MarkStatusCommand: UndoableCommand {
|
||||
let articles: Set<Article>
|
||||
let undoManager: UndoManager
|
||||
let flag: Bool
|
||||
let directlyMarked: Bool
|
||||
let statusKey: ArticleStatus.Key
|
||||
var completion: (() -> Void)? = nil
|
||||
|
||||
init?(initialArticles: [Article], statusKey: ArticleStatus.Key, flag: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
init?(initialArticles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
|
||||
// Filter out articles that already have the desired status or can't be marked.
|
||||
let articlesToMark = MarkStatusCommand.filteredArticles(initialArticles, statusKey, flag)
|
||||
@@ -30,8 +42,9 @@ final class MarkStatusCommand: UndoableCommand {
|
||||
completion?()
|
||||
return nil
|
||||
}
|
||||
self.articles = Set(articlesToMark)
|
||||
self.articles = articlesToMark
|
||||
|
||||
self.directlyMarked = directlyMarked
|
||||
self.flag = flag
|
||||
self.statusKey = statusKey
|
||||
self.undoManager = undoManager
|
||||
@@ -42,21 +55,39 @@ final class MarkStatusCommand: UndoableCommand {
|
||||
self.redoActionName = actionName
|
||||
}
|
||||
|
||||
convenience init?(initialArticles: [Article], markingRead: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, undoManager: undoManager, completion: completion)
|
||||
convenience init?(initialArticles: [Article], statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
self.init(initialArticles: Set(initialArticles), statusKey: .read, flag: flag, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion)
|
||||
}
|
||||
|
||||
convenience init?(initialArticles: [Article], markingStarred: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, undoManager: undoManager, completion: completion)
|
||||
convenience init?(initialArticles: Set<Article>, markingRead: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion)
|
||||
}
|
||||
|
||||
convenience init?(initialArticles: [Article], markingRead: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion)
|
||||
}
|
||||
|
||||
convenience init?(initialArticles: Set<Article>, markingStarred: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion)
|
||||
}
|
||||
|
||||
convenience init?(initialArticles: [Article], markingStarred: Bool, directlyMarked: Bool, undoManager: UndoManager, completion: (() -> Void)? = nil) {
|
||||
self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, directlyMarked: directlyMarked, undoManager: undoManager, completion: completion)
|
||||
}
|
||||
|
||||
func perform() {
|
||||
mark(statusKey, flag)
|
||||
if directlyMarked {
|
||||
markStatusCommandDidDirectMarking()
|
||||
}
|
||||
registerUndo()
|
||||
}
|
||||
|
||||
func undo() {
|
||||
mark(statusKey, !flag)
|
||||
if directlyMarked {
|
||||
markStatusCommandDidUndoDirectMarking()
|
||||
}
|
||||
registerRedo()
|
||||
}
|
||||
}
|
||||
@@ -67,6 +98,18 @@ private extension MarkStatusCommand {
|
||||
markArticles(articles, statusKey: statusKey, flag: flag, completion: completion)
|
||||
completion = nil
|
||||
}
|
||||
|
||||
func markStatusCommandDidDirectMarking() {
|
||||
NotificationCenter.default.post(name: .MarkStatusCommandDidDirectMarking, object: self, userInfo: [Account.UserInfoKey.articles: articles,
|
||||
Account.UserInfoKey.statusKey: statusKey,
|
||||
Account.UserInfoKey.statusFlag: flag])
|
||||
}
|
||||
|
||||
func markStatusCommandDidUndoDirectMarking() {
|
||||
NotificationCenter.default.post(name: .MarkStatusCommandDidUndoDirectMarking, object: self, userInfo: [Account.UserInfoKey.articles: articles,
|
||||
Account.UserInfoKey.statusKey: statusKey,
|
||||
Account.UserInfoKey.statusFlag: flag])
|
||||
}
|
||||
|
||||
static private let markReadActionName = NSLocalizedString("Mark Read", comment: "command")
|
||||
static private let markUnreadActionName = NSLocalizedString("Mark Unread", comment: "command")
|
||||
@@ -83,7 +126,7 @@ private extension MarkStatusCommand {
|
||||
}
|
||||
}
|
||||
|
||||
static func filteredArticles(_ articles: [Article], _ statusKey: ArticleStatus.Key, _ flag: Bool) -> [Article] {
|
||||
static func filteredArticles(_ articles: Set<Article>, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set<Article> {
|
||||
|
||||
return articles.filter{ article in
|
||||
guard article.status.boolStatus(forKey: statusKey) != flag else { return false }
|
||||
@@ -93,4 +136,5 @@ private extension MarkStatusCommand {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -110,6 +110,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
private var directlyMarkedAsUnreadArticles = Set<Article>()
|
||||
|
||||
var prefersStatusBarHidden = false
|
||||
|
||||
private let treeControllerDelegate = WebFeedTreeControllerDelegate()
|
||||
@@ -330,6 +332,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(importDownloadedTheme(_:)), name: .didEndDownloadingTheme, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidDirectMarking(_:)), name: .MarkStatusCommandDidDirectMarking, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(markStatusCommandDidUndoDirectMarking(_:)), name: .MarkStatusCommandDidUndoDirectMarking, object: nil)
|
||||
}
|
||||
|
||||
func restoreWindowState(_ activity: NSUserActivity?) {
|
||||
@@ -546,6 +550,28 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func markStatusCommandDidDirectMarking(_ note: Notification) {
|
||||
guard let userInfo = note.userInfo,
|
||||
let articles = userInfo[Account.UserInfoKey.articles] as? Set<Article>,
|
||||
let statusKey = userInfo[Account.UserInfoKey.statusKey] as? ArticleStatus.Key,
|
||||
let flag = userInfo[Account.UserInfoKey.statusFlag] as? Bool else { return }
|
||||
|
||||
if statusKey == .read && flag == false {
|
||||
directlyMarkedAsUnreadArticles.formUnion(articles)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func markStatusCommandDidUndoDirectMarking(_ note: Notification) {
|
||||
guard let userInfo = note.userInfo,
|
||||
let articles = userInfo[Account.UserInfoKey.articles] as? Set<Article>,
|
||||
let statusKey = userInfo[Account.UserInfoKey.statusKey] as? ArticleStatus.Key,
|
||||
let flag = userInfo[Account.UserInfoKey.statusFlag] as? Bool else { return }
|
||||
|
||||
if statusKey == .read && flag == false {
|
||||
directlyMarkedAsUnreadArticles.subtract(articles)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: API
|
||||
|
||||
func suspend() {
|
||||
@@ -996,7 +1022,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
}
|
||||
|
||||
func markAllAsRead(_ articles: [Article], completion: (() -> Void)? = nil) {
|
||||
markArticlesWithUndo(articles, statusKey: .read, flag: true, completion: completion)
|
||||
var markableArticles = Set(articles)
|
||||
markableArticles.subtract(directlyMarkedAsUnreadArticles)
|
||||
markArticlesWithUndo(markableArticles, statusKey: .read, flag: true, directlyMarked: false, completion: completion)
|
||||
}
|
||||
|
||||
func markAllAsReadInTimeline(completion: (() -> Void)? = nil) {
|
||||
@@ -1044,13 +1072,13 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
|
||||
func markAsReadForCurrentArticle() {
|
||||
if let article = currentArticle {
|
||||
markArticlesWithUndo([article], statusKey: .read, flag: true)
|
||||
markArticlesWithUndo([article], statusKey: .read, flag: true, directlyMarked: true)
|
||||
}
|
||||
}
|
||||
|
||||
func markAsUnreadForCurrentArticle() {
|
||||
if let article = currentArticle {
|
||||
markArticlesWithUndo([article], statusKey: .read, flag: false)
|
||||
markArticlesWithUndo([article], statusKey: .read, flag: false, directlyMarked: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1062,7 +1090,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
|
||||
func toggleRead(_ article: Article) {
|
||||
guard !article.status.read || article.isAvailableToMarkUnread else { return }
|
||||
markArticlesWithUndo([article], statusKey: .read, flag: !article.status.read)
|
||||
markArticlesWithUndo([article], statusKey: .read, flag: !article.status.read, directlyMarked: true)
|
||||
}
|
||||
|
||||
func toggleStarredForCurrentArticle() {
|
||||
@@ -1072,7 +1100,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
}
|
||||
|
||||
func toggleStar(_ article: Article) {
|
||||
markArticlesWithUndo([article], statusKey: .starred, flag: !article.status.starred)
|
||||
markArticlesWithUndo([article], statusKey: .starred, flag: !article.status.starred, directlyMarked: true)
|
||||
}
|
||||
|
||||
func timelineFeedIsEqualTo(_ feed: WebFeed) -> Bool {
|
||||
@@ -1415,9 +1443,18 @@ private extension SceneCoordinator {
|
||||
navController.toolbar.tintColor = AppAssets.primaryAccentColor
|
||||
}
|
||||
|
||||
func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
|
||||
func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool, completion: (() -> Void)? = nil) {
|
||||
markArticlesWithUndo(Set(articles), statusKey: statusKey, flag: flag, directlyMarked: directlyMarked, completion: completion)
|
||||
}
|
||||
|
||||
func markArticlesWithUndo(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, directlyMarked: Bool, completion: (() -> Void)? = nil) {
|
||||
guard let undoManager = undoManager,
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager, completion: completion) else {
|
||||
let markReadCommand = MarkStatusCommand(initialArticles: articles,
|
||||
statusKey: statusKey,
|
||||
flag: flag,
|
||||
directlyMarked: directlyMarked,
|
||||
undoManager: undoManager,
|
||||
completion: completion) else {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
@@ -1935,6 +1972,7 @@ private extension SceneCoordinator {
|
||||
|
||||
func emptyTheTimeline() {
|
||||
if !articles.isEmpty {
|
||||
directlyMarkedAsUnreadArticles = Set<Article>()
|
||||
replaceArticles(with: Set<Article>(), animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user