mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-01-31 11:14:54 +01:00
Merge branch 'main' of https://github.com/Ranchero-Software/NetNewsWire into main
This commit is contained in:
commit
e9f7cdb0c6
@ -28,6 +28,11 @@ public enum AccountBehavior: Equatable {
|
||||
*/
|
||||
case disallowFeedInRootFolder
|
||||
|
||||
/**
|
||||
Account doesn't support a feed being in more than one folder.
|
||||
*/
|
||||
case disallowFeedInMultipleFolders
|
||||
|
||||
/**
|
||||
Account doesn't support folders
|
||||
*/
|
||||
|
@ -29,7 +29,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ReaderAPI")
|
||||
|
||||
var behaviors: AccountBehaviors {
|
||||
var behaviors: AccountBehaviors = [.disallowOPMLImports]
|
||||
var behaviors: AccountBehaviors = [.disallowOPMLImports, .disallowFeedInMultipleFolders]
|
||||
if variant == .freshRSS {
|
||||
behaviors.append(.disallowFeedInRootFolder)
|
||||
}
|
||||
@ -186,8 +186,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
caller.retrieveItemIDs(type: .unread) { result in
|
||||
switch result {
|
||||
case .success(let articleIDs):
|
||||
self.syncArticleReadState(account: account, articleIDs: articleIDs)
|
||||
group.leave()
|
||||
self.syncArticleReadState(account: account, articleIDs: articleIDs) {
|
||||
group.leave()
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.info, log: self.log, "Retrieving unread entries failed: %@.", error.localizedDescription)
|
||||
group.leave()
|
||||
@ -199,8 +200,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
caller.retrieveItemIDs(type: .starred) { result in
|
||||
switch result {
|
||||
case .success(let articleIDs):
|
||||
self.syncArticleStarredState(account: account, articleIDs: articleIDs)
|
||||
group.leave()
|
||||
self.syncArticleStarredState(account: account, articleIDs: articleIDs) {
|
||||
group.leave()
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription)
|
||||
group.leave()
|
||||
@ -918,7 +920,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
let group = DispatchGroup()
|
||||
|
||||
let articleIDs = Array(fetchedArticleIDs)
|
||||
let chunkedArticleIDs = articleIDs.chunked(into: 100)
|
||||
let chunkedArticleIDs = articleIDs.chunked(into: 150)
|
||||
|
||||
self.refreshProgress.addToNumberOfTasksAndRemaining(chunkedArticleIDs.count - 1)
|
||||
|
||||
@ -1001,7 +1003,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func syncArticleReadState(account: Account, articleIDs: [String]?) {
|
||||
func syncArticleReadState(account: Account, articleIDs: [String]?, completion: @escaping (() -> Void)) {
|
||||
guard let articleIDs = articleIDs else {
|
||||
return
|
||||
}
|
||||
@ -1016,13 +1018,25 @@ private extension ReaderAPIAccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
// Mark articles as unread
|
||||
let deltaUnreadArticleIDs = updatableReaderUnreadArticleIDs.subtracting(currentUnreadArticleIDs)
|
||||
account.markAsUnread(deltaUnreadArticleIDs)
|
||||
group.enter()
|
||||
account.markAsUnread(deltaUnreadArticleIDs) { _ in
|
||||
group.leave()
|
||||
}
|
||||
|
||||
// Mark articles as read
|
||||
let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(updatableReaderUnreadArticleIDs)
|
||||
account.markAsRead(deltaReadArticleIDs)
|
||||
group.enter()
|
||||
account.markAsRead(deltaReadArticleIDs) { _ in
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1037,7 +1051,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func syncArticleStarredState(account: Account, articleIDs: [String]?) {
|
||||
func syncArticleStarredState(account: Account, articleIDs: [String]?, completion: @escaping (() -> Void)) {
|
||||
guard let articleIDs = articleIDs else {
|
||||
return
|
||||
}
|
||||
@ -1052,13 +1066,25 @@ private extension ReaderAPIAccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
// Mark articles as starred
|
||||
let deltaStarredArticleIDs = updatableReaderUnreadArticleIDs.subtracting(currentStarredArticleIDs)
|
||||
account.markAsStarred(deltaStarredArticleIDs)
|
||||
group.enter()
|
||||
account.markAsStarred(deltaStarredArticleIDs) { _ in
|
||||
group.leave()
|
||||
}
|
||||
|
||||
// Mark articles as unstarred
|
||||
let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(updatableReaderUnreadArticleIDs)
|
||||
account.markAsUnstarred(deltaUnstarredArticleIDs)
|
||||
group.enter()
|
||||
account.markAsUnstarred(deltaUnstarredArticleIDs) { _ in
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -631,52 +631,76 @@ private extension SidebarOutlineDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool {
|
||||
return violatesAccountSpecificBehavior(parentNode, Set([draggedFeed]))
|
||||
func violatesAccountSpecificBehavior(_ dropTargetNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool {
|
||||
return violatesAccountSpecificBehavior(dropTargetNode, Set([draggedFeed]))
|
||||
}
|
||||
|
||||
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
if violatesDisallowFeedInRootFolder(parentNode) {
|
||||
func violatesAccountSpecificBehavior(_ dropTargetNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
if violatesDisallowFeedInRootFolder(dropTargetNode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if violatesDisallowFeedCopyInRootFolder(parentNode, draggedFeeds) {
|
||||
if violatesDisallowFeedCopyInRootFolder(dropTargetNode, draggedFeeds) {
|
||||
return true
|
||||
}
|
||||
|
||||
if violatesDisallowFeedInMultipleFolders(dropTargetNode, draggedFeeds) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func violatesDisallowFeedInRootFolder(_ parentNode: Node) -> Bool {
|
||||
guard let parentAccount = nodeAccount(parentNode), parentAccount.behaviors.contains(.disallowFeedInRootFolder) else {
|
||||
func violatesDisallowFeedInRootFolder(_ dropTargetNode: Node) -> Bool {
|
||||
guard let parentAccount = nodeAccount(dropTargetNode), parentAccount.behaviors.contains(.disallowFeedInRootFolder) else {
|
||||
return false
|
||||
}
|
||||
|
||||
if parentNode.representedObject is Account {
|
||||
if dropTargetNode.representedObject is Account {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func violatesDisallowFeedCopyInRootFolder(_ parentNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
guard let parentAccount = nodeAccount(parentNode), parentAccount.behaviors.contains(.disallowFeedCopyInRootFolder) else {
|
||||
func violatesDisallowFeedCopyInRootFolder(_ dropTargetNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
guard let dropTargetAccount = nodeAccount(dropTargetNode), dropTargetAccount.behaviors.contains(.disallowFeedCopyInRootFolder) else {
|
||||
return false
|
||||
}
|
||||
|
||||
for draggedFeed in draggedFeeds {
|
||||
if parentAccount.accountID != draggedFeed.accountID {
|
||||
if dropTargetAccount.accountID != draggedFeed.accountID {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if parentNode.representedObject is Account && (NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false) {
|
||||
if dropTargetNode.representedObject is Account && (NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func violatesDisallowFeedInMultipleFolders(_ dropTargetNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
guard let dropTargetAccount = nodeAccount(dropTargetNode), dropTargetAccount.behaviors.contains(.disallowFeedInMultipleFolders) else {
|
||||
return false
|
||||
}
|
||||
|
||||
for draggedFeed in draggedFeeds {
|
||||
if dropTargetAccount.accountID == draggedFeed.accountID {
|
||||
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if dropTargetAccount.hasWebFeed(withURL: draggedFeed.url) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func indexWhereDraggedFeedWouldAppear(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Int {
|
||||
let draggedFeedWrapper = PasteboardFeedObjectWrapper(pasteboardFeed: draggedFeed)
|
||||
let draggedFeedNode = Node(representedObject: draggedFeedWrapper, parent: nil)
|
||||
|
@ -18,6 +18,7 @@ final class MasterFeedTableViewIdentifier: NSObject, NSCopying {
|
||||
|
||||
let isEditable: Bool
|
||||
let isPsuedoFeed: Bool
|
||||
let isAccount: Bool
|
||||
let isFolder: Bool
|
||||
let isWebFeed: Bool
|
||||
|
||||
@ -26,6 +27,19 @@ final class MasterFeedTableViewIdentifier: NSObject, NSCopying {
|
||||
let unreadCount: Int
|
||||
let childCount: Int
|
||||
|
||||
var account: Account? {
|
||||
if isAccount, let containerID = containerID {
|
||||
return AccountManager.shared.existingContainer(with: containerID) as? Account
|
||||
}
|
||||
if isFolder, let parentContainerID = parentContainerID {
|
||||
return AccountManager.shared.existingContainer(with: parentContainerID) as? Account
|
||||
}
|
||||
if isWebFeed, let feedID = feedID {
|
||||
return (AccountManager.shared.existingFeed(with: feedID) as? WebFeed)?.account
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
init(node: Node, unreadCount: Int) {
|
||||
let feed = node.representedObject as! Feed
|
||||
self.feedID = feed.feedID
|
||||
@ -34,6 +48,7 @@ final class MasterFeedTableViewIdentifier: NSObject, NSCopying {
|
||||
|
||||
self.isEditable = !(node.representedObject is PseudoFeed)
|
||||
self.isPsuedoFeed = node.representedObject is PseudoFeed
|
||||
self.isAccount = node.representedObject is Account
|
||||
self.isFolder = node.representedObject is Folder
|
||||
self.isWebFeed = node.representedObject is WebFeed
|
||||
self.nameForDisplay = feed.nameForDisplay
|
||||
|
@ -22,10 +22,24 @@ extension MasterFeedViewController: UITableViewDropDelegate {
|
||||
destIndexPath.section > 0,
|
||||
tableView.hasActiveDrag,
|
||||
let destIdentifier = dataSource.itemIdentifier(for: destIndexPath),
|
||||
let destAccount = destIdentifier.account,
|
||||
let destCell = tableView.cellForRow(at: destIndexPath) else {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
// Validate account specific behaviors...
|
||||
if destAccount.behaviors.contains(.disallowFeedInRootFolder) && destIdentifier.isAccount {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
if destAccount.behaviors.contains(.disallowFeedInMultipleFolders),
|
||||
let sourceFeedID = (session.localDragSession?.items.first?.localObject as? MasterFeedTableViewIdentifier)?.feedID,
|
||||
let sourceWebFeed = AccountManager.shared.existingFeed(with: sourceFeedID) as? WebFeed,
|
||||
sourceWebFeed.account?.accountID != destAccount.accountID && destAccount.hasWebFeed(withURL: sourceWebFeed.url) {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
// Determine the correct drop proposal
|
||||
if destIdentifier.isFolder {
|
||||
if session.location(in: destCell).y >= 0 {
|
||||
return UITableViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath)
|
||||
|
Loading…
x
Reference in New Issue
Block a user