This commit is contained in:
Brent Simmons 2020-10-31 23:10:22 -07:00
commit e9f7cdb0c6
5 changed files with 108 additions and 24 deletions

View File

@ -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
*/

View File

@ -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)
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)
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()
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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)