mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2024-12-20 06:30:31 +01:00
Make undo deleting feeds/folders work.
This commit is contained in:
parent
57296279e4
commit
9818278c9b
@ -14,7 +14,7 @@ import Data
|
|||||||
|
|
||||||
final class DeleteFromSidebarCommand: UndoableCommand {
|
final class DeleteFromSidebarCommand: UndoableCommand {
|
||||||
|
|
||||||
let undoManager: UndoManager
|
let undoManager: UndoManager
|
||||||
let undoActionName: String
|
let undoActionName: String
|
||||||
var redoActionName: String {
|
var redoActionName: String {
|
||||||
get {
|
get {
|
||||||
@ -22,40 +22,40 @@ final class DeleteFromSidebarCommand: UndoableCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let itemSpecifiers: [SidebarItemSpecifier]
|
private let itemSpecifiers: [SidebarItemSpecifier]
|
||||||
|
|
||||||
init?(nodesToDelete: [Node], undoManager: UndoManager) {
|
init?(nodesToDelete: [Node], undoManager: UndoManager) {
|
||||||
|
|
||||||
guard DeleteFromSidebarCommand.canDelete(nodesToDelete) else {
|
guard DeleteFromSidebarCommand.canDelete(nodesToDelete) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let actionName = DeleteActionName.name(for: nodesToDelete) else {
|
guard let actionName = DeleteActionName.name(for: nodesToDelete) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
self.undoActionName = actionName
|
self.undoActionName = actionName
|
||||||
self.undoManager = undoManager
|
self.undoManager = undoManager
|
||||||
|
|
||||||
let itemSpecifiers = nodesToDelete.flatMap{ SidebarItemSpecifier(node: $0) }
|
let itemSpecifiers = nodesToDelete.flatMap{ SidebarItemSpecifier(node: $0) }
|
||||||
guard !itemSpecifiers.isEmpty else {
|
guard !itemSpecifiers.isEmpty else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self.itemSpecifiers = itemSpecifiers
|
self.itemSpecifiers = itemSpecifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
func perform() {
|
func perform() {
|
||||||
|
|
||||||
BatchUpdate.shared.perform {
|
BatchUpdate.shared.perform {
|
||||||
itemSpecifiers.forEach { $0.delete() }
|
itemSpecifiers.forEach { $0.delete() }
|
||||||
}
|
}
|
||||||
registerUndo()
|
registerUndo()
|
||||||
}
|
}
|
||||||
|
|
||||||
func undo() {
|
func undo() {
|
||||||
|
|
||||||
BatchUpdate.shared.perform {
|
BatchUpdate.shared.perform {
|
||||||
|
itemSpecifiers.forEach { $0.restore() }
|
||||||
}
|
}
|
||||||
registerRedo()
|
registerRedo()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,133 +88,176 @@ final class DeleteFromSidebarCommand: UndoableCommand {
|
|||||||
private struct SidebarItemSpecifier {
|
private struct SidebarItemSpecifier {
|
||||||
|
|
||||||
private weak var account: Account?
|
private weak var account: Account?
|
||||||
private let parentFolder: Folder?
|
private let parentFolder: Folder?
|
||||||
private let folder: Folder?
|
private let folder: Folder?
|
||||||
private let feed: Feed?
|
private let feed: Feed?
|
||||||
private let path: ContainerPath
|
private let path: ContainerPath
|
||||||
|
|
||||||
private var container: Container? {
|
private var container: Container? {
|
||||||
get {
|
get {
|
||||||
if let parentFolder = parentFolder {
|
if let parentFolder = parentFolder {
|
||||||
return parentFolder
|
return parentFolder
|
||||||
}
|
}
|
||||||
if let account = account {
|
if let account = account {
|
||||||
return account
|
return account
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(node: Node) {
|
init?(node: Node) {
|
||||||
|
|
||||||
var account: Account?
|
var account: Account?
|
||||||
|
|
||||||
self.parentFolder = node.parentFolder()
|
|
||||||
|
|
||||||
if let feed = node.representedObject as? Feed {
|
self.parentFolder = node.parentFolder()
|
||||||
|
|
||||||
|
if let feed = node.representedObject as? Feed {
|
||||||
self.feed = feed
|
self.feed = feed
|
||||||
self.folder = nil
|
self.folder = nil
|
||||||
account = feed.account
|
account = feed.account
|
||||||
}
|
}
|
||||||
else if let folder = node.representedObject as? Folder {
|
else if let folder = node.representedObject as? Folder {
|
||||||
self.feed = nil
|
self.feed = nil
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
account = folder.account
|
account = folder.account
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if account == nil {
|
if account == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
self.account = account!
|
self.account = account!
|
||||||
self.path = ContainerPath(account: account!, folders: node.containingFolders())
|
self.path = ContainerPath(account: account!, folders: node.containingFolders())
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete() {
|
||||||
|
|
||||||
|
guard let container = container else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let feed = feed {
|
||||||
|
container.deleteFeed(feed)
|
||||||
|
}
|
||||||
|
else if let folder = folder {
|
||||||
|
container.deleteFolder(folder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func restore() {
|
||||||
|
|
||||||
|
if let feed = feed {
|
||||||
|
restoreFeed()
|
||||||
|
}
|
||||||
|
else if let folder = folder {
|
||||||
|
restoreFolder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func restoreFeed() {
|
||||||
|
|
||||||
|
guard let account = account, let feed = feed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let feedToUse = uniquedFeed(feed)
|
||||||
|
account.addFeed(feedToUse, to: resolvedFolder())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func restoreFolder() {
|
||||||
|
|
||||||
|
guard let account = account, let folder = folder else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account.addFolder(folder, to: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func uniquedFeed(_ feed: Feed) -> Feed {
|
||||||
|
|
||||||
|
// A Feed may appear in multiple places in a given account,
|
||||||
|
// but it’s best if they’re the same Feed instance.
|
||||||
|
// Usually this will return the same Feed that was passed-in,
|
||||||
|
// but not necessarily always.
|
||||||
|
|
||||||
|
return account?.existingFeed(with: feed.feedID) ?? feed
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resolvedFolder() -> Folder? {
|
||||||
|
|
||||||
|
return path.resolveContainer() as? Folder
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete() {
|
|
||||||
|
|
||||||
guard let container = container else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let feed = feed {
|
|
||||||
container.deleteFeed(feed)
|
|
||||||
}
|
|
||||||
else if let folder = folder {
|
|
||||||
container.deleteFolder(folder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension Node {
|
private extension Node {
|
||||||
|
|
||||||
func parentFolder() -> Folder? {
|
func parentFolder() -> Folder? {
|
||||||
|
|
||||||
guard let parentNode = self.parent else {
|
guard let parentNode = self.parent else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if parentNode.isRoot {
|
if parentNode.isRoot {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if let folder = parentNode.representedObject as? Folder {
|
if let folder = parentNode.representedObject as? Folder {
|
||||||
return folder
|
return folder
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func containingFolders() -> [Folder] {
|
func containingFolders() -> [Folder] {
|
||||||
|
|
||||||
var nomad = self.parent
|
var nomad = self.parent
|
||||||
var folders = [Folder]()
|
var folders = [Folder]()
|
||||||
|
|
||||||
while nomad != nil {
|
while nomad != nil {
|
||||||
if let folder = nomad!.representedObject as? Folder {
|
if let folder = nomad!.representedObject as? Folder {
|
||||||
folders += [folder]
|
folders += [folder]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
nomad = nomad!.parent
|
nomad = nomad!.parent
|
||||||
}
|
}
|
||||||
|
|
||||||
return folders.reversed()
|
return folders.reversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct DeleteActionName {
|
private struct DeleteActionName {
|
||||||
|
|
||||||
private static let deleteFeed = NSLocalizedString("Delete Feed", comment: "command")
|
private static let deleteFeed = NSLocalizedString("Delete Feed", comment: "command")
|
||||||
private static let deleteFeeds = NSLocalizedString("Delete Feeds", comment: "command")
|
private static let deleteFeeds = NSLocalizedString("Delete Feeds", comment: "command")
|
||||||
private static let deleteFolder = NSLocalizedString("Delete Folder", comment: "command")
|
private static let deleteFolder = NSLocalizedString("Delete Folder", comment: "command")
|
||||||
private static let deleteFolders = NSLocalizedString("Delete Folders", comment: "command")
|
private static let deleteFolders = NSLocalizedString("Delete Folders", comment: "command")
|
||||||
private static let deleteFeedsAndFolders = NSLocalizedString("Delete Feeds and Folders", comment: "command")
|
private static let deleteFeedsAndFolders = NSLocalizedString("Delete Feeds and Folders", comment: "command")
|
||||||
|
|
||||||
static func name(for nodes: [Node]) -> String? {
|
static func name(for nodes: [Node]) -> String? {
|
||||||
|
|
||||||
var numberOfFeeds = 0
|
var numberOfFeeds = 0
|
||||||
var numberOfFolders = 0
|
var numberOfFolders = 0
|
||||||
|
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
if let _ = node.representedObject as? Feed {
|
if let _ = node.representedObject as? Feed {
|
||||||
numberOfFeeds += 1
|
numberOfFeeds += 1
|
||||||
}
|
}
|
||||||
else if let _ = node.representedObject as? Folder {
|
else if let _ = node.representedObject as? Folder {
|
||||||
numberOfFolders += 1
|
numberOfFolders += 1
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil // Delete only Feeds and Folders.
|
return nil // Delete only Feeds and Folders.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if numberOfFolders < 1 {
|
if numberOfFolders < 1 {
|
||||||
return numberOfFeeds == 1 ? deleteFeed : deleteFeeds
|
return numberOfFeeds == 1 ? deleteFeed : deleteFeeds
|
||||||
}
|
}
|
||||||
if numberOfFeeds < 1 {
|
if numberOfFeeds < 1 {
|
||||||
return numberOfFolders == 1 ? deleteFolder : deleteFolders
|
return numberOfFolders == 1 ? deleteFolder : deleteFolders
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleteFeedsAndFolders
|
return deleteFeedsAndFolders
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import RSCore
|
|||||||
TreeController(delegate: treeControllerDelegate)
|
TreeController(delegate: treeControllerDelegate)
|
||||||
}()
|
}()
|
||||||
var undoableCommands = [UndoableCommand]()
|
var undoableCommands = [UndoableCommand]()
|
||||||
|
private var animatingChanges = false
|
||||||
|
|
||||||
//MARK: NSViewController
|
//MARK: NSViewController
|
||||||
|
|
||||||
@ -79,11 +80,13 @@ import RSCore
|
|||||||
|
|
||||||
let selectedRows = outlineView.selectedRowIndexes
|
let selectedRows = outlineView.selectedRowIndexes
|
||||||
|
|
||||||
|
animatingChanges = true
|
||||||
outlineView.beginUpdates()
|
outlineView.beginUpdates()
|
||||||
outlineView.removeItems(at: selectedRows, inParent: nil, withAnimation: [.slideDown])
|
outlineView.removeItems(at: selectedRows, inParent: nil, withAnimation: [.slideDown])
|
||||||
outlineView.endUpdates()
|
outlineView.endUpdates()
|
||||||
|
|
||||||
runCommand(deleteCommand)
|
runCommand(deleteCommand)
|
||||||
|
animatingChanges = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Navigation
|
// MARK: Navigation
|
||||||
@ -170,7 +173,7 @@ private extension SidebarViewController {
|
|||||||
|
|
||||||
func rebuildTreeAndReloadDataIfNeeded() {
|
func rebuildTreeAndReloadDataIfNeeded() {
|
||||||
|
|
||||||
if !BatchUpdate.shared.isPerforming {
|
if !animatingChanges && !BatchUpdate.shared.isPerforming {
|
||||||
treeController.rebuild()
|
treeController.rebuild()
|
||||||
outlineView.reloadData()
|
outlineView.reloadData()
|
||||||
}
|
}
|
||||||
|
@ -276,7 +276,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||||||
@discardableResult
|
@discardableResult
|
||||||
public func addFolder(_ folder: Folder, to parentFolder: Folder?) -> Bool {
|
public func addFolder(_ folder: Folder, to parentFolder: Folder?) -> Bool {
|
||||||
|
|
||||||
return false // TODO
|
// TODO: support subfolders, maybe, some day, if one of the sync systems
|
||||||
|
// supports subfolders. But, for now, parentFolder is ignored.
|
||||||
|
|
||||||
|
if objectIsChild(folder) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
children += [folder]
|
||||||
|
postChildrenDidChangeNotification()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func importOPML(_ opmlDocument: RSOPMLDocument) {
|
public func importOPML(_ opmlDocument: RSOPMLDocument) {
|
||||||
|
Loading…
Reference in New Issue
Block a user