Make progress on deleting and especially undoing delete.
This commit is contained in:
parent
2396a3bfca
commit
ef303c0c38
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// DeleteFromSidebarCommand.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 11/4/17.
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSCore
|
||||
import RSTree
|
||||
import Account
|
||||
import Data
|
||||
|
||||
final class DeleteFromSidebarCommand: UndoableCommand {
|
||||
|
||||
private struct ActionName {
|
||||
static private let deleteFeed = NSLocalizedString("Delete Feed", comment: "command")
|
||||
static private let deleteFeeds = NSLocalizedString("Delete Feeds", comment: "command")
|
||||
static private let deleteFolder = NSLocalizedString("Delete Folder", comment: "command")
|
||||
static private let deleteFolders = NSLocalizedString("Delete Folders", comment: "command")
|
||||
static private let deleteFeedsAndFolders = NSLocalizedString("Delete Feeds and Folders", comment: "command")
|
||||
}
|
||||
|
||||
let undoActionName: String
|
||||
var redoActionName: String {
|
||||
get {
|
||||
return undoActionName
|
||||
}
|
||||
}
|
||||
|
||||
let undoManager: UndoManager
|
||||
|
||||
init?(nodesToDelete: [Node], undoManager: UndoManager) {
|
||||
|
||||
var numberOfFeeds = 0
|
||||
var numberOfFolders = 0
|
||||
|
||||
for node in nodesToDelete {
|
||||
if let _ = node.representedObject as? Feed {
|
||||
numberOfFeeds += 1
|
||||
}
|
||||
else if let _ = node.representedObject as? Folder {
|
||||
numberOfFolders += 1
|
||||
}
|
||||
else {
|
||||
return nil // Delete only Feeds and Folders.
|
||||
}
|
||||
}
|
||||
|
||||
if numberOfFeeds < 1 && numberOfFolders < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if numberOfFolders < 1 {
|
||||
self.undoActionName = numberOfFeeds == 1 ? ActionName.deleteFeed : ActionName.deleteFeeds
|
||||
}
|
||||
else if numberOfFeeds < 1 {
|
||||
self.undoActionName = numberOfFolders == 1 ? ActionName.deleteFolder : ActionName.deleteFolders
|
||||
}
|
||||
else {
|
||||
self.undoActionName = ActionName.deleteFeedsAndFolders
|
||||
}
|
||||
|
||||
self.undoManager = undoManager
|
||||
}
|
||||
|
||||
func perform() {
|
||||
|
||||
registerUndo()
|
||||
}
|
||||
|
||||
func undo() {
|
||||
|
||||
registerRedo()
|
||||
}
|
||||
|
||||
static func canDelete(_ nodes: [Node]) -> Bool {
|
||||
|
||||
// Return true if all nodes are feeds and folders.
|
||||
// Any other type: return false.
|
||||
|
||||
if nodes.isEmpty {
|
||||
return false
|
||||
}
|
||||
|
||||
for node in nodes {
|
||||
if let _ = node.representedObject as? Feed {
|
||||
continue
|
||||
}
|
||||
if let _ = node.representedObject as? Folder {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Remember as much as we can now about the items being deleted,
|
||||
// so they can be restored to the correct place.
|
||||
|
||||
private struct SidebarItemSpecifier {
|
||||
|
||||
weak var account: Account?
|
||||
let node: Node
|
||||
let folder: Folder?
|
||||
let feed: Feed?
|
||||
let path: ContainerPath
|
||||
|
||||
init?(node: Node) {
|
||||
|
||||
self.node = node
|
||||
|
||||
var account: Account?
|
||||
if let feed = node.representedObject as? Feed {
|
||||
self.feed = feed
|
||||
account = feed.account
|
||||
}
|
||||
else if let folder = node.representedObject as? Folder {
|
||||
self.folder = folder
|
||||
account = folder.account
|
||||
}
|
||||
|
||||
guard let account = account else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.account = account
|
||||
self.path = SidebarPath(account: account, node: node)
|
||||
}
|
||||
}
|
||||
|
|
@ -82,6 +82,7 @@
|
|||
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */; };
|
||||
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */; };
|
||||
84B99C6B1FAE370B00ECDEDB /* FeedListFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */; };
|
||||
84B99C9D1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */; };
|
||||
84BB4B771F11753300858766 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; };
|
||||
84BB4B781F11753300858766 /* Data.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
|
||||
|
@ -452,6 +453,7 @@
|
|||
84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListTreeControllerDelegate.swift; sourceTree = "<group>"; };
|
||||
84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListFolder.swift; sourceTree = "<group>"; };
|
||||
84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListFeed.swift; sourceTree = "<group>"; };
|
||||
84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteFromSidebarCommand.swift; sourceTree = "<group>"; };
|
||||
84BB4B611F1174D400858766 /* Data.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Data.xcodeproj; path = Frameworks/Data/Data.xcodeproj; sourceTree = "<group>"; };
|
||||
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = "<group>"; };
|
||||
84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = "<group>"; };
|
||||
|
@ -542,6 +544,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */,
|
||||
84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */,
|
||||
);
|
||||
path = Commands;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1237,6 +1240,7 @@
|
|||
849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */,
|
||||
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */,
|
||||
849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */,
|
||||
84B99C9D1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift in Sources */,
|
||||
849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */,
|
||||
849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */,
|
||||
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */,
|
||||
|
|
|
@ -201,6 +201,17 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
return folder
|
||||
}
|
||||
|
||||
public func ensureFolder(withFolderNames folderNames: [String]) -> Folder? {
|
||||
|
||||
// TODO: support subfolders, maybe, some day.
|
||||
// Since we don’t, just take the last name and make sure there’s a Folder.
|
||||
|
||||
guard let folderName = folderNames.last else {
|
||||
return nil
|
||||
}
|
||||
return ensureFolder(with: folderName)
|
||||
}
|
||||
|
||||
public func canAddFeed(_ feed: Feed, to folder: Folder?) -> Bool {
|
||||
|
||||
// If folder is nil, then it should go at the top level.
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E77531F6F00E300A165E2 /* AccountManager.swift */; };
|
||||
848935001F62484F00CEBD24 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 848934F61F62484F00CEBD24 /* Account.framework */; };
|
||||
848935051F62485000CEBD24 /* AccountTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935041F62485000CEBD24 /* AccountTests.swift */; };
|
||||
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */; };
|
||||
84C3654A1F899F3B001EC85C /* CombinedRefreshProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */; };
|
||||
84C8B3F41F89DE430053CCA6 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
@ -114,6 +115,7 @@
|
|||
848935041F62485000CEBD24 /* AccountTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTests.swift; sourceTree = "<group>"; };
|
||||
848935061F62485000CEBD24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
848935101F62486800CEBD24 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
|
||||
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerPath.swift; sourceTree = "<group>"; };
|
||||
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedRefreshProgress.swift; sourceTree = "<group>"; };
|
||||
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtensions.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -205,6 +207,7 @@
|
|||
848935101F62486800CEBD24 /* Account.swift */,
|
||||
841974241F6DDCE4006346C4 /* AccountDelegate.swift */,
|
||||
841974001F6DD1EC006346C4 /* Folder.swift */,
|
||||
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */,
|
||||
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */,
|
||||
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */,
|
||||
8419740D1F6DD25F006346C4 /* Container.swift */,
|
||||
|
@ -434,6 +437,7 @@
|
|||
846E77451F6EF9B900A165E2 /* Container.swift in Sources */,
|
||||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
||||
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
|
||||
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */,
|
||||
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */,
|
||||
841974011F6DD1EC006346C4 /* Folder.swift in Sources */,
|
||||
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,
|
||||
|
|
|
@ -33,6 +33,7 @@ public protocol Container {
|
|||
func existingFeed(with feedID: String) -> Feed?
|
||||
func existingFeed(withURL url: String) -> Feed?
|
||||
func existingFolder(with name: String) -> Folder?
|
||||
func existingFolder(withID: Int) -> Folder?
|
||||
|
||||
func postChildrenDidChangeNotification()
|
||||
}
|
||||
|
@ -154,6 +155,23 @@ public extension Container {
|
|||
return nil
|
||||
}
|
||||
|
||||
func existingFolder(withID folderID: Int) -> Folder? {
|
||||
|
||||
for child in children {
|
||||
|
||||
if let folder = child as? Folder {
|
||||
if folder.folderID == folderID {
|
||||
return folder
|
||||
}
|
||||
if let subFolder = folder.existingFolder(withID: folderID) {
|
||||
return subFolder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func postChildrenDidChangeNotification() {
|
||||
|
||||
NotificationCenter.default.post(name: .ChildrenDidChange, object: self)
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// ContainerPath.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Brent Simmons on 11/4/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Used to identify the parent of an object.
|
||||
// Mainly used with deleting objects and undo/redo.
|
||||
// Especially redo. The idea is to put something back in the right place.
|
||||
|
||||
public struct ContainerPath {
|
||||
|
||||
private weak var account: Account?
|
||||
private let names: [String] // empty if top-level of account
|
||||
private let folderID: Int? // nil if top-level
|
||||
private let isTopLevel: Bool
|
||||
|
||||
// folders should be from top-level down, as in ["Cats", "Tabbies"]
|
||||
|
||||
public init(account: Account, folders: [Folder]) {
|
||||
|
||||
self.account = account
|
||||
self.names = folders.map { $0.nameForDisplay }
|
||||
self.isTopLevel = folders.isEmpty
|
||||
|
||||
if let lastFolder = folders.last {
|
||||
self.folderID = lastFolder.folderID
|
||||
}
|
||||
}
|
||||
|
||||
public func resolveContainer() -> Container? {
|
||||
|
||||
// The only time it should fail is if the account no longer exists.
|
||||
// Otherwise the worst-case scenario is that it will create Folders if needed.
|
||||
|
||||
guard let account = account else {
|
||||
return nil
|
||||
}
|
||||
if isTopLevel {
|
||||
return account
|
||||
}
|
||||
|
||||
if let folderID = folderID, let folder = account.existingFolder(withID: folderID) {
|
||||
return folder
|
||||
}
|
||||
|
||||
return account.ensureFolder(withFolderNames: names)
|
||||
}
|
||||
}
|
|
@ -12,11 +12,12 @@ import RSCore
|
|||
|
||||
public final class Folder: DisplayNameProvider, Container, UnreadCountProvider, Hashable {
|
||||
|
||||
|
||||
public weak var account: Account?
|
||||
public var children = [AnyObject]()
|
||||
var name: String?
|
||||
static let untitledName = NSLocalizedString("Untitled ƒ", comment: "Folder name")
|
||||
public let folderID: Int // not saved: per-run only
|
||||
static var incrementingID = 0
|
||||
public let hashValue: Int
|
||||
|
||||
// MARK: - Fetching Articles
|
||||
|
@ -54,7 +55,11 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
|
|||
|
||||
self.account = account
|
||||
self.name = name
|
||||
self.hashValue = name?.hashValue ?? Folder.untitledName.hashValue
|
||||
|
||||
let folderID = Folder.incrementingID
|
||||
Folder.incrementingID += 1
|
||||
self.folderID = folderID
|
||||
self.hashValue = folderID
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue