mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Continue adopting MainActor.
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
import AppKit
|
||||
import RSCore
|
||||
|
||||
@objc final class DetailKeyboardDelegate: NSObject, KeyboardDelegate {
|
||||
@MainActor @objc final class DetailKeyboardDelegate: NSObject, KeyboardDelegate {
|
||||
|
||||
let shortcuts: Set<KeyboardShortcut>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import AppKit
|
||||
import RSCore
|
||||
|
||||
final class MainWindowKeyboardHandler: KeyboardDelegate {
|
||||
@MainActor final class MainWindowKeyboardHandler: KeyboardDelegate {
|
||||
|
||||
static let shared = MainWindowKeyboardHandler()
|
||||
let globalShortcuts: Set<KeyboardShortcut>
|
||||
|
||||
@@ -12,8 +12,8 @@ import RSCore
|
||||
@objc final class SharingServicePickerDelegate: NSObject, NSSharingServicePickerDelegate {
|
||||
|
||||
private let sharingServiceDelegate: SharingServiceDelegate
|
||||
|
||||
init(_ window: NSWindow?) {
|
||||
|
||||
@MainActor init(_ window: NSWindow?) {
|
||||
sharingServiceDelegate = SharingServiceDelegate(window)
|
||||
}
|
||||
|
||||
@@ -27,14 +27,14 @@ import RSCore
|
||||
}
|
||||
|
||||
static func customSharingServices(for items: [Any]) -> [NSSharingService] {
|
||||
guard let object = items.first else {
|
||||
return [NSSharingService]()
|
||||
}
|
||||
|
||||
let customServices: [SendToCommand] = [SendToMarsEditCommand(), SendToMicroBlogCommand()]
|
||||
|
||||
return customServices.compactMap { (sendToCommand) -> NSSharingService? in
|
||||
|
||||
guard let object = items.first else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard sendToCommand.canSendObject(object, selectedText: nil) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import AppKit
|
||||
import RSCore
|
||||
|
||||
@objc final class SidebarKeyboardDelegate: NSObject, KeyboardDelegate {
|
||||
@MainActor @objc final class SidebarKeyboardDelegate: NSObject, KeyboardDelegate {
|
||||
|
||||
@IBOutlet weak var sidebarViewController: SidebarViewController?
|
||||
let shortcuts: Set<KeyboardShortcut>
|
||||
|
||||
@@ -181,7 +181,7 @@ extension Feed: PasteboardWriterOwner {
|
||||
return [FeedPasteboardWriter.feedUTIType, .URL, .string, FeedPasteboardWriter.feedUTIInternalType]
|
||||
}
|
||||
|
||||
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
@MainActor func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
|
||||
let plist: Any?
|
||||
|
||||
@@ -204,15 +204,15 @@ extension Feed: PasteboardWriterOwner {
|
||||
|
||||
private extension FeedPasteboardWriter {
|
||||
|
||||
var pasteboardFeed: PasteboardFeed {
|
||||
@MainActor var pasteboardFeed: PasteboardFeed {
|
||||
return PasteboardFeed(url: feed.url, feedID: feed.feedID, homePageURL: feed.homePageURL, name: feed.name, editedName: feed.editedName, accountID: feed.account?.accountID, accountType: feed.account?.type)
|
||||
}
|
||||
|
||||
var exportDictionary: PasteboardFeedDictionary {
|
||||
@MainActor var exportDictionary: PasteboardFeedDictionary {
|
||||
return pasteboardFeed.exportDictionary()
|
||||
}
|
||||
|
||||
var internalDictionary: PasteboardFeedDictionary {
|
||||
@MainActor var internalDictionary: PasteboardFeedDictionary {
|
||||
var dictionary = pasteboardFeed.internalDictionary()
|
||||
if dictionary[PasteboardFeed.Key.containerName] == nil,
|
||||
case let .folder(accountID, folderName) = containerID {
|
||||
|
||||
@@ -109,7 +109,7 @@ extension Folder: PasteboardWriterOwner {
|
||||
return [.string, FolderPasteboardWriter.folderUTIInternalType]
|
||||
}
|
||||
|
||||
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
@MainActor func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
|
||||
let plist: Any?
|
||||
|
||||
@@ -127,11 +127,11 @@ extension Folder: PasteboardWriterOwner {
|
||||
}
|
||||
|
||||
private extension FolderPasteboardWriter {
|
||||
var pasteboardFolder: PasteboardFolder {
|
||||
@MainActor var pasteboardFolder: PasteboardFolder {
|
||||
return PasteboardFolder(name: folder.name ?? "", folderID: String(folder.folderID), accountID: folder.account?.accountID)
|
||||
}
|
||||
|
||||
var internalDictionary: PasteboardFeedDictionary {
|
||||
@MainActor var internalDictionary: PasteboardFeedDictionary {
|
||||
return pasteboardFolder.internalDictionary()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import Articles
|
||||
import RSCore
|
||||
import Account
|
||||
|
||||
@objc final class SidebarOutlineDataSource: NSObject, NSOutlineViewDataSource {
|
||||
@MainActor @objc final class SidebarOutlineDataSource: NSObject, NSOutlineViewDataSource {
|
||||
|
||||
let treeController: TreeController
|
||||
static let dragOperationNone = NSDragOperation(rawValue: 0)
|
||||
@@ -250,7 +250,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func accountForNode(_ node: Node) -> Account? {
|
||||
@MainActor func accountForNode(_ node: Node) -> Account? {
|
||||
if let account = node.representedObject as? Account {
|
||||
return account
|
||||
}
|
||||
@@ -263,7 +263,7 @@ private extension SidebarOutlineDataSource {
|
||||
return nil
|
||||
}
|
||||
|
||||
func commonAccountsFor(_ nodes: Set<Node>) -> Set<Account> {
|
||||
@MainActor func commonAccountsFor(_ nodes: Set<Node>) -> Set<Account> {
|
||||
var accounts = Set<Account>()
|
||||
for node in nodes {
|
||||
guard let oneAccount = accountForNode(node) else {
|
||||
@@ -274,7 +274,7 @@ private extension SidebarOutlineDataSource {
|
||||
return accounts
|
||||
}
|
||||
|
||||
func accountHasFolderRepresentingAnyDraggedFolders(_ account: Account, _ draggedFolders: Set<PasteboardFolder>) -> Bool {
|
||||
@MainActor func accountHasFolderRepresentingAnyDraggedFolders(_ account: Account, _ draggedFolders: Set<PasteboardFolder>) -> Bool {
|
||||
for draggedFolder in draggedFolders {
|
||||
if account.existingFolder(with: draggedFolder.name) != nil {
|
||||
return true
|
||||
@@ -283,7 +283,7 @@ private extension SidebarOutlineDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func validateLocalFolderDrop(_ outlineView: NSOutlineView, _ draggedFolder: PasteboardFolder, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
@MainActor func validateLocalFolderDrop(_ outlineView: NSOutlineView, _ draggedFolder: PasteboardFolder, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
guard let dropAccount = parentNode.representedObject as? Account, dropAccount.accountID != draggedFolder.accountID else {
|
||||
return SidebarOutlineDataSource.dragOperationNone
|
||||
}
|
||||
@@ -297,7 +297,7 @@ private extension SidebarOutlineDataSource {
|
||||
return .copy // different AccountIDs means can only copy
|
||||
}
|
||||
|
||||
func validateLocalFoldersDrop(_ outlineView: NSOutlineView, _ draggedFolders: Set<PasteboardFolder>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
@MainActor func validateLocalFoldersDrop(_ outlineView: NSOutlineView, _ draggedFolders: Set<PasteboardFolder>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
guard let dropAccount = parentNode.representedObject as? Account else {
|
||||
return SidebarOutlineDataSource.dragOperationNone
|
||||
}
|
||||
@@ -315,7 +315,7 @@ private extension SidebarOutlineDataSource {
|
||||
return .copy // different AccountIDs means can only copy
|
||||
}
|
||||
|
||||
func copyFeedInAccount(_ feed: Feed, _ destination: Container ) {
|
||||
@MainActor func copyFeedInAccount(_ feed: Feed, _ destination: Container ) {
|
||||
destination.account?.addFeed(feed, to: destination) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -326,7 +326,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func moveFeedInAccount(_ feed: Feed, _ source: Container, _ destination: Container) {
|
||||
@MainActor func moveFeedInAccount(_ feed: Feed, _ source: Container, _ destination: Container) {
|
||||
BatchUpdate.shared.start()
|
||||
source.account?.moveFeed(feed, from: source, to: destination) { result in
|
||||
BatchUpdate.shared.end()
|
||||
@@ -339,7 +339,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func copyFeedBetweenAccounts(_ feed: Feed, _ destinationContainer: Container) {
|
||||
@MainActor func copyFeedBetweenAccounts(_ feed: Feed, _ destinationContainer: Container) {
|
||||
guard let destinationAccount = destinationContainer.account else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ extension Article: PasteboardWriterOwner {
|
||||
static let articleUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.article"
|
||||
static let articleUTIInternalType = NSPasteboard.PasteboardType(rawValue: articleUTIInternal)
|
||||
|
||||
private lazy var renderedHTML: String = {
|
||||
@MainActor private lazy var renderedHTML: String = {
|
||||
let rendering = ArticleRenderer.articleHTML(article: article, theme: ArticleThemesManager.shared.currentTheme)
|
||||
return rendering.html
|
||||
}()
|
||||
@@ -46,7 +46,7 @@ extension Article: PasteboardWriterOwner {
|
||||
return types
|
||||
}
|
||||
|
||||
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
@MainActor func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
let plist: Any?
|
||||
|
||||
switch type {
|
||||
@@ -70,7 +70,7 @@ extension Article: PasteboardWriterOwner {
|
||||
|
||||
private extension ArticlePasteboardWriter {
|
||||
|
||||
func plainText() -> String {
|
||||
@MainActor func plainText() -> String {
|
||||
var s = ""
|
||||
|
||||
if let title = article.title {
|
||||
@@ -137,7 +137,7 @@ private extension ArticlePasteboardWriter {
|
||||
static let accountID = "accountID"
|
||||
}
|
||||
|
||||
func exportDictionary() -> [String: Any] {
|
||||
@MainActor func exportDictionary() -> [String: Any] {
|
||||
var d = [String: Any]()
|
||||
|
||||
d[Key.articleID] = article.articleID
|
||||
@@ -163,7 +163,7 @@ private extension ArticlePasteboardWriter {
|
||||
return d
|
||||
}
|
||||
|
||||
func internalDictionary() -> [String: Any] {
|
||||
@MainActor func internalDictionary() -> [String: Any] {
|
||||
var d = exportDictionary()
|
||||
d[Key.accountID] = article.accountID
|
||||
return d
|
||||
|
||||
@@ -11,7 +11,7 @@ import RSCore
|
||||
|
||||
// Doesn’t have any shortcuts of its own — they’re all in MainWindowKeyboardHandler.
|
||||
|
||||
@objc final class TimelineKeyboardDelegate: NSObject, KeyboardDelegate {
|
||||
@MainActor @objc final class TimelineKeyboardDelegate: NSObject, KeyboardDelegate {
|
||||
|
||||
@IBOutlet weak var timelineViewController: TimelineViewController?
|
||||
let shortcuts: Set<KeyboardShortcut>
|
||||
|
||||
@@ -162,7 +162,7 @@ struct AddAccountsView: View {
|
||||
|
||||
}
|
||||
|
||||
var icloudAccount: some View {
|
||||
@MainActor var icloudAccount: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("label.text.cloudkit", comment: "iCloud")
|
||||
.font(.headline)
|
||||
@@ -261,7 +261,7 @@ struct AddAccountsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func isCloudInUse() -> Bool {
|
||||
@MainActor private func isCloudInUse() -> Bool {
|
||||
AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit })
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import Articles
|
||||
import RSCore
|
||||
|
||||
@objc(ScriptableAccount)
|
||||
class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
|
||||
@MainActor class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
|
||||
|
||||
let account:Account
|
||||
init (_ account:Account) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import Account
|
||||
import Articles
|
||||
|
||||
@objc(ScriptableArticle)
|
||||
class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
|
||||
@MainActor class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
|
||||
|
||||
let article:Article
|
||||
let container:ScriptingObjectContainer
|
||||
@@ -111,7 +111,9 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
return article.status.boolStatus(forKey:.read)
|
||||
}
|
||||
set {
|
||||
markArticles([self.article], statusKey: .read, flag: newValue)
|
||||
Task { @MainActor in
|
||||
markArticles([self.article], statusKey: .read, flag: newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +123,9 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
return article.status.boolStatus(forKey:.starred)
|
||||
}
|
||||
set {
|
||||
markArticles([self.article], statusKey: .starred, flag: newValue)
|
||||
Task { @MainActor in
|
||||
markArticles([self.article], statusKey: .starred, flag: newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
|
||||
return self.classDescription as! NSScriptClassDescription
|
||||
}
|
||||
|
||||
func deleteElement(_ element:ScriptingObject) {
|
||||
@MainActor func deleteElement(_ element:ScriptingObject) {
|
||||
if let scriptableFeed = element as? ScriptableFeed {
|
||||
BatchUpdate.shared.perform {
|
||||
folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in }
|
||||
@@ -65,38 +65,43 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
|
||||
or
|
||||
tell account X to make new folder at end with properties {name:"new folder name"}
|
||||
*/
|
||||
class func handleCreateElement(command:NSCreateCommand) -> Any? {
|
||||
class func handleCreateElement(command:NSCreateCommand) -> Any? {
|
||||
guard command.isCreateCommand(forClass:"fold") else { return nil }
|
||||
let name = command.property(forKey:"name") as? String ?? ""
|
||||
|
||||
// some combination of the tell target and the location specifier ("in" or "at")
|
||||
// identifies where the new folder should be created
|
||||
let (account, folder) = command.accountAndFolderForNewChild()
|
||||
guard folder == nil else {
|
||||
print("support for folders within folders is NYI");
|
||||
return nil
|
||||
}
|
||||
|
||||
command.suspendExecution()
|
||||
|
||||
account.addFolder(name) { result in
|
||||
switch result {
|
||||
case .success(let folder):
|
||||
let scriptableAccount = ScriptableAccount(account)
|
||||
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
|
||||
command.resumeExecution(withResult:scriptableFolder.objectSpecifier)
|
||||
case .failure:
|
||||
|
||||
Task { @MainActor in
|
||||
|
||||
// some combination of the tell target and the location specifier ("in" or "at")
|
||||
// identifies where the new folder should be created
|
||||
let (account, folder) = command.accountAndFolderForNewChild()
|
||||
guard folder == nil else {
|
||||
print("NetNewsWire does not support creating folders within folders.");
|
||||
command.resumeExecution(withResult:nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
account.addFolder(name) { result in
|
||||
switch result {
|
||||
case .success(let folder):
|
||||
let scriptableAccount = ScriptableAccount(account)
|
||||
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
|
||||
command.resumeExecution(withResult:scriptableFolder.objectSpecifier)
|
||||
case .failure:
|
||||
command.resumeExecution(withResult:nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: --- Scriptable elements ---
|
||||
|
||||
@objc(feeds)
|
||||
var feeds:NSArray {
|
||||
@MainActor var feeds:NSArray {
|
||||
let feeds = Array(folder.topLevelFeeds)
|
||||
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ extension NSScriptCommand {
|
||||
return true
|
||||
}
|
||||
|
||||
func accountAndFolderForNewChild() -> (Account, Folder?) {
|
||||
@MainActor func accountAndFolderForNewChild() -> (Account, Folder?) {
|
||||
let appleEvent = self.appleEvent
|
||||
var account = AccountManager.shared.defaultAccount
|
||||
var folder:Folder? = nil
|
||||
|
||||
@@ -71,7 +71,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
return url
|
||||
}
|
||||
|
||||
class func scriptableFeed(_ feed: Feed, account: Account, folder: Folder?) -> ScriptableFeed {
|
||||
@MainActor class func scriptableFeed(_ feed: Feed, account: Account, folder: Folder?) -> ScriptableFeed {
|
||||
let scriptableAccount = ScriptableAccount(account)
|
||||
if let folder = folder {
|
||||
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
|
||||
@@ -85,33 +85,39 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
guard command.isCreateCommand(forClass:"Feed") else { return nil }
|
||||
guard let arguments = command.arguments else {return nil}
|
||||
let titleFromArgs = command.property(forKey:"name") as? String
|
||||
let (account, folder) = command.accountAndFolderForNewChild()
|
||||
guard let url = self.urlForNewFeed(arguments:arguments) else {return nil}
|
||||
|
||||
if let existingFeed = account.existingFeed(withURL:url) {
|
||||
return scriptableFeed(existingFeed, account:account, folder:folder).objectSpecifier
|
||||
}
|
||||
|
||||
let container: Container = folder != nil ? folder! : account
|
||||
|
||||
// We need to download the feed and parse it.
|
||||
// RSParser does the callback for the download on the main thread.
|
||||
// Because we can't wait here (on the main thread) for the callback, we have to return from this function.
|
||||
// Generally, returning from an AppleEvent handler function means that handling the Apple event is over,
|
||||
// but we don’t yet have the result of the event yet, so we prevent the Apple event from returning by calling
|
||||
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
|
||||
command.suspendExecution()
|
||||
|
||||
account.createFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||
let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder)
|
||||
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
|
||||
case .failure:
|
||||
|
||||
// We need to download the feed and parse it.
|
||||
// RSParser does the callback for the download on the main thread.
|
||||
// Because we can't wait here (on the main thread) for the callback, we have to return from this function.
|
||||
// Generally, returning from an AppleEvent handler function means that handling the Apple event is over,
|
||||
// but we don’t yet have the result of the event yet, so we prevent the Apple event from returning by calling
|
||||
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
|
||||
command.suspendExecution()
|
||||
|
||||
Task { @MainActor in
|
||||
let (account, folder) = command.accountAndFolderForNewChild()
|
||||
guard let url = self.urlForNewFeed(arguments:arguments) else {
|
||||
command.resumeExecution(withResult:nil)
|
||||
return
|
||||
}
|
||||
|
||||
if let existingFeed = account.existingFeed(withURL:url) {
|
||||
let scriptableFeed = scriptableFeed(existingFeed, account:account, folder:folder)
|
||||
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
|
||||
return
|
||||
}
|
||||
|
||||
let container: Container = folder != nil ? folder! : account
|
||||
account.createFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||
let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder)
|
||||
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
|
||||
case .failure:
|
||||
command.resumeExecution(withResult:nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -235,7 +235,7 @@ private extension ActivityManager {
|
||||
}
|
||||
#endif
|
||||
|
||||
func makeKeywords(_ article: Article) -> [String] {
|
||||
@MainActor func makeKeywords(_ article: Article) -> [String] {
|
||||
let feedNameKeywords = makeKeywords(article.feed?.nameForDisplay)
|
||||
let articleTitleKeywords = makeKeywords(ArticleStringFormatter.truncatedTitle(article))
|
||||
return feedNameKeywords + articleTitleKeywords
|
||||
@@ -274,11 +274,11 @@ private extension ActivityManager {
|
||||
activity.becomeCurrent()
|
||||
}
|
||||
|
||||
static func identifier(for folder: Folder) -> String {
|
||||
@MainActor static func identifier(for folder: Folder) -> String {
|
||||
return "account_\(folder.account!.accountID)_folder_\(folder.nameForDisplay)"
|
||||
}
|
||||
|
||||
static func identifier(for feed: Feed) -> String {
|
||||
@MainActor static func identifier(for feed: Feed) -> String {
|
||||
return "account_\(feed.account!.accountID)_feed_\(feed.feedID)"
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import RSCore
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
struct ArticleRenderer {
|
||||
@MainActor struct ArticleRenderer {
|
||||
|
||||
typealias Rendering = (style: String, html: String, title: String, baseURL: String)
|
||||
|
||||
@@ -330,7 +330,7 @@ private extension ArticleRenderer {
|
||||
|
||||
private extension Article {
|
||||
|
||||
var baseURL: URL? {
|
||||
@MainActor var baseURL: URL? {
|
||||
var s = link
|
||||
if s == nil {
|
||||
s = feed?.homePageURL
|
||||
|
||||
@@ -12,7 +12,7 @@ import RSTree
|
||||
import Account
|
||||
import Articles
|
||||
|
||||
final class DeleteCommand: UndoableCommand {
|
||||
@MainActor final class DeleteCommand: UndoableCommand {
|
||||
|
||||
let treeController: TreeController?
|
||||
let undoManager: UndoManager
|
||||
@@ -112,7 +112,7 @@ private struct SidebarItemSpecifier {
|
||||
return nil
|
||||
}
|
||||
|
||||
init?(node: Node, errorHandler: @escaping (Error) -> ()) {
|
||||
@MainActor init?(node: Node, errorHandler: @escaping (Error) -> ()) {
|
||||
|
||||
var account: Account?
|
||||
|
||||
@@ -142,7 +142,7 @@ private struct SidebarItemSpecifier {
|
||||
|
||||
}
|
||||
|
||||
func delete(completion: @escaping () -> Void) {
|
||||
@MainActor func delete(completion: @escaping () -> Void) {
|
||||
|
||||
if let feed {
|
||||
|
||||
@@ -170,7 +170,7 @@ private struct SidebarItemSpecifier {
|
||||
}
|
||||
}
|
||||
|
||||
func restore() {
|
||||
@MainActor func restore() {
|
||||
|
||||
if let _ = feed {
|
||||
restoreFeed()
|
||||
@@ -180,7 +180,7 @@ private struct SidebarItemSpecifier {
|
||||
}
|
||||
}
|
||||
|
||||
private func restoreFeed() {
|
||||
@MainActor private func restoreFeed() {
|
||||
|
||||
guard let account = account, let feed = feed, let container = path.resolveContainer() else {
|
||||
return
|
||||
@@ -194,7 +194,7 @@ private struct SidebarItemSpecifier {
|
||||
|
||||
}
|
||||
|
||||
private func restoreFolder() {
|
||||
@MainActor private func restoreFolder() {
|
||||
|
||||
guard let account = account, let folder = folder else {
|
||||
return
|
||||
|
||||
@@ -23,7 +23,7 @@ public extension Notification.Name {
|
||||
static let MarkStatusCommandDidUndoDirectMarking = Notification.Name("MarkStatusCommandDidUndoDirectMarking")
|
||||
}
|
||||
|
||||
final class MarkStatusCommand: UndoableCommand {
|
||||
@MainActor final class MarkStatusCommand: UndoableCommand {
|
||||
|
||||
let undoActionName: String
|
||||
let redoActionName: String
|
||||
|
||||
@@ -23,23 +23,25 @@ final class SendToMarsEditCommand: SendToCommand {
|
||||
|
||||
func sendObject(_ object: Any?, selectedText: String?) {
|
||||
|
||||
guard canSendObject(object, selectedText: selectedText) else {
|
||||
return
|
||||
}
|
||||
guard let article = (object as? ArticlePasteboardWriter)?.article else {
|
||||
return
|
||||
}
|
||||
guard let app = appToUse(), app.launchIfNeeded(), app.bringToFront() else {
|
||||
return
|
||||
}
|
||||
Task { @MainActor in
|
||||
guard canSendObject(object, selectedText: selectedText) else {
|
||||
return
|
||||
}
|
||||
guard let article = (object as? ArticlePasteboardWriter)?.article else {
|
||||
return
|
||||
}
|
||||
guard let app = appToUse(), app.launchIfNeeded(), app.bringToFront() else {
|
||||
return
|
||||
}
|
||||
|
||||
send(article, to: app)
|
||||
send(article, to: app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SendToMarsEditCommand {
|
||||
|
||||
func send(_ article: Article, to app: UserApp) {
|
||||
@MainActor func send(_ article: Article, to app: UserApp) {
|
||||
|
||||
// App has already been launched.
|
||||
|
||||
|
||||
@@ -31,36 +31,38 @@ final class SendToMicroBlogCommand: SendToCommand {
|
||||
|
||||
func sendObject(_ object: Any?, selectedText: String?) {
|
||||
|
||||
guard canSendObject(object, selectedText: selectedText) else {
|
||||
return
|
||||
}
|
||||
guard let article = (object as? ArticlePasteboardWriter)?.article else {
|
||||
return
|
||||
}
|
||||
guard microBlogApp.launchIfNeeded(), microBlogApp.bringToFront() else {
|
||||
return
|
||||
}
|
||||
Task { @MainActor in
|
||||
guard canSendObject(object, selectedText: selectedText) else {
|
||||
return
|
||||
}
|
||||
guard let article = (object as? ArticlePasteboardWriter)?.article else {
|
||||
return
|
||||
}
|
||||
guard microBlogApp.launchIfNeeded(), microBlogApp.bringToFront() else {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: get text from contentHTML or contentText if no title and no selectedText.
|
||||
// TODO: consider selectedText.
|
||||
// TODO: get text from contentHTML or contentText if no title and no selectedText.
|
||||
// TODO: consider selectedText.
|
||||
|
||||
let s = article.attributionString + article.linkString
|
||||
let s = article.attributionString + article.linkString
|
||||
|
||||
let urlQueryDictionary = ["text": s]
|
||||
guard let urlQueryString = urlQueryDictionary.urlQueryString else {
|
||||
return
|
||||
let urlQueryDictionary = ["text": s]
|
||||
guard let urlQueryString = urlQueryDictionary.urlQueryString else {
|
||||
return
|
||||
}
|
||||
guard let url = URL(string: "microblog://post?" + urlQueryString) else {
|
||||
return
|
||||
}
|
||||
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
guard let url = URL(string: "microblog://post?" + urlQueryString) else {
|
||||
return
|
||||
}
|
||||
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Article {
|
||||
|
||||
var attributionString: String {
|
||||
@MainActor var attributionString: String {
|
||||
|
||||
// Feed name, or feed name + author name (if author is specified per-article).
|
||||
// Includes trailing space.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import Account
|
||||
|
||||
struct AddFeedDefaultContainer {
|
||||
@MainActor struct AddFeedDefaultContainer {
|
||||
|
||||
static var defaultContainer: Container? {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import Account
|
||||
|
||||
// These handle multiple accounts.
|
||||
|
||||
func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
|
||||
@MainActor func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
|
||||
let d: [String: Set<Article>] = accountAndArticlesDictionary(articles)
|
||||
|
||||
let group = DispatchGroup()
|
||||
@@ -93,7 +93,7 @@ extension Article {
|
||||
return datePublished ?? dateModified ?? status.dateArrived
|
||||
}
|
||||
|
||||
var isAvailableToMarkUnread: Bool {
|
||||
@MainActor var isAvailableToMarkUnread: Bool {
|
||||
guard let markUnreadWindow = account?.behaviors.compactMap( { behavior -> Int? in
|
||||
switch behavior {
|
||||
case .disallowMarkAsUnreadAfterPeriod(let days):
|
||||
@@ -133,7 +133,7 @@ extension Article {
|
||||
}
|
||||
}
|
||||
|
||||
func byline() -> String {
|
||||
@MainActor func byline() -> String {
|
||||
guard let authors = authors ?? feed?.authors, !authors.isEmpty else {
|
||||
return ""
|
||||
}
|
||||
@@ -194,7 +194,7 @@ struct ArticlePathKey {
|
||||
|
||||
extension Article {
|
||||
|
||||
public var pathUserInfo: [AnyHashable : Any] {
|
||||
@MainActor public var pathUserInfo: [AnyHashable : Any] {
|
||||
return [
|
||||
ArticlePathKey.accountID: accountID,
|
||||
ArticlePathKey.accountName: account?.nameForDisplay ?? "",
|
||||
@@ -209,7 +209,7 @@ extension Article {
|
||||
|
||||
extension Article: SortableArticle {
|
||||
|
||||
var sortableName: String {
|
||||
@MainActor var sortableName: String {
|
||||
return feed?.name ?? ""
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ extension Notification.Name {
|
||||
static let FaviconDidBecomeAvailable = Notification.Name("FaviconDidBecomeAvailableNotification") // userInfo key: FaviconDownloader.UserInfoKey.faviconURL
|
||||
}
|
||||
|
||||
final class FaviconDownloader: Logging {
|
||||
@MainActor final class FaviconDownloader: Logging {
|
||||
|
||||
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
|
||||
|
||||
@@ -280,11 +280,15 @@ private extension FaviconDownloader {
|
||||
}
|
||||
|
||||
func queueSaveHomePageToFaviconURLCacheIfNeeded() {
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageToFaviconURLCacheIfNeeded))
|
||||
Task { @MainActor in
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageToFaviconURLCacheIfNeeded))
|
||||
}
|
||||
}
|
||||
|
||||
func queueSaveHomePageURLsWithNoFaviconURLCacheIfNeeded() {
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageURLsWithNoFaviconURLCacheIfNeeded))
|
||||
Task { @MainActor in
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageURLsWithNoFaviconURLCacheIfNeeded))
|
||||
}
|
||||
}
|
||||
|
||||
func saveHomePageToFaviconURLCache() {
|
||||
|
||||
@@ -20,7 +20,7 @@ extension Notification.Name {
|
||||
|
||||
public final class FeedIconDownloader {
|
||||
|
||||
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
|
||||
@MainActor private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
|
||||
|
||||
private let imageDownloader: ImageDownloader
|
||||
|
||||
@@ -28,7 +28,9 @@ public final class FeedIconDownloader {
|
||||
private var feedURLToIconURLCachePath: String
|
||||
private var feedURLToIconURLCacheDirty = false {
|
||||
didSet {
|
||||
queueSaveFeedURLToIconURLCacheIfNeeded()
|
||||
Task { @MainActor in
|
||||
queueSaveFeedURLToIconURLCacheIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +38,9 @@ public final class FeedIconDownloader {
|
||||
private var homePageToIconURLCachePath: String
|
||||
private var homePageToIconURLCacheDirty = false {
|
||||
didSet {
|
||||
queueSaveHomePageToIconURLCacheIfNeeded()
|
||||
Task { @MainActor in
|
||||
queueSaveHomePageToIconURLCacheIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +48,9 @@ public final class FeedIconDownloader {
|
||||
private var homePagesWithNoIconURLCachePath: String
|
||||
private var homePagesWithNoIconURLCacheDirty = false {
|
||||
didSet {
|
||||
queueHomePagesWithNoIconURLCacheIfNeeded()
|
||||
Task { @MainActor in
|
||||
queueHomePagesWithNoIconURLCacheIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,15 +261,15 @@ private extension FeedIconDownloader {
|
||||
homePagesWithNoIconURLCache = Set(decoded)
|
||||
}
|
||||
|
||||
func queueSaveFeedURLToIconURLCacheIfNeeded() {
|
||||
@MainActor func queueSaveFeedURLToIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveFeedURLToIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueSaveHomePageToIconURLCacheIfNeeded() {
|
||||
@MainActor func queueSaveHomePageToIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueHomePagesWithNoIconURLCacheIfNeeded() {
|
||||
@MainActor func queueHomePagesWithNoIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ struct ExtensionAccount: ExtensionContainer {
|
||||
let containerID: ContainerIdentifier?
|
||||
let folders: [ExtensionFolder]
|
||||
|
||||
init(account: Account) {
|
||||
@MainActor init(account: Account) {
|
||||
self.name = account.nameForDisplay
|
||||
self.accountID = account.accountID
|
||||
self.type = account.type
|
||||
@@ -84,7 +84,7 @@ struct ExtensionFolder: ExtensionContainer {
|
||||
let name: String
|
||||
let containerID: ContainerIdentifier?
|
||||
|
||||
init(folder: Folder) {
|
||||
@MainActor init(folder: Folder) {
|
||||
self.accountName = folder.account?.nameForDisplay ?? ""
|
||||
self.accountID = folder.account?.accountID ?? ""
|
||||
self.name = folder.nameForDisplay
|
||||
|
||||
@@ -21,10 +21,12 @@ final class ExtensionContainersFile: Logging {
|
||||
|
||||
private var isDirty = false {
|
||||
didSet {
|
||||
queueSaveToDiskIfNeeded()
|
||||
Task { @MainActor in
|
||||
queueSaveToDiskIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5)
|
||||
@MainActor private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5)
|
||||
|
||||
init() {
|
||||
if !FileManager.default.fileExists(atPath: ExtensionContainersFile.filePath) {
|
||||
@@ -66,7 +68,7 @@ private extension ExtensionContainersFile {
|
||||
isDirty = true
|
||||
}
|
||||
|
||||
func queueSaveToDiskIfNeeded() {
|
||||
@MainActor func queueSaveToDiskIfNeeded() {
|
||||
saveQueue.add(self, #selector(saveToDiskIfNeeded))
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Logging {
|
||||
return operationQueue
|
||||
}
|
||||
|
||||
override init() {
|
||||
@MainActor override init() {
|
||||
operationQueue = OperationQueue()
|
||||
operationQueue.maxConcurrentOperationCount = 1
|
||||
|
||||
@@ -46,7 +46,9 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Logging {
|
||||
|
||||
func resume() {
|
||||
NSFileCoordinator.addFilePresenter(self)
|
||||
process()
|
||||
Task { @MainActor in
|
||||
process()
|
||||
}
|
||||
}
|
||||
|
||||
func suspend() {
|
||||
@@ -93,7 +95,7 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Logging {
|
||||
|
||||
private extension ExtensionFeedAddRequestFile {
|
||||
|
||||
func process() {
|
||||
@MainActor func process() {
|
||||
|
||||
let decoder = PropertyListDecoder()
|
||||
let encoder = PropertyListEncoder()
|
||||
@@ -128,7 +130,7 @@ private extension ExtensionFeedAddRequestFile {
|
||||
requests?.forEach { processRequest($0) }
|
||||
}
|
||||
|
||||
func processRequest(_ request: ExtensionFeedAddRequest) {
|
||||
@MainActor func processRequest(_ request: ExtensionFeedAddRequest) {
|
||||
var destinationAccountID: String? = nil
|
||||
switch request.destinationContainerID {
|
||||
case .account(let accountID):
|
||||
|
||||
@@ -107,7 +107,9 @@ extension SmartFeed: ArticleFetcher {
|
||||
private extension SmartFeed {
|
||||
|
||||
func queueFetchUnreadCounts() {
|
||||
CoalescingQueue.standard.add(self, #selector(fetchUnreadCounts))
|
||||
Task { @MainActor in
|
||||
CoalescingQueue.standard.add(self, #selector(fetchUnreadCounts))
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor func fetchUnreadCount(for account: Account) {
|
||||
|
||||
@@ -17,9 +17,9 @@ protocol SmartFeedDelegate: ItemIdentifiable, DisplayNameProvider, ArticleFetche
|
||||
func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock)
|
||||
}
|
||||
|
||||
extension SmartFeedDelegate {
|
||||
@MainActor extension SmartFeedDelegate {
|
||||
|
||||
@MainActor func fetchArticles() throws -> Set<Article> {
|
||||
func fetchArticles() throws -> Set<Article> {
|
||||
return try AccountManager.shared.fetchArticles(fetchType)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import RSCore
|
||||
import Account
|
||||
|
||||
final class SmartFeedsController: DisplayNameProvider, ContainerIdentifiable {
|
||||
@MainActor final class SmartFeedsController: DisplayNameProvider, ContainerIdentifiable {
|
||||
|
||||
var containerID: ContainerIdentifier? {
|
||||
return ContainerIdentifier.smartFeedController
|
||||
|
||||
@@ -67,7 +67,7 @@ extension Array where Element == Article {
|
||||
return false
|
||||
}
|
||||
|
||||
func anyArticleIsReadAndCanMarkUnread() -> Bool {
|
||||
@MainActor func anyArticleIsReadAndCanMarkUnread() -> Bool {
|
||||
return anyArticlePassesTest { $0.status.read && $0.isAvailableToMarkUnread }
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ extension Array where Element == Article {
|
||||
var i = 0
|
||||
for article in self {
|
||||
let otherArticle = otherArticles[i]
|
||||
if article.account != otherArticle.account || article.articleID != otherArticle.articleID {
|
||||
if article.accountID != otherArticle.accountID || article.articleID != otherArticle.articleID {
|
||||
return false
|
||||
}
|
||||
i += 1
|
||||
|
||||
@@ -33,7 +33,7 @@ final class FetchRequestOperation {
|
||||
self.resultBlock = resultBlock
|
||||
}
|
||||
|
||||
func run(_ completion: @escaping (FetchRequestOperation) -> Void) {
|
||||
@MainActor func run(_ completion: @escaping (FetchRequestOperation) -> Void) {
|
||||
precondition(Thread.isMainThread)
|
||||
precondition(!isFinished)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
// Main thread only.
|
||||
|
||||
final class FetchRequestQueue {
|
||||
@MainActor final class FetchRequestQueue {
|
||||
|
||||
private var pendingRequests = [FetchRequestOperation]()
|
||||
private var currentRequest: FetchRequestOperation? = nil
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import Account
|
||||
|
||||
class AccountRefreshTimer {
|
||||
@MainActor class AccountRefreshTimer {
|
||||
|
||||
var shuttingDown = false
|
||||
|
||||
@@ -75,5 +75,4 @@ class AccountRefreshTimer {
|
||||
|
||||
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class ArticleStatusSyncTimer {
|
||||
private var lastTimedRefresh: Date?
|
||||
private let launchTime = Date()
|
||||
|
||||
func fireOldTimer() {
|
||||
@MainActor func fireOldTimer() {
|
||||
if let timer = internalTimer {
|
||||
if timer.fireDate < Date() {
|
||||
timedRefresh(nil)
|
||||
@@ -59,7 +59,7 @@ class ArticleStatusSyncTimer {
|
||||
|
||||
}
|
||||
|
||||
@objc func timedRefresh(_ sender: Timer?) {
|
||||
@MainActor @objc func timedRefresh(_ sender: Timer?) {
|
||||
|
||||
guard !shuttingDown else {
|
||||
return
|
||||
|
||||
@@ -41,7 +41,7 @@ final class FeedTreeControllerDelegate: TreeControllerDelegate {
|
||||
|
||||
private extension FeedTreeControllerDelegate {
|
||||
|
||||
func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
|
||||
@MainActor func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
|
||||
var topLevelNodes = [Node]()
|
||||
|
||||
let smartFeedsNode = rootNode.existingOrNewChildNode(with: SmartFeedsController.shared)
|
||||
@@ -54,7 +54,7 @@ private extension FeedTreeControllerDelegate {
|
||||
return topLevelNodes
|
||||
}
|
||||
|
||||
func childNodesForSmartFeeds(_ parentNode: Node) -> [Node] {
|
||||
@MainActor func childNodesForSmartFeeds(_ parentNode: Node) -> [Node] {
|
||||
return SmartFeedsController.shared.smartFeeds.compactMap { (feed) -> Node? in
|
||||
// All Smart Feeds should remain visible despite the Hide Read Feeds setting
|
||||
return parentNode.existingOrNewChildNode(with: feed as AnyObject)
|
||||
@@ -132,7 +132,7 @@ private extension FeedTreeControllerDelegate {
|
||||
return node
|
||||
}
|
||||
|
||||
func sortedAccountNodes(_ parent: Node) -> [Node] {
|
||||
@MainActor func sortedAccountNodes(_ parent: Node) -> [Node] {
|
||||
let nodes = AccountManager.shared.sortedActiveAccounts.compactMap { (account) -> Node? in
|
||||
let accountNode = parent.existingOrNewChildNode(with: account)
|
||||
accountNode.canHaveChildNodes = true
|
||||
|
||||
@@ -14,7 +14,7 @@ import Account
|
||||
|
||||
final class FolderTreeControllerDelegate: TreeControllerDelegate {
|
||||
|
||||
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
|
||||
@MainActor func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
|
||||
|
||||
return node.isRoot ? childNodesForRootNode(node) : childNodes(node)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ final class FolderTreeControllerDelegate: TreeControllerDelegate {
|
||||
|
||||
private extension FolderTreeControllerDelegate {
|
||||
|
||||
func childNodesForRootNode(_ node: Node) -> [Node]? {
|
||||
@MainActor func childNodesForRootNode(_ node: Node) -> [Node]? {
|
||||
|
||||
let accountNodes: [Node] = AccountManager.shared.sortedActiveAccounts.map { account in
|
||||
let accountNode = Node(representedObject: account, parent: node)
|
||||
@@ -33,7 +33,7 @@ private extension FolderTreeControllerDelegate {
|
||||
|
||||
}
|
||||
|
||||
func childNodes(_ node: Node) -> [Node]? {
|
||||
@MainActor func childNodes(_ node: Node) -> [Node]? {
|
||||
|
||||
guard let account = node.representedObject as? Account, let folders = account.folders else {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user