Merge remote-tracking branch 'brentsimmons/master'

This commit is contained in:
Olof Hellman 2018-02-08 00:12:33 -08:00
commit dcd2ee94f9
8 changed files with 184 additions and 81 deletions

View File

@ -51,6 +51,7 @@
846E77421F6EF6A100A165E2 /* Database.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E77211F6EF5D100A165E2 /* Database.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 846E77421F6EF6A100A165E2 /* Database.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E77211F6EF5D100A165E2 /* Database.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */; }; 84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */; };
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; };
847FA121202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */; };
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; };
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; }; 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; };
849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; }; 849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; };
@ -544,6 +545,7 @@
84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkReadOrUnreadCommand.swift; sourceTree = "<group>"; }; 84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkReadOrUnreadCommand.swift; sourceTree = "<group>"; };
8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; }; 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarContextualMenuDelegate.swift; sourceTree = "<group>"; };
848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = "<group>"; }; 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = "<group>"; };
849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = "<group>"; }; 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = "<group>"; };
849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = "<group>"; }; 849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = "<group>"; };
@ -924,6 +926,7 @@
845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */, 845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */,
849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */, 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */,
84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */, 84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */,
847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */,
844B5B6A1FEA224000C7C76A /* Keyboard */, 844B5B6A1FEA224000C7C76A /* Keyboard */,
845A29251FC928C7007B49E3 /* Cell */, 845A29251FC928C7007B49E3 /* Cell */,
84A37CB3201ECD610087C5AF /* Renaming */, 84A37CB3201ECD610087C5AF /* Renaming */,
@ -1832,6 +1835,7 @@
84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */, 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */,
D5907D972004B7EB005947E5 /* Account+Scriptability.swift in Sources */, D5907D972004B7EB005947E5 /* Account+Scriptability.swift in Sources */,
841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */, 841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */,
847FA121202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift in Sources */,
842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */, 842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */,
844B5B5B1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift in Sources */, 844B5B5B1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift in Sources */,
84DAEE321F870B390058304B /* DockBadge.swift in Sources */, 84DAEE321F870B390058304B /* DockBadge.swift in Sources */,

View File

@ -359,6 +359,7 @@
<outlet property="dataSource" destination="XML-A3-pDn" id="04v-0e-BM6"/> <outlet property="dataSource" destination="XML-A3-pDn" id="04v-0e-BM6"/>
<outlet property="delegate" destination="XML-A3-pDn" id="fPE-cv-p5c"/> <outlet property="delegate" destination="XML-A3-pDn" id="fPE-cv-p5c"/>
<outlet property="keyboardDelegate" destination="h5K-zR-cUa" id="BlT-aW-sea"/> <outlet property="keyboardDelegate" destination="h5K-zR-cUa" id="BlT-aW-sea"/>
<outlet property="menu" destination="p3f-EZ-sSD" id="KTA-tl-UrO"/>
</connections> </connections>
</outlineView> </outlineView>
</subviews> </subviews>
@ -441,11 +442,28 @@
</constraints> </constraints>
</view> </view>
<connections> <connections>
<outlet property="contextualMenuDelegate" destination="GIi-rR-u3i" id="HnU-AM-kBV"/>
<outlet property="gearMenuDelegate" destination="f3Y-bi-SLk" id="2on-6C-laM"/> <outlet property="gearMenuDelegate" destination="f3Y-bi-SLk" id="2on-6C-laM"/>
<outlet property="outlineView" destination="cnV-kg-Dn2" id="FVf-OT-E3h"/> <outlet property="outlineView" destination="cnV-kg-Dn2" id="FVf-OT-E3h"/>
</connections> </connections>
</viewController> </viewController>
<customObject id="Jih-JO-hIE" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="Jih-JO-hIE" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<menu id="p3f-EZ-sSD">
<items>
<menuItem title="Item 1" id="ZDH-CV-Y2s">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Item 2" id="1F7-qu-7oN">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Item 3" id="r9E-FO-GoU">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
<connections>
<outlet property="delegate" destination="GIi-rR-u3i" id="xCL-ce-fof"/>
</connections>
</menu>
<customObject id="f3Y-bi-SLk" customClass="SidebarGearMenuDelegate" customModule="Evergreen" customModuleProvider="target"> <customObject id="f3Y-bi-SLk" customClass="SidebarGearMenuDelegate" customModule="Evergreen" customModuleProvider="target">
<connections> <connections>
<outlet property="sidebarViewController" destination="XML-A3-pDn" id="Tw0-4c-1Go"/> <outlet property="sidebarViewController" destination="XML-A3-pDn" id="Tw0-4c-1Go"/>
@ -456,6 +474,11 @@
<outlet property="sidebarViewController" destination="XML-A3-pDn" id="kwd-Zc-HJm"/> <outlet property="sidebarViewController" destination="XML-A3-pDn" id="kwd-Zc-HJm"/>
</connections> </connections>
</customObject> </customObject>
<customObject id="GIi-rR-u3i" customClass="SidebarContextualMenuDelegate" customModule="Evergreen" customModuleProvider="target">
<connections>
<outlet property="sidebarViewController" destination="XML-A3-pDn" id="cFr-ow-5mf"/>
</connections>
</customObject>
</objects> </objects>
<point key="canvasLocation" x="-74" y="-186"/> <point key="canvasLocation" x="-74" y="-186"/>
</scene> </scene>
@ -682,12 +705,12 @@
<image name="NSMobileMe" width="32" height="32"/> <image name="NSMobileMe" width="32" height="32"/>
<image name="NSRefreshTemplate" width="11" height="15"/> <image name="NSRefreshTemplate" width="11" height="15"/>
<image name="NSShareTemplate" width="11" height="16"/> <image name="NSShareTemplate" width="11" height="16"/>
<image name="action" width="9.5" height="9.5"/> <image name="action" width="19" height="19"/>
<image name="markAllRead" width="11" height="9.5"/> <image name="markAllRead" width="22" height="19"/>
<image name="markRead" width="9.5" height="9.5"/> <image name="markRead" width="19" height="19"/>
<image name="newFolder" width="9.5" height="9.5"/> <image name="newFolder" width="19" height="19"/>
<image name="nextUnread" width="12" height="9.5"/> <image name="nextUnread" width="24" height="19"/>
<image name="openInBrowser" width="9.5" height="9.5"/> <image name="openInBrowser" width="19" height="19"/>
<image name="star" width="9.5" height="9.5"/> <image name="star" width="19" height="19"/>
</resources> </resources>
</document> </document>

