 	}
 
 	// MARK: - API
+
+	func deepLinkRevealAndSelect(for userInfo: [AnyHashable : Any]) {
+		guard let accountNode = findAccountNode(userInfo), let feedNode = findFeedNode(userInfo, beginningAt: accountNode) else {
+			return
+		}
+		revealAndSelectRepresentedObject(feedNode.representedObject)
+	}
+
+	func toggleReadFilter() {
+		if treeControllerDelegate.isReadFiltered {
+			treeControllerDelegate.isReadFiltered = false
+		} else {
+			treeControllerDelegate.isReadFiltered = true
+		}
+		rebuildTreeAndRestoreSelection()
+	}
+
+}
+
+// MARK: - NSUserInterfaceValidations
+
+extension SidebarViewController: NSUserInterfaceValidations {
+
+	func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
+		if item.action == #selector(copy(_:)) {
+			return NSPasteboard.general.canCopyAtLeastOneObject(selectedObjects)
+		}
+		return true
+	}
+}
+
+//MARK: - Private
+
+private extension SidebarViewController {
+	
+	var accountNodes: [Account] {
+		return treeController.rootNode.childNodes.compactMap { $0.representedObject as? Account }
+	}
+	
+	var selectedNodes: [Node] {
+		if let nodes = outlineView.selectedItems as? [Node] {
+			return nodes
+		}
+		return [Node]()
+	}
+	
+	var selectedFeeds: [Feed] {
+		selectedNodes.compactMap { $0.representedObject as? Feed }
+	}
+
+	var singleSelectedNode: Node? {
+		guard selectedNodes.count == 1 else {
+			return nil
+		}
+		return selectedNodes.first!
+	}
+
+	var singleSelectedWebFeed: WebFeed? {
+		guard let node = singleSelectedNode else {
+			return nil
+		}
+		return node.representedObject as? WebFeed
+	}
+	
+	func ensureFeedIsAvailableToSelect(_ feed: Feed) {
+		guard isReadFiltered else { return }
+		addToFilterExeptionsIfNecessary(feed)
+		addTreeControllerToFilterExceptions()
+		rebuildTreeAndRestoreSelection()
+	}
+
+	func addAllSelectedToFilterExceptions() {
+		selectedFeeds.forEach { addToFilterExeptionsIfNecessary($0) }
+	}
+	
+	func addToFilterExeptionsIfNecessary(_ feed: Feed?) {
+		if isReadFiltered, let feedID = feed?.feedID {
+			if feed is SmartFeed {
+				treeControllerDelegate.addFilterException(feedID)
+			} else if let folderFeed = feed as? Folder {
+				if folderFeed.account?.existingFolder(withID: folderFeed.folderID) != nil {
+					treeControllerDelegate.addFilterException(feedID)
+				}
+			} else if let webFeed = feed as? WebFeed {
+				if webFeed.account?.existingWebFeed(withWebFeedID: webFeed.webFeedID) != nil {
+					treeControllerDelegate.addFilterException(feedID)
+					addParentFolderToFilterExceptions(webFeed)
+				}
+			}
+		}
+	}
+	
+	func addParentFolderToFilterExceptions(_ feed: Feed) {
+		guard let node = treeController.rootNode.descendantNodeRepresentingObject(feed as AnyObject),
+			let folder = node.parent?.representedObject as? Folder,
+			let folderFeedID = folder.feedID else {
+				return
+		}
+		
+		treeControllerDelegate.addFilterException(folderFeedID)
+	}
+	
 
 	func queueRebuildTreeAndRestoreSelection() {
 		rebuildTreeAndRestoreSelectionQueue.add(self, #selector(rebuildTreeAndRestoreSelection))

	}

	@IBAction func openInBrowser(_ sender: Any?) {
		guard let feed = singleSelectedFeed, let homePageURL = feed.homePageURL else {
			return
		}
		Browser.open(homePageURL)
	}

	@IBAction func gotoToday(_ sender: Any?) {
		outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.todayFeed, treeController)
		focus()
	}

	@IBAction func gotoAllUnread(_ sender: Any?) {
		outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.unreadFeed, treeController)
		focus()
	}

	@IBAction func gotoStarred(_ sender: Any?) {
		outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.starredFeed, treeController)
		focus()
	}

		
	}
	
	func rebuildTreeAndReloadDataIfNeeded() {
		if !animatingChanges && !BatchUpdate.shared.isPerforming {
			addAllSelectedToFilterExceptions()
			treeController.rebuild()
			treeControllerDelegate.resetFilterExceptions()
			outlineView.reloadData()
		}
	}
	
	func addTreeControllerToFilterExceptions() {
		treeController.visitNodes(addTreeControllerToFilterExceptionsVisitor(node:))
	}

	func addTreeControllerToFilterExceptionsVisitor(node: Node) {
		if let feed = node.representedObject as? Feed, let feedID = feed.feedID {
			treeControllerDelegate.addFilterException(feedID)
		}
	}