View File

@ -153,6 +153,21 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
return canShowShareMenu() return canShowShareMenu()
} }
if item.action == #selector(toggleSidebar(_:)) {
guard let splitViewItem = sidebarSplitViewItem else {
return false
}
let sidebarIsShowing = !splitViewItem.isCollapsed
if let menuItem = item as? NSMenuItem {
let title = sidebarIsShowing ? NSLocalizedString("Hide Sidebar", comment: "Menu item") : NSLocalizedString("Show Sidebar", comment: "Menu item")
menuItem.title = title
}
return true
}
return true return true
} }
@ -412,58 +427,46 @@ extension MainWindowController : ScriptingMainWindowController {
private extension MainWindowController { private extension MainWindowController {
var splitViewController: NSSplitViewController? { var splitViewController: NSSplitViewController? {
get {
guard let viewController = contentViewController else { guard let viewController = contentViewController else {
return nil return nil
} }
return viewController.childViewControllers.first as? NSSplitViewController return viewController.childViewControllers.first as? NSSplitViewController
} }
}
var sidebarViewController: SidebarViewController? { var sidebarViewController: SidebarViewController? {
get {
return splitViewController?.splitViewItems[0].viewController as? SidebarViewController return splitViewController?.splitViewItems[0].viewController as? SidebarViewController
} }
}
var timelineViewController: TimelineViewController? { var timelineViewController: TimelineViewController? {
get {
return splitViewController?.splitViewItems[1].viewController as? TimelineViewController return splitViewController?.splitViewItems[1].viewController as? TimelineViewController
} }
var sidebarSplitViewItem: NSSplitViewItem? {
return splitViewController?.splitViewItems[0]
} }
var detailSplitViewItem: NSSplitViewItem? { var detailSplitViewItem: NSSplitViewItem? {
get {
return splitViewController?.splitViewItems[2] return splitViewController?.splitViewItems[2]
} }
}
var detailViewController: DetailViewController? { var detailViewController: DetailViewController? {
get {
return splitViewController?.splitViewItems[2].viewController as? DetailViewController return splitViewController?.splitViewItems[2].viewController as? DetailViewController
} }
}
var selectedArticles: [Article]? { var selectedArticles: [Article]? {
get {
return timelineViewController?.selectedArticles return timelineViewController?.selectedArticles
} }
}
var oneSelectedArticle: Article? { var oneSelectedArticle: Article? {
get {
if let articles = selectedArticles { if let articles = selectedArticles {
return articles.count == 1 ? articles[0] : nil return articles.count == 1 ? articles[0] : nil
} }
return nil return nil
} }
}
var currentLink: String? { var currentLink: String? {
get {
return oneSelectedArticle?.preferredLink return oneSelectedArticle?.preferredLink
} }
}
func canGoToNextUnread() -> Bool { func canGoToNextUnread() -> Bool {

View File

@ -0,0 +1,34 @@
//
// SidebarContextualMenuDelegate.swift
// Evergreen
//
// Created by Brent Simmons on 2/7/18.
// Copyright © 2018 Ranchero Software. All rights reserved.
//
import AppKit
@objc final class SidebarContextualMenuDelegate: NSObject, NSMenuDelegate {
@IBOutlet weak var sidebarViewController: SidebarViewController?
public func menuNeedsUpdate(_ menu: NSMenu) {
guard let sidebarViewController = sidebarViewController else {
return
}
menu.removeAllItems()
guard let contextualMenu = sidebarViewController.contextualMenuForClickedRows() else {
return
}
let items = contextualMenu.items
contextualMenu.removeAllItems()
for menuItem in items {
menu.addItem(menuItem)
}
}
}

View File

@ -14,10 +14,6 @@ import AppKit
public func menuNeedsUpdate(_ menu: NSMenu) { public func menuNeedsUpdate(_ menu: NSMenu) {
guard let sidebarViewController = sidebarViewController else {
return
}
// Save the first item, since its the gear icon itself. // Save the first item, since its the gear icon itself.
guard let gearMenuItem = menu.item(at: 0) else { guard let gearMenuItem = menu.item(at: 0) else {
assertionFailure("Expected sidebar gear menu to have at least one item.") assertionFailure("Expected sidebar gear menu to have at least one item.")
@ -26,6 +22,9 @@ import AppKit
menu.removeAllItems() menu.removeAllItems()
menu.addItem(gearMenuItem) menu.addItem(gearMenuItem)
guard let sidebarViewController = sidebarViewController else {
return
}
guard let contextualMenu = sidebarViewController.contextualMenuForSelectedObjects() else { guard let contextualMenu = sidebarViewController.contextualMenuForSelectedObjects() else {
return return
} }

View File

@ -12,19 +12,9 @@ import RSTree
class SidebarOutlineView : NSOutlineView { class SidebarOutlineView : NSOutlineView {
weak var sidebarViewController: SidebarViewController?
@IBOutlet var keyboardDelegate: KeyboardDelegate! @IBOutlet var keyboardDelegate: KeyboardDelegate!
//MARK: NSResponder // MARK: NSTableView
override func keyDown(with event: NSEvent) {
if keyboardDelegate.keydown(event, in: self) {
return
}
super.keyDown(with: event)
}
override func frameOfCell(atColumn column: Int, row: Int) -> NSRect { override func frameOfCell(atColumn column: Int, row: Int) -> NSRect {
@ -45,6 +35,8 @@ class SidebarOutlineView : NSOutlineView {
return frame return frame
} }
// MARK: NSView
override func viewWillStartLiveResize() { override func viewWillStartLiveResize() {
if let scrollView = self.enclosingScrollView { if let scrollView = self.enclosingScrollView {
@ -60,4 +52,15 @@ class SidebarOutlineView : NSOutlineView {
} }
super.viewDidEndLiveResize() super.viewDidEndLiveResize()
} }
// MARK: NSResponder
override func keyDown(with event: NSEvent) {
if keyboardDelegate.keydown(event, in: self) {
return
}
super.keyDown(with: event)
}
} }

View File

@ -16,6 +16,7 @@ import RSCore
@IBOutlet var outlineView: SidebarOutlineView! @IBOutlet var outlineView: SidebarOutlineView!
@IBOutlet var gearMenuDelegate: SidebarGearMenuDelegate! @IBOutlet var gearMenuDelegate: SidebarGearMenuDelegate!
@IBOutlet var contextualMenuDelegate: SidebarContextualMenuDelegate!
let treeControllerDelegate = SidebarTreeControllerDelegate() let treeControllerDelegate = SidebarTreeControllerDelegate()
lazy var treeController: TreeController = { lazy var treeController: TreeController = {
@ -37,7 +38,6 @@ import RSCore
sidebarCellAppearance = SidebarCellAppearance(theme: appDelegate.currentTheme, fontSize: AppDefaults.shared.sidebarFontSize) sidebarCellAppearance = SidebarCellAppearance(theme: appDelegate.currentTheme, fontSize: AppDefaults.shared.sidebarFontSize)
outlineView.sidebarViewController = self
outlineView.setDraggingSourceOperationMask(.move, forLocal: true) outlineView.setDraggingSourceOperationMask(.move, forLocal: true)
outlineView.setDraggingSourceOperationMask(.copy, forLocal: false) outlineView.setDraggingSourceOperationMask(.copy, forLocal: false)
@ -200,6 +200,22 @@ import RSCore
return menu(for: selectedObjects) return menu(for: selectedObjects)
} }
func contextualMenuForClickedRows() -> NSMenu? {
let row = outlineView.clickedRow
guard row != -1, let node = nodeForRow(row) else {
return nil
}
if outlineView.selectedRowIndexes.contains(row) {
// If the clickedRow is part of the selected rows, then do a contextual menu for all the selected rows.
return contextualMenuForSelectedObjects()
}
let object = node.representedObject
return menu(for: [object])
}
// MARK: NSOutlineViewDelegate // MARK: NSOutlineViewDelegate
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {

View File

@ -36,7 +36,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
didSet { didSet {
if showFeedNames != oldValue { if showFeedNames != oldValue {
updateShowAvatars() updateShowAvatars()
tableView.rowHeight = currentRowHeight updateTableViewRowHeight()
} }
} }
} }
@ -147,8 +147,10 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
cellAppearance = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: false, fontSize: fontSize) cellAppearance = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: false, fontSize: fontSize)
cellAppearanceWithAvatar = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: true, fontSize: fontSize) cellAppearanceWithAvatar = TimelineCellAppearance(theme: appDelegate.currentTheme, showAvatar: true, fontSize: fontSize)
updateRowHeights() updateRowHeights()
performBlockAndRestoreSelection {
tableView.reloadData() tableView.reloadData()
} }
}
// MARK: - API // MARK: - API
@ -416,6 +418,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
rowHeightWithFeedName = calculateRowHeight(showingFeedNames: true) rowHeightWithFeedName = calculateRowHeight(showingFeedNames: true)
rowHeightWithoutFeedName = calculateRowHeight(showingFeedNames: false) rowHeightWithoutFeedName = calculateRowHeight(showingFeedNames: false)
updateTableViewRowHeight()
} }
} }
@ -587,6 +590,11 @@ private extension TimelineViewController {
} }
} }
func updateTableViewRowHeight() {
tableView.rowHeight = currentRowHeight
}
func updateShowAvatars() { func updateShowAvatars() {
if showFeedNames { if showFeedNames {
@ -617,17 +625,32 @@ private extension TimelineViewController {
func sortDirectionDidChange() { func sortDirectionDidChange() {
let selectedArticleIDs = selectedArticles.articleIDs() performBlockAndRestoreSelection {
let unsortedArticles = Set(articles) let unsortedArticles = Set(articles)
updateArticles(with: unsortedArticles) updateArticles(with: unsortedArticles)
}
}
selectArticles(selectedArticleIDs) func selectedArticleIDs() -> [String] {
return selectedArticles.articleIDs()
}
func restoreSelection(_ articleIDs: [String]) {
selectArticles(articleIDs)
if tableView.selectedRow != -1 { if tableView.selectedRow != -1 {
tableView.scrollRowToVisible(tableView.selectedRow) tableView.scrollRowToVisible(tableView.selectedRow)
} }
} }
func performBlockAndRestoreSelection(_ block: (() -> Void)) {
let savedSelection = selectedArticleIDs()
block()
restoreSelection(savedSelection)
}
// MARK: Fetching Articles // MARK: Fetching Articles
func fetchArticles() { func fetchArticles() {
@ -672,13 +695,11 @@ private extension TimelineViewController {
return return
} }
let selectedArticleIDs = selectedArticles.articleIDs() performBlockAndRestoreSelection {
var unsortedArticles = fetchUnsortedArticles(for: representedObjects) var unsortedArticles = fetchUnsortedArticles(for: representedObjects)
unsortedArticles.formUnion(Set(articles)) unsortedArticles.formUnion(Set(articles))
updateArticles(with: unsortedArticles) updateArticles(with: unsortedArticles)
}
selectArticles(selectedArticleIDs)
} }
func selectArticles(_ articleIDs: [String]) { func selectArticles(_ articleIDs: [String]) {