2017-05-22 13:00:45 -07:00
|
|
|
|
//
|
|
|
|
|
// AppDelegate.swift
|
2018-08-28 22:18:24 -07:00
|
|
|
|
// NetNewsWire
|
2017-05-22 13:00:45 -07:00
|
|
|
|
//
|
2017-05-27 10:43:27 -07:00
|
|
|
|
// Created by Brent Simmons on 7/11/15.
|
|
|
|
|
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
|
2017-05-22 13:00:45 -07:00
|
|
|
|
//
|
|
|
|
|
|
2018-02-02 22:51:32 -08:00
|
|
|
|
import AppKit
|
2019-10-02 19:42:16 -05:00
|
|
|
|
import UserNotifications
|
2018-07-23 18:29:08 -07:00
|
|
|
|
import Articles
|
2017-05-27 10:43:27 -07:00
|
|
|
|
import RSTree
|
2017-05-27 13:37:50 -07:00
|
|
|
|
import RSWeb
|
2017-09-17 17:12:42 -07:00
|
|
|
|
import Account
|
2017-11-13 18:33:23 -08:00
|
|
|
|
import RSCore
|
2020-07-29 05:11:57 -05:00
|
|
|
|
import RSCoreResources
|
2020-07-30 17:40:45 -05:00
|
|
|
|
import Secrets
|
2020-12-02 07:56:22 -06:00
|
|
|
|
import OSLog
|
2020-12-11 18:09:36 -06:00
|
|
|
|
import CrashReporter
|
2019-10-22 19:44:06 -04:00
|
|
|
|
|
|
|
|
|
// If we're not going to import Sparkle, provide dummy protocols to make it easy
|
|
|
|
|
// for AppDelegate to comply
|
|
|
|
|
#if MAC_APP_STORE || TEST
|
|
|
|
|
protocol SPUStandardUserDriverDelegate {}
|
|
|
|
|
protocol SPUUpdaterDelegate {}
|
|
|
|
|
#else
|
2019-10-22 16:42:17 -05:00
|
|
|
|
import Sparkle
|
|
|
|
|
#endif
|
2017-05-27 10:43:27 -07:00
|
|
|
|
|
2017-11-14 21:31:17 -08:00
|
|
|
|
var appDelegate: AppDelegate!
|
2017-05-22 13:00:45 -07:00
|
|
|
|
|
|
|
|
|
@NSApplicationMain
|
2019-10-22 19:44:06 -04:00
|
|
|
|
class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate
|
|
|
|
|
{
|
2017-05-22 13:00:45 -07:00
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
private struct WindowRestorationIdentifiers {
|
|
|
|
|
static let mainWindow = "mainWindow"
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-02 19:42:16 -05:00
|
|
|
|
var userNotificationManager: UserNotificationManager!
|
2017-11-24 21:39:59 -08:00
|
|
|
|
var faviconDownloader: FaviconDownloader!
|
2017-11-25 20:12:53 -08:00
|
|
|
|
var imageDownloader: ImageDownloader!
|
2017-11-26 13:16:32 -08:00
|
|
|
|
var authorAvatarDownloader: AuthorAvatarDownloader!
|
2019-11-14 20:11:41 -06:00
|
|
|
|
var webFeedIconDownloader: WebFeedIconDownloader!
|
2020-08-13 18:03:39 -05:00
|
|
|
|
var extensionContainersFile: ExtensionContainersFile!
|
|
|
|
|
var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile!
|
|
|
|
|
|
2017-11-24 21:39:59 -08:00
|
|
|
|
var appName: String!
|
2019-05-15 17:21:58 -05:00
|
|
|
|
|
|
|
|
|
var refreshTimer: AccountRefreshTimer?
|
|
|
|
|
var syncTimer: ArticleStatusSyncTimer?
|
|
|
|
|
|
2019-01-27 18:00:09 -08:00
|
|
|
|
var shuttingDown = false {
|
|
|
|
|
didSet {
|
|
|
|
|
if shuttingDown {
|
2019-04-23 11:20:44 -05:00
|
|
|
|
refreshTimer?.shuttingDown = shuttingDown
|
|
|
|
|
refreshTimer?.invalidate()
|
2019-05-15 17:21:58 -05:00
|
|
|
|
syncTimer?.shuttingDown = shuttingDown
|
|
|
|
|
syncTimer?.invalidate()
|
2019-01-27 18:00:09 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-09 17:58:51 -06:00
|
|
|
|
var isShutDownSyncDone = false
|
|
|
|
|
|
2018-01-27 11:32:59 -08:00
|
|
|
|
@IBOutlet var debugMenuItem: NSMenuItem!
|
2018-01-27 15:11:02 -08:00
|
|
|
|
@IBOutlet var sortByOldestArticleOnTopMenuItem: NSMenuItem!
|
|
|
|
|
@IBOutlet var sortByNewestArticleOnTopMenuItem: NSMenuItem!
|
2019-09-08 17:09:26 -05:00
|
|
|
|
@IBOutlet var groupArticlesByFeedMenuItem: NSMenuItem!
|
2018-12-09 12:12:55 -08:00
|
|
|
|
@IBOutlet var checkForUpdatesMenuItem: NSMenuItem!
|
2019-09-19 10:38:17 -05:00
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
var unreadCount = 0 {
|
|
|
|
|
didSet {
|
2017-10-05 18:12:58 -07:00
|
|
|
|
if unreadCount != oldValue {
|
2018-12-27 21:19:19 -08:00
|
|
|
|
CoalescingQueue.standard.add(self, #selector(updateDockBadge))
|
2017-11-19 13:57:42 -08:00
|
|
|
|
postUnreadCountDidChangeNotification()
|
2017-10-05 18:12:58 -07:00
|
|
|
|
}
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
private var mainWindowController: MainWindowController? {
|
|
|
|
|
var bestController: MainWindowController?
|
|
|
|
|
for candidateController in mainWindowControllers {
|
|
|
|
|
if let bestWindow = bestController?.window, let candidateWindow = candidateController.window {
|
|
|
|
|
if bestWindow.orderedIndex > candidateWindow.orderedIndex {
|
|
|
|
|
bestController = candidateController
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
bestController = candidateController
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return bestController
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var mainWindowControllers = [MainWindowController]()
|
2017-11-24 21:39:59 -08:00
|
|
|
|
private var preferencesWindowController: NSWindowController?
|
|
|
|
|
private var addFeedController: AddFeedController?
|
|
|
|
|
private var addFolderWindowController: AddFolderWindowController?
|
2019-05-01 16:04:56 -05:00
|
|
|
|
private var importOPMLController: ImportOPMLWindowController?
|
|
|
|
|
private var exportOPMLController: ExportOPMLWindowController?
|
2017-11-24 21:39:59 -08:00
|
|
|
|
private var keyboardShortcutsWindowController: WebViewWindowController?
|
|
|
|
|
private var inspectorWindowController: InspectorWindowController?
|
2018-12-29 12:31:39 -08:00
|
|
|
|
private var crashReportWindowController: CrashReportWindowController? // For testing only
|
2019-09-06 09:13:09 -04:00
|
|
|
|
private let appMovementMonitor = RSAppMovementMonitor()
|
2019-10-22 19:33:00 -04:00
|
|
|
|
#if !MAC_APP_STORE && !TEST
|
|
|
|
|
private var softwareUpdater: SPUUpdater!
|
2020-12-11 18:09:36 -06:00
|
|
|
|
private var crashReporter: PLCrashReporter!
|
2019-10-22 19:33:00 -04:00
|
|
|
|
#endif
|
2017-11-24 21:39:59 -08:00
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
override init() {
|
|
|
|
|
NSWindow.allowsAutomaticWindowTabbing = false
|
|
|
|
|
super.init()
|
2017-10-18 18:37:45 -07:00
|
|
|
|
|
2020-12-11 18:09:36 -06:00
|
|
|
|
#if !MAC_APP_STORE
|
|
|
|
|
let crashReporterConfig = PLCrashReporterConfig.defaultConfiguration()
|
|
|
|
|
crashReporter = PLCrashReporter(configuration: crashReporterConfig)
|
|
|
|
|
crashReporter.enable()
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-07-30 17:40:45 -05:00
|
|
|
|
SecretsManager.provider = Secrets()
|
2020-01-10 14:00:22 -06:00
|
|
|
|
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!)
|
2020-04-16 15:06:56 -05:00
|
|
|
|
FeedProviderManager.shared.delegate = ExtensionPointManager.shared
|
|
|
|
|
|
2017-10-18 18:37:45 -07:00
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
2019-02-17 18:46:28 -08:00
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(inspectableObjectsDidChange(_:)), name: .InspectableObjectsDidChange, object: nil)
|
2020-02-26 11:29:59 -08:00
|
|
|
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(didWakeNotification(_:)), name: NSWorkspace.didWakeNotification, object: nil)
|
2018-01-21 12:46:22 -08:00
|
|
|
|
|
2017-11-14 21:31:17 -08:00
|
|
|
|
appDelegate = self
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 13:13:40 -08:00
|
|
|
|
// MARK: - API
|
|
|
|
|
func showAddFolderSheetOnWindow(_ window: NSWindow) {
|
|
|
|
|
addFolderWindowController = AddFolderWindowController()
|
|
|
|
|
addFolderWindowController!.runSheetOnWindow(window)
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 21:25:45 -05:00
|
|
|
|
func showAddWebFeedSheetOnWindow(_ window: NSWindow, urlString: String?, name: String?, account: Account?, folder: Folder?) {
|
2018-02-03 21:30:30 -08:00
|
|
|
|
addFeedController = AddFeedController(hostWindow: window)
|
2020-04-21 21:25:45 -05:00
|
|
|
|
addFeedController?.showAddFeedSheet(.webFeed, urlString, name, account, folder)
|
2018-02-03 21:30:30 -08:00
|
|
|
|
}
|
2018-09-13 10:04:20 -05:00
|
|
|
|
|
2017-09-18 22:00:35 -07:00
|
|
|
|
// MARK: - NSApplicationDelegate
|
2019-10-17 22:11:35 -07:00
|
|
|
|
|
2018-11-26 19:22:05 -08:00
|
|
|
|
func applicationWillFinishLaunching(_ notification: Notification) {
|
|
|
|
|
installAppleEventHandlers()
|
2020-03-02 17:46:31 -08:00
|
|
|
|
|
|
|
|
|
CacheCleaner.purgeIfNecessary()
|
|
|
|
|
|
|
|
|
|
// Try to establish a cache in the Caches folder, but if it fails for some reason fall back to a temporary dir
|
|
|
|
|
let cacheFolder: String
|
|
|
|
|
if let userCacheFolder = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path {
|
|
|
|
|
cacheFolder = userCacheFolder
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String)
|
|
|
|
|
cacheFolder = (NSTemporaryDirectory() as NSString).appendingPathComponent(bundleIdentifier)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let faviconsFolder = (cacheFolder as NSString).appendingPathComponent("Favicons")
|
|
|
|
|
let faviconsFolderURL = URL(fileURLWithPath: faviconsFolder)
|
|
|
|
|
try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil)
|
|
|
|
|
faviconDownloader = FaviconDownloader(folder: faviconsFolder)
|
|
|
|
|
|
|
|
|
|
let imagesFolder = (cacheFolder as NSString).appendingPathComponent("Images")
|
|
|
|
|
let imagesFolderURL = URL(fileURLWithPath: imagesFolder)
|
|
|
|
|
try! FileManager.default.createDirectory(at: imagesFolderURL, withIntermediateDirectories: true, attributes: nil)
|
|
|
|
|
imageDownloader = ImageDownloader(folder: imagesFolder)
|
|
|
|
|
|
|
|
|
|
authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader)
|
|
|
|
|
webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
|
|
|
|
|
|
2020-03-04 15:40:40 -07:00
|
|
|
|
appName = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String)
|
2018-11-26 19:22:05 -08:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
func applicationDidFinishLaunching(_ note: Notification) {
|
|
|
|
|
|
2019-10-22 19:33:00 -04:00
|
|
|
|
#if MAC_APP_STORE || TEST
|
2018-12-09 12:12:55 -08:00
|
|
|
|
checkForUpdatesMenuItem.isHidden = true
|
2019-10-22 19:33:00 -04:00
|
|
|
|
#else
|
|
|
|
|
// Initialize Sparkle...
|
|
|
|
|
let hostBundle = Bundle.main
|
|
|
|
|
let updateDriver = SPUStandardUserDriver(hostBundle: hostBundle, delegate: self)
|
|
|
|
|
self.softwareUpdater = SPUUpdater(hostBundle: hostBundle, applicationBundle: hostBundle, userDriver: updateDriver, delegate: self)
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
try self.softwareUpdater.start()
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
NSLog("Failed to start software updater with error: \(error)")
|
|
|
|
|
}
|
2018-12-09 12:12:55 -08:00
|
|
|
|
#endif
|
|
|
|
|
|
2020-07-02 11:17:38 +08:00
|
|
|
|
AppDefaults.shared.registerDefaults()
|
|
|
|
|
let isFirstRun = AppDefaults.shared.isFirstRun
|
2017-11-25 16:10:19 -08:00
|
|
|
|
if isFirstRun {
|
2020-12-02 07:56:22 -06:00
|
|
|
|
os_log(.debug, "Is first run.")
|
2017-11-25 16:10:19 -08:00
|
|
|
|
}
|
2019-05-01 05:53:18 -05:00
|
|
|
|
let localAccount = AccountManager.shared.defaultAccount
|
2019-10-17 13:25:11 -07:00
|
|
|
|
|
|
|
|
|
if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() {
|
|
|
|
|
// Import feeds. Either old NNW 3 feeds or the default feeds.
|
|
|
|
|
if !NNW3ImportController.importSubscriptionsIfFileExists(account: localAccount) {
|
|
|
|
|
DefaultFeedsImporter.importDefaultFeeds(account: localAccount)
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-27 10:43:27 -07:00
|
|
|
|
|
2018-01-27 15:24:33 -08:00
|
|
|
|
updateSortMenuItems()
|
2019-09-08 17:09:26 -05:00
|
|
|
|
updateGroupByFeedMenuItem()
|
2020-03-05 17:42:17 -07:00
|
|
|
|
|
|
|
|
|
if mainWindowController == nil {
|
|
|
|
|
let mainWindowController = createAndShowMainWindow()
|
|
|
|
|
mainWindowController.restoreStateFromUserDefaults()
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-23 18:30:28 -07:00
|
|
|
|
if isFirstRun {
|
|
|
|
|
mainWindowController?.window?.center()
|
|
|
|
|
}
|
2017-10-18 18:37:45 -07:00
|
|
|
|
|
2019-11-14 20:11:41 -06:00
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
|
2018-01-27 15:24:33 -08:00
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
|
2017-11-26 13:16:32 -08:00
|
|
|
|
|
2017-10-18 18:37:45 -07:00
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.unreadCount = AccountManager.shared.unreadCount
|
|
|
|
|
}
|
2017-12-25 10:23:12 -08:00
|
|
|
|
|
2018-01-21 13:27:17 -08:00
|
|
|
|
if InspectorWindowController.shouldOpenAtStartup {
|
2018-01-21 13:30:26 -08:00
|
|
|
|
self.toggleInspectorWindow(self)
|
2018-01-21 13:27:17 -08:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-13 18:03:39 -05:00
|
|
|
|
extensionContainersFile = ExtensionContainersFile()
|
|
|
|
|
extensionFeedAddRequestFile = ExtensionFeedAddRequestFile()
|
|
|
|
|
|
2019-05-15 17:21:58 -05:00
|
|
|
|
refreshTimer = AccountRefreshTimer()
|
|
|
|
|
syncTimer = ArticleStatusSyncTimer()
|
2019-04-24 14:46:01 -05:00
|
|
|
|
|
2020-08-21 12:51:53 -05:00
|
|
|
|
UNUserNotificationCenter.current().requestAuthorization(options:[.badge]) { (granted, error) in }
|
|
|
|
|
|
2020-07-27 13:54:34 -04:00
|
|
|
|
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
|
|
|
|
|
if settings.authorizationStatus == .authorized {
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
NSApplication.shared.registerForRemoteNotifications()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-02 19:42:16 -05:00
|
|
|
|
|
|
|
|
|
UNUserNotificationCenter.current().delegate = self
|
|
|
|
|
userNotificationManager = UserNotificationManager()
|
|
|
|
|
|
2020-07-02 11:17:38 +08:00
|
|
|
|
if AppDefaults.shared.showDebugMenu {
|
2019-11-28 14:59:45 -06:00
|
|
|
|
refreshTimer!.update()
|
|
|
|
|
syncTimer!.update()
|
|
|
|
|
|
|
|
|
|
// The Web Inspector uses SPI and can never appear in a MAC_APP_STORE build.
|
|
|
|
|
#if MAC_APP_STORE
|
|
|
|
|
let debugMenu = debugMenuItem.submenu!
|
|
|
|
|
let toggleWebInspectorItemIndex = debugMenu.indexOfItem(withTarget: self, andAction: #selector(toggleWebInspectorEnabled(_:)))
|
|
|
|
|
if toggleWebInspectorItemIndex != -1 {
|
|
|
|
|
debugMenu.removeItem(at: toggleWebInspectorItemIndex)
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
2018-01-27 11:32:59 -08:00
|
|
|
|
debugMenuItem.menu?.removeItem(debugMenuItem)
|
2017-12-25 10:23:12 -08:00
|
|
|
|
DispatchQueue.main.async {
|
2019-04-24 14:46:01 -05:00
|
|
|
|
self.refreshTimer!.timedRefresh(nil)
|
2019-05-15 17:21:58 -05:00
|
|
|
|
self.syncTimer!.timedRefresh(nil)
|
2017-12-25 10:23:12 -08:00
|
|
|
|
}
|
2019-11-28 14:59:45 -06:00
|
|
|
|
}
|
2019-01-27 18:00:09 -08:00
|
|
|
|
|
2019-01-11 23:18:48 -08:00
|
|
|
|
#if !MAC_APP_STORE
|
2020-12-11 18:09:36 -06:00
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
CrashReporter.check(crashReporter: self.crashReporter)
|
|
|
|
|
}
|
2019-01-11 23:18:48 -08:00
|
|
|
|
#endif
|
2020-03-30 02:48:25 -05:00
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
2019-10-03 15:49:27 -05:00
|
|
|
|
|
|
|
|
|
func application(_ application: NSApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([NSUserActivityRestoring]) -> Void) -> Bool {
|
|
|
|
|
guard let mainWindowController = mainWindowController else {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
mainWindowController.handle(userActivity)
|
|
|
|
|
return true
|
|
|
|
|
}
|
2017-05-27 10:43:27 -07:00
|
|
|
|
|
|
|
|
|
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
2019-01-27 20:25:09 -08:00
|
|
|
|
// https://github.com/brentsimmons/NetNewsWire/issues/522
|
|
|
|
|
// I couldn’t reproduce the crashing bug, but it appears to happen on creating a main window
|
|
|
|
|
// and its views and view controllers. The check below is so that the app does nothing
|
|
|
|
|
// if the window doesn’t already exist — because it absolutely *should* exist already.
|
|
|
|
|
// And if the window exists, then maybe the views and view controllers are also already loaded?
|
|
|
|
|
// We’ll try this, and then see if we get more crash logs like this or not.
|
|
|
|
|
guard let mainWindowController = mainWindowController, mainWindowController.isWindowLoaded else {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
mainWindowController.showWindow(self)
|
2017-05-27 10:43:27 -07:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-27 18:00:09 -08:00
|
|
|
|
func applicationDidBecomeActive(_ notification: Notification) {
|
2020-02-26 11:29:59 -08:00
|
|
|
|
fireOldTimers()
|
2019-01-27 18:00:09 -08:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
func applicationDidResignActive(_ notification: Notification) {
|
2019-10-20 02:28:00 -05:00
|
|
|
|
ArticleStringFormatter.emptyCaches()
|
2018-01-21 13:27:17 -08:00
|
|
|
|
saveState()
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-30 02:48:25 -05:00
|
|
|
|
func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) {
|
|
|
|
|
AccountManager.shared.receiveRemoteNotification(userInfo: userInfo)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-21 13:27:17 -08:00
|
|
|
|
func applicationWillTerminate(_ notification: Notification) {
|
2019-01-27 18:00:09 -08:00
|
|
|
|
shuttingDown = true
|
2018-01-21 13:27:17 -08:00
|
|
|
|
saveState()
|
2021-01-09 17:58:51 -06:00
|
|
|
|
|
2021-01-18 17:48:07 -06:00
|
|
|
|
AccountManager.shared.sendArticleStatusAll() {
|
2021-01-09 17:58:51 -06:00
|
|
|
|
self.isShutDownSyncDone = true
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-18 17:48:07 -06:00
|
|
|
|
let timeout = Date().addingTimeInterval(2)
|
|
|
|
|
while !isShutDownSyncDone && RunLoop.current.run(mode: .default, before: .distantFuture) && timeout > Date() { }
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Notifications
|
2017-10-18 18:37:45 -07:00
|
|
|
|
@objc func unreadCountDidChange(_ note: Notification) {
|
|
|
|
|
if note.object is AccountManager {
|
|
|
|
|
unreadCount = AccountManager.shared.unreadCount
|
|
|
|
|
}
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-14 20:11:41 -06:00
|
|
|
|
@objc func webFeedSettingDidChange(_ note: Notification) {
|
|
|
|
|
guard let feed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else {
|
2017-11-26 13:16:32 -08:00
|
|
|
|
return
|
|
|
|
|
}
|
2019-11-14 20:11:41 -06:00
|
|
|
|
if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL {
|
2019-03-17 13:54:30 -07:00
|
|
|
|
let _ = faviconDownloader.favicon(for: feed)
|
|
|
|
|
}
|
2017-11-26 13:16:32 -08:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-17 18:46:28 -08:00
|
|
|
|
@objc func inspectableObjectsDidChange(_ note: Notification) {
|
2018-01-21 12:46:22 -08:00
|
|
|
|
guard let inspectorWindowController = inspectorWindowController, inspectorWindowController.isOpen else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
inspectorWindowController.objects = objectsForInspector()
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 15:24:33 -08:00
|
|
|
|
@objc func userDefaultsDidChange(_ note: Notification) {
|
|
|
|
|
updateSortMenuItems()
|
2019-09-08 17:09:26 -05:00
|
|
|
|
updateGroupByFeedMenuItem()
|
2019-04-23 11:20:44 -05:00
|
|
|
|
refreshTimer?.update()
|
2019-09-03 22:17:31 -07:00
|
|
|
|
updateDockBadge()
|
2018-01-27 15:24:33 -08:00
|
|
|
|
}
|
2020-02-26 11:29:59 -08:00
|
|
|
|
|
|
|
|
|
@objc func didWakeNotification(_ note: Notification) {
|
|
|
|
|
fireOldTimers()
|
|
|
|
|
}
|
2018-01-27 15:24:33 -08:00
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
// MARK: Main Window
|
2020-03-02 17:46:31 -08:00
|
|
|
|
|
|
|
|
|
func createMainWindowController() -> MainWindowController {
|
2020-08-08 20:49:22 -05:00
|
|
|
|
let controller: MainWindowController
|
2020-08-10 11:43:18 -05:00
|
|
|
|
if #available(macOS 11.0, *) {
|
2020-12-06 15:21:30 -06:00
|
|
|
|
controller = windowControllerWithName("UnifiedWindow") as! MainWindowController
|
2020-08-08 20:49:22 -05:00
|
|
|
|
} else {
|
|
|
|
|
controller = windowControllerWithName("MainWindow") as! MainWindowController
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-05 17:42:17 -07:00
|
|
|
|
if !(mainWindowController?.isOpen ?? false) {
|
|
|
|
|
mainWindowControllers.removeAll()
|
|
|
|
|
}
|
2020-03-02 17:46:31 -08:00
|
|
|
|
mainWindowControllers.append(controller)
|
|
|
|
|
return controller
|
|
|
|
|
}
|
2017-05-27 10:43:27 -07:00
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
func windowControllerWithName(_ storyboardName: String) -> NSWindowController {
|
2018-12-09 12:32:33 -08:00
|
|
|
|
let storyboard = NSStoryboard(name: NSStoryboard.Name(storyboardName), bundle: nil)
|
2017-05-27 10:43:27 -07:00
|
|
|
|
return storyboard.instantiateInitialController()! as! NSWindowController
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
@discardableResult
|
2020-03-05 17:42:17 -07:00
|
|
|
|
func createAndShowMainWindow() -> MainWindowController {
|
2020-03-02 17:46:31 -08:00
|
|
|
|
let controller = createMainWindowController()
|
|
|
|
|
controller.showWindow(self)
|
|
|
|
|
|
|
|
|
|
if let window = controller.window {
|
|
|
|
|
window.restorationClass = Self.self
|
|
|
|
|
window.identifier = NSUserInterfaceItemIdentifier(rawValue: WindowRestorationIdentifiers.mainWindow)
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-05 17:42:17 -07:00
|
|
|
|
return controller
|
2020-03-02 17:46:31 -08:00
|
|
|
|
}
|
2017-05-27 10:43:27 -07:00
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
func createAndShowMainWindowIfNecessary() {
|
2017-05-27 10:43:27 -07:00
|
|
|
|
if mainWindowController == nil {
|
2020-03-02 17:46:31 -08:00
|
|
|
|
createAndShowMainWindow()
|
2020-03-05 17:42:17 -07:00
|
|
|
|
} else {
|
|
|
|
|
mainWindowController?.showWindow(self)
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 18:06:55 -08:00
|
|
|
|
func removeMainWindow(_ windowController: MainWindowController) {
|
2020-03-05 17:42:17 -07:00
|
|
|
|
guard mainWindowControllers.count > 1 else { return }
|
2020-03-02 18:06:55 -08:00
|
|
|
|
if let index = mainWindowControllers.firstIndex(of: windowController) {
|
|
|
|
|
mainWindowControllers.remove(at: index)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
// MARK: NSUserInterfaceValidations
|
|
|
|
|
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
2019-01-27 18:00:09 -08:00
|
|
|
|
if shuttingDown {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2017-05-27 10:43:27 -07:00
|
|
|
|
|
2018-02-03 10:56:12 -08:00
|
|
|
|
let isDisplayingSheet = mainWindowController?.isDisplayingSheet ?? false
|
2020-08-11 20:19:17 -05:00
|
|
|
|
let isSpecialAccountAvailable = AccountManager.shared.activeAccounts.contains(where: { $0.type == .onMyMac || $0.type == .cloudKit })
|
2018-02-03 10:56:12 -08:00
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
if item.action == #selector(refreshAll(_:)) {
|
2019-05-19 14:23:54 -05:00
|
|
|
|
return !AccountManager.shared.refreshInProgress && !AccountManager.shared.activeAccounts.isEmpty
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
2020-10-18 20:32:10 -05:00
|
|
|
|
|
|
|
|
|
if item.action == #selector(importOPMLFromFile(_:)) {
|
|
|
|
|
return AccountManager.shared.activeAccounts.contains(where: { !$0.behaviors.contains(where: { $0 == .disallowOPMLImports }) })
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
if item.action == #selector(addAppNews(_:)) {
|
2020-10-29 17:52:58 -05:00
|
|
|
|
return !isDisplayingSheet && !AccountManager.shared.anyAccountHasNetNewsWireNewsSubscription() && !AccountManager.shared.activeAccounts.isEmpty
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
2020-10-18 20:32:10 -05:00
|
|
|
|
|
2018-01-27 15:13:45 -08:00
|
|
|
|
if item.action == #selector(sortByNewestArticleOnTop(_:)) || item.action == #selector(sortByOldestArticleOnTop(_:)) {
|
|
|
|
|
return mainWindowController?.isOpen ?? false
|
|
|
|
|
}
|
2020-10-18 20:32:10 -05:00
|
|
|
|
|
2020-04-21 21:25:45 -05:00
|
|
|
|
if item.action == #selector(showAddWebFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) {
|
2019-05-19 10:21:42 -05:00
|
|
|
|
return !isDisplayingSheet && !AccountManager.shared.activeAccounts.isEmpty
|
2018-02-03 10:56:12 -08:00
|
|
|
|
}
|
2020-10-18 20:32:10 -05:00
|
|
|
|
|
2020-05-10 11:44:30 -05:00
|
|
|
|
if item.action == #selector(showAddRedditFeedWindow(_:)) {
|
2020-08-12 10:27:58 -05:00
|
|
|
|
guard !isDisplayingSheet && isSpecialAccountAvailable && ExtensionPointManager.shared.isRedditEnabled else {
|
2020-05-10 11:44:30 -05:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return ExtensionPointManager.shared.isRedditEnabled
|
|
|
|
|
}
|
2020-10-18 20:32:10 -05:00
|
|
|
|
|
2020-04-22 11:36:07 -05:00
|
|
|
|
if item.action == #selector(showAddTwitterFeedWindow(_:)) {
|
2020-08-12 10:27:58 -05:00
|
|
|
|
guard !isDisplayingSheet && isSpecialAccountAvailable && ExtensionPointManager.shared.isTwitterEnabled else {
|
2020-05-02 15:06:59 -05:00
|
|
|
|
return false
|
|
|
|
|
}
|
2020-05-02 17:21:01 -05:00
|
|
|
|
return ExtensionPointManager.shared.isTwitterEnabled
|
2020-04-22 11:36:07 -05:00
|
|
|
|
}
|
2020-10-18 20:32:10 -05:00
|
|
|
|
|
2019-09-19 11:17:58 -05:00
|
|
|
|
#if !MAC_APP_STORE
|
2019-09-16 21:06:35 -05:00
|
|
|
|
if item.action == #selector(toggleWebInspectorEnabled(_:)) {
|
2020-07-02 11:17:38 +08:00
|
|
|
|
(item as! NSMenuItem).state = AppDefaults.shared.webInspectorEnabled ? .on : .off
|
2019-09-16 21:06:35 -05:00
|
|
|
|
}
|
2019-09-19 10:38:17 -05:00
|
|
|
|
#endif
|
2020-10-18 20:32:10 -05:00
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-02 19:42:16 -05:00
|
|
|
|
// MARK: UNUserNotificationCenterDelegate
|
|
|
|
|
|
|
|
|
|
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
|
|
|
completionHandler([.alert, .badge, .sound])
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-03 11:39:48 -05:00
|
|
|
|
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
2020-12-23 21:44:45 +08:00
|
|
|
|
|
|
|
|
|
let userInfo = response.notification.request.content.userInfo
|
|
|
|
|
|
|
|
|
|
switch response.actionIdentifier {
|
|
|
|
|
case "MARK_AS_READ":
|
|
|
|
|
handleMarkAsRead(userInfo: userInfo)
|
|
|
|
|
case "MARK_AS_STARRED":
|
|
|
|
|
handleMarkAsStarred(userInfo: userInfo)
|
|
|
|
|
default:
|
|
|
|
|
mainWindowController?.handle(response)
|
|
|
|
|
}
|
2019-10-03 11:39:48 -05:00
|
|
|
|
completionHandler()
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
// MARK: Add Feed
|
2020-04-21 21:25:45 -05:00
|
|
|
|
func addWebFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
|
2020-03-02 17:46:31 -08:00
|
|
|
|
createAndShowMainWindowIfNecessary()
|
|
|
|
|
|
2018-02-03 10:56:12 -08:00
|
|
|
|
if mainWindowController!.isDisplayingSheet {
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-05-27 10:43:27 -07:00
|
|
|
|
|
2020-04-21 21:25:45 -05:00
|
|
|
|
showAddWebFeedSheetOnWindow(mainWindowController!.window!, urlString: urlString, name: name, account: account, folder: folder)
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-27 21:19:19 -08:00
|
|
|
|
// MARK: - Dock Badge
|
|
|
|
|
@objc func updateDockBadge() {
|
2020-07-02 11:17:38 +08:00
|
|
|
|
let label = unreadCount > 0 && !AppDefaults.shared.hideDockUnreadCount ? "\(unreadCount)" : ""
|
2018-12-27 21:19:19 -08:00
|
|
|
|
NSApplication.shared.dockTile.badgeLabel = label
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-23 12:17:14 -07:00
|
|
|
|
// MARK: - Actions
|
2018-02-11 18:58:50 -08:00
|
|
|
|
@IBAction func showPreferences(_ sender: Any?) {
|
2017-05-27 10:43:27 -07:00
|
|
|
|
if preferencesWindowController == nil {
|
|
|
|
|
preferencesWindowController = windowControllerWithName("Preferences")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
preferencesWindowController!.showWindow(self)
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
@IBAction func newMainWindow(_ sender: Any?) {
|
2017-05-27 10:43:27 -07:00
|
|
|
|
createAndShowMainWindow()
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
@IBAction func showMainWindow(_ sender: Any?) {
|
|
|
|
|
createAndShowMainWindowIfNecessary()
|
2020-03-04 18:22:15 -07:00
|
|
|
|
mainWindowController?.window?.makeKey()
|
2020-03-02 17:46:31 -08:00
|
|
|
|
}
|
2017-05-27 10:43:27 -07:00
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
@IBAction func refreshAll(_ sender: Any?) {
|
2019-05-26 11:54:32 -05:00
|
|
|
|
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present)
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 21:25:45 -05:00
|
|
|
|
@IBAction func showAddWebFeedWindow(_ sender: Any?) {
|
|
|
|
|
addWebFeed(nil)
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-10 11:44:30 -05:00
|
|
|
|
@IBAction func showAddRedditFeedWindow(_ sender: Any?) {
|
|
|
|
|
createAndShowMainWindowIfNecessary()
|
|
|
|
|
addFeedController = AddFeedController(hostWindow: mainWindowController!.window!)
|
|
|
|
|
addFeedController?.showAddFeedSheet(.redditFeed)
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 21:25:45 -05:00
|
|
|
|
@IBAction func showAddTwitterFeedWindow(_ sender: Any?) {
|
|
|
|
|
createAndShowMainWindowIfNecessary()
|
|
|
|
|
addFeedController = AddFeedController(hostWindow: mainWindowController!.window!)
|
|
|
|
|
addFeedController?.showAddFeedSheet(.twitterFeed)
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-11 18:58:50 -08:00
|
|
|
|
@IBAction func showAddFolderWindow(_ sender: Any?) {
|
2020-03-02 17:46:31 -08:00
|
|
|
|
createAndShowMainWindowIfNecessary()
|
2017-11-15 13:13:40 -08:00
|
|
|
|
showAddFolderSheetOnWindow(mainWindowController!.window!)
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-13 18:33:23 -08:00
|
|
|
|
@IBAction func showKeyboardShortcutsWindow(_ sender: Any?) {
|
|
|
|
|
if keyboardShortcutsWindowController == nil {
|
2020-03-02 17:46:31 -08:00
|
|
|
|
|
2017-11-13 18:33:23 -08:00
|
|
|
|
keyboardShortcutsWindowController = WebViewWindowController(title: NSLocalizedString("Keyboard Shortcuts", comment: "window title"))
|
|
|
|
|
let htmlFile = Bundle(for: type(of: self)).path(forResource: "KeyboardShortcuts", ofType: "html")!
|
|
|
|
|
keyboardShortcutsWindowController?.displayContents(of: htmlFile)
|
2017-12-20 17:23:46 -08:00
|
|
|
|
|
2017-12-22 11:13:20 -08:00
|
|
|
|
if let window = keyboardShortcutsWindowController?.window {
|
|
|
|
|
let point = NSPoint(x: 128, y: 64)
|
2019-03-04 14:39:30 -06:00
|
|
|
|
let size = NSSize(width: 620, height: 1100)
|
2017-12-22 11:13:20 -08:00
|
|
|
|
let minSize = NSSize(width: 400, height: 400)
|
|
|
|
|
window.setPointAndSizeAdjustingForScreen(point: point, size: size, minimumSize: minSize)
|
2017-12-20 17:23:46 -08:00
|
|
|
|
}
|
2020-03-02 17:46:31 -08:00
|
|
|
|
|
2017-11-13 18:33:23 -08:00
|
|
|
|
}
|
2017-12-20 17:23:46 -08:00
|
|
|
|
|
2017-11-13 18:33:23 -08:00
|
|
|
|
keyboardShortcutsWindowController!.showWindow(self)
|
|
|
|
|
}
|
2017-11-15 22:33:35 -08:00
|
|
|
|
|
|
|
|
|
@IBAction func toggleInspectorWindow(_ sender: Any?) {
|
|
|
|
|
if inspectorWindowController == nil {
|
2018-01-20 19:06:07 -08:00
|
|
|
|
inspectorWindowController = (windowControllerWithName("Inspector") as! InspectorWindowController)
|
2017-11-15 22:33:35 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if inspectorWindowController!.isOpen {
|
|
|
|
|
inspectorWindowController!.window!.performClose(self)
|
|
|
|
|
}
|
|
|
|
|
else {
|
2018-01-21 12:46:22 -08:00
|
|
|
|
inspectorWindowController!.objects = objectsForInspector()
|
2017-11-15 22:33:35 -08:00
|
|
|
|
inspectorWindowController!.showWindow(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-11 18:58:50 -08:00
|
|
|
|
@IBAction func importOPMLFromFile(_ sender: Any?) {
|
2020-03-02 17:46:31 -08:00
|
|
|
|
createAndShowMainWindowIfNecessary()
|
2019-05-01 16:04:56 -05:00
|
|
|
|
if mainWindowController!.isDisplayingSheet {
|
|
|
|
|
return
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
2019-05-01 16:04:56 -05:00
|
|
|
|
|
|
|
|
|
importOPMLController = ImportOPMLWindowController()
|
|
|
|
|
importOPMLController?.runSheetOnWindow(mainWindowController!.window!)
|
2019-10-14 20:45:58 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func importNNW3FromFile(_ sender: Any?) {
|
2020-03-02 17:46:31 -08:00
|
|
|
|
createAndShowMainWindowIfNecessary()
|
2019-10-14 20:45:58 -05:00
|
|
|
|
if mainWindowController!.isDisplayingSheet {
|
|
|
|
|
return
|
|
|
|
|
}
|
2019-10-17 13:25:11 -07:00
|
|
|
|
NNW3ImportController.askUserToImportNNW3Subscriptions(window: mainWindowController!.window!)
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
2017-09-23 12:17:14 -07:00
|
|
|
|
|
2018-02-11 18:58:50 -08:00
|
|
|
|
@IBAction func exportOPML(_ sender: Any?) {
|
2020-03-02 17:46:31 -08:00
|
|
|
|
createAndShowMainWindowIfNecessary()
|
2019-05-01 16:04:56 -05:00
|
|
|
|
if mainWindowController!.isDisplayingSheet {
|
|
|
|
|
return
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
2019-05-01 16:04:56 -05:00
|
|
|
|
|
|
|
|
|
exportOPMLController = ExportOPMLWindowController()
|
|
|
|
|
exportOPMLController?.runSheetOnWindow(mainWindowController!.window!)
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
2017-09-23 12:17:14 -07:00
|
|
|
|
|
2018-02-11 18:58:50 -08:00
|
|
|
|
@IBAction func addAppNews(_ sender: Any?) {
|
2020-10-29 17:52:58 -05:00
|
|
|
|
if AccountManager.shared.anyAccountHasNetNewsWireNewsSubscription() {
|
2017-05-27 10:43:27 -07:00
|
|
|
|
return
|
|
|
|
|
}
|
2020-10-29 17:52:58 -05:00
|
|
|
|
addWebFeed(AccountManager.netNewsWireNewsURL, name: "NetNewsWire News")
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
2017-05-27 13:37:50 -07:00
|
|
|
|
|
2018-02-11 18:58:50 -08:00
|
|
|
|
@IBAction func openWebsite(_ sender: Any?) {
|
2017-05-27 13:37:50 -07:00
|
|
|
|
|
2018-08-28 22:18:24 -07:00
|
|
|
|
Browser.open("https://ranchero.com/netnewswire/", inBackground: false)
|
2017-05-27 13:37:50 -07:00
|
|
|
|
}
|
2020-08-15 15:01:00 +08:00
|
|
|
|
|
|
|
|
|
@IBAction func openReleaseNotes(_ sender: Any?) {
|
|
|
|
|
Browser.open(URL.releaseNotes.absoluteString, inBackground: false)
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 13:37:50 -07:00
|
|
|
|
|
2019-06-14 17:38:00 -05:00
|
|
|
|
@IBAction func openHowToSupport(_ sender: Any?) {
|
|
|
|
|
|
2020-07-26 05:46:48 -04:00
|
|
|
|
Browser.open("https://github.com/brentsimmons/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown", inBackground: false)
|
2019-06-14 17:38:00 -05:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-11 18:58:50 -08:00
|
|
|
|
@IBAction func openRepository(_ sender: Any?) {
|
2017-05-27 13:37:50 -07:00
|
|
|
|
|
2018-08-28 22:18:24 -07:00
|
|
|
|
Browser.open("https://github.com/brentsimmons/NetNewsWire", inBackground: false)
|
2017-05-27 13:37:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-11 18:58:50 -08:00
|
|
|
|
@IBAction func openBugTracker(_ sender: Any?) {
|
2017-05-27 13:37:50 -07:00
|
|
|
|
|
2018-08-28 22:18:24 -07:00
|
|
|
|
Browser.open("https://github.com/brentsimmons/NetNewsWire/issues", inBackground: false)
|
2017-05-27 13:37:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-25 10:21:25 -07:00
|
|
|
|
@IBAction func openSlackGroup(_ sender: Any?) {
|
2019-09-29 22:46:40 -07:00
|
|
|
|
Browser.open("https://ranchero.com/netnewswire/slack", inBackground: false)
|
2019-05-25 10:21:25 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-20 21:23:48 -08:00
|
|
|
|
@IBAction func openTechnotes(_ sender: Any?) {
|
|
|
|
|
|
2020-07-26 05:46:48 -04:00
|
|
|
|
Browser.open("https://github.com/brentsimmons/NetNewsWire/tree/main/Technotes", inBackground: false)
|
2017-12-20 21:23:48 -08:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-11 18:58:50 -08:00
|
|
|
|
@IBAction func showHelp(_ sender: Any?) {
|
2017-05-27 13:37:50 -07:00
|
|
|
|
|
2020-09-08 09:03:11 -04:00
|
|
|
|
Browser.open("https://ranchero.com/netnewswire/help/mac/5.1/en/", inBackground: false)
|
2017-05-27 13:37:50 -07:00
|
|
|
|
}
|
2017-11-16 18:23:07 -08:00
|
|
|
|
|
2018-02-12 13:10:13 -08:00
|
|
|
|
@IBAction func donateToAppCampForGirls(_ sender: Any?) {
|
|
|
|
|
Browser.open("https://appcamp4girls.com/contribute/", inBackground: false)
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-29 13:59:14 -08:00
|
|
|
|
@IBAction func showPrivacyPolicy(_ sender: Any?) {
|
|
|
|
|
Browser.open("https://ranchero.com/netnewswire/privacypolicy", inBackground: false)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 12:39:07 -08:00
|
|
|
|
@IBAction func gotoToday(_ sender: Any?) {
|
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
createAndShowMainWindowIfNecessary()
|
2018-01-27 12:39:07 -08:00
|
|
|
|
mainWindowController!.gotoToday(sender)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func gotoAllUnread(_ sender: Any?) {
|
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
createAndShowMainWindowIfNecessary()
|
2018-01-27 12:39:07 -08:00
|
|
|
|
mainWindowController!.gotoAllUnread(sender)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func gotoStarred(_ sender: Any?) {
|
|
|
|
|
|
2020-03-02 17:46:31 -08:00
|
|
|
|
createAndShowMainWindowIfNecessary()
|
2018-01-27 12:39:07 -08:00
|
|
|
|
mainWindowController!.gotoStarred(sender)
|
|
|
|
|
}
|
2018-01-27 15:11:02 -08:00
|
|
|
|
|
|
|
|
|
@IBAction func sortByOldestArticleOnTop(_ sender: Any?) {
|
|
|
|
|
|
2020-07-02 11:17:38 +08:00
|
|
|
|
AppDefaults.shared.timelineSortDirection = .orderedAscending
|
2018-01-27 15:11:02 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func sortByNewestArticleOnTop(_ sender: Any?) {
|
|
|
|
|
|
2020-07-02 11:17:38 +08:00
|
|
|
|
AppDefaults.shared.timelineSortDirection = .orderedDescending
|
2018-01-27 15:11:02 -08:00
|
|
|
|
}
|
2019-09-08 17:09:26 -05:00
|
|
|
|
|
|
|
|
|
@IBAction func groupByFeedToggled(_ sender: NSMenuItem) {
|
2020-07-02 11:17:38 +08:00
|
|
|
|
AppDefaults.shared.timelineGroupByFeed.toggle()
|
2019-09-08 17:09:26 -05:00
|
|
|
|
}
|
2019-10-22 19:33:00 -04:00
|
|
|
|
|
|
|
|
|
@IBAction func checkForUpdates(_ sender: Any?) {
|
|
|
|
|
#if !MAC_APP_STORE && !TEST
|
|
|
|
|
self.softwareUpdater.checkForUpdates()
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 10:43:27 -07:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-18 22:29:43 -08:00
|
|
|
|
// MARK: - Debug Menu
|
|
|
|
|
extension AppDelegate {
|
|
|
|
|
|
|
|
|
|
@IBAction func debugSearch(_ sender: Any?) {
|
2019-05-01 05:53:18 -05:00
|
|
|
|
AccountManager.shared.defaultAccount.debugRunSearch()
|
2019-02-18 22:29:43 -08:00
|
|
|
|
}
|
2019-09-16 21:06:35 -05:00
|
|
|
|
|
2020-05-15 17:29:24 -05:00
|
|
|
|
@IBAction func debugDropConditionalGetInfo(_ sender: Any?) {
|
|
|
|
|
#if DEBUG
|
|
|
|
|
AccountManager.shared.activeAccounts.forEach{ $0.debugDropConditionalGetInfo() }
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func debugTestCrashReporterWindow(_ sender: Any?) {
|
|
|
|
|
#if DEBUG
|
|
|
|
|
crashReportWindowController = CrashReportWindowController(crashLogText: "This is a test crash log.")
|
|
|
|
|
crashReportWindowController!.testing = true
|
|
|
|
|
crashReportWindowController!.showWindow(self)
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func debugTestCrashReportSending(_ sender: Any?) {
|
|
|
|
|
#if DEBUG
|
|
|
|
|
CrashReporter.sendCrashLogText("This is a test. Hi, Brent.")
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func openApplicationSupportFolder(_ sender: Any?) {
|
|
|
|
|
#if DEBUG
|
|
|
|
|
guard let appSupport = Platform.dataSubfolder(forApplication: nil, folderName: "") else { return }
|
|
|
|
|
NSWorkspace.shared.open(URL(fileURLWithPath: appSupport))
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-16 21:06:35 -05:00
|
|
|
|
@IBAction func toggleWebInspectorEnabled(_ sender: Any?) {
|
2019-09-19 11:17:58 -05:00
|
|
|
|
#if !MAC_APP_STORE
|
2020-07-02 11:17:38 +08:00
|
|
|
|
let newValue = !AppDefaults.shared.webInspectorEnabled
|
|
|
|
|
AppDefaults.shared.webInspectorEnabled = newValue
|
2019-09-17 13:58:45 -05:00
|
|
|
|
|
2019-09-19 10:38:17 -05:00
|
|
|
|
// An attached inspector can display incorrectly on certain setups (like mine); default to displaying in a separate window,
|
|
|
|
|
// and reset the default to a separate window when the preference is toggled off and on again in case the inspector is
|
|
|
|
|
// accidentally reattached.
|
2020-07-02 11:17:38 +08:00
|
|
|
|
AppDefaults.shared.webInspectorStartsAttached = false
|
2019-09-19 10:38:17 -05:00
|
|
|
|
NotificationCenter.default.post(name: .WebInspectorEnabledDidChange, object: newValue)
|
|
|
|
|
#endif
|
2019-09-16 21:06:35 -05:00
|
|
|
|
}
|
2020-05-15 17:29:24 -05:00
|
|
|
|
|
2019-02-18 22:29:43 -08:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-14 13:18:25 -08:00
|
|
|
|
private extension AppDelegate {
|
2017-05-22 13:00:45 -07:00
|
|
|
|
|
2020-02-26 11:29:59 -08:00
|
|
|
|
func fireOldTimers() {
|
|
|
|
|
// It’s possible there’s a refresh timer set to go off in the past.
|
|
|
|
|
// In that case, refresh now and update the timer.
|
|
|
|
|
refreshTimer?.fireOldTimer()
|
|
|
|
|
syncTimer?.fireOldTimer()
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-21 12:46:22 -08:00
|
|
|
|
func objectsForInspector() -> [Any]? {
|
|
|
|
|
|
|
|
|
|
guard let window = NSApplication.shared.mainWindow, let windowController = window.windowController as? MainWindowController else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return windowController.selectedObjectsInSidebar()
|
|
|
|
|
}
|
2018-01-21 13:27:17 -08:00
|
|
|
|
|
|
|
|
|
func saveState() {
|
2020-03-05 17:42:17 -07:00
|
|
|
|
mainWindowController?.saveStateToUserDefaults()
|
2018-02-12 13:31:43 -08:00
|
|
|
|
inspectorWindowController?.saveState()
|
2018-01-21 13:27:17 -08:00
|
|
|
|
}
|
2018-01-27 15:24:33 -08:00
|
|
|
|
|
|
|
|
|
func updateSortMenuItems() {
|
|
|
|
|
|
2020-07-02 11:17:38 +08:00
|
|
|
|
let sortByNewestOnTop = AppDefaults.shared.timelineSortDirection == .orderedDescending
|
2018-01-27 15:24:33 -08:00
|
|
|
|
sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off
|
|
|
|
|
sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on
|
|
|
|
|
}
|
2019-09-08 17:09:26 -05:00
|
|
|
|
|
|
|
|
|
func updateGroupByFeedMenuItem() {
|
2020-07-02 11:17:38 +08:00
|
|
|
|
let groupByFeedEnabled = AppDefaults.shared.timelineGroupByFeed
|
2019-09-08 17:09:26 -05:00
|
|
|
|
groupArticlesByFeedMenuItem.state = groupByFeedEnabled ? .on : .off
|
|
|
|
|
}
|
2017-11-14 13:18:25 -08:00
|
|
|
|
}
|
2018-02-08 00:11:52 -08:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
the ScriptingAppDelegate protocol exposes a narrow set of accessors with
|
|
|
|
|
internal visibility which are very similar to some private vars.
|
|
|
|
|
|
|
|
|
|
These would be unnecessary if the similar accessors were marked internal rather than private,
|
|
|
|
|
but for now, we'll keep the stratification of visibility
|
|
|
|
|
*/
|
|
|
|
|
extension AppDelegate : ScriptingAppDelegate {
|
|
|
|
|
|
|
|
|
|
internal var scriptingMainWindowController: ScriptingMainWindowController? {
|
|
|
|
|
return mainWindowController
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal var scriptingCurrentArticle: Article? {
|
|
|
|
|
return self.scriptingMainWindowController?.scriptingCurrentArticle
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal var scriptingSelectedArticles: [Article] {
|
|
|
|
|
return self.scriptingMainWindowController?.scriptingSelectedArticles ?? []
|
|
|
|
|
}
|
2019-10-20 02:28:00 -05:00
|
|
|
|
}
|
2020-03-02 17:46:31 -08:00
|
|
|
|
|
|
|
|
|
extension AppDelegate: NSWindowRestoration {
|
|
|
|
|
|
|
|
|
|
@objc static func restoreWindow(withIdentifier identifier: NSUserInterfaceItemIdentifier, state: NSCoder, completionHandler: @escaping (NSWindow?, Error?) -> Void) {
|
|
|
|
|
var mainWindow: NSWindow? = nil
|
|
|
|
|
if identifier.rawValue == WindowRestorationIdentifiers.mainWindow {
|
2020-03-05 17:42:17 -07:00
|
|
|
|
mainWindow = appDelegate.createAndShowMainWindow().window
|
2020-03-02 17:46:31 -08:00
|
|
|
|
}
|
|
|
|
|
completionHandler(mainWindow, nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2020-12-23 21:44:45 +08:00
|
|
|
|
|
|
|
|
|
// Handle Notification Actions
|
|
|
|
|
|
|
|
|
|
private extension AppDelegate {
|
|
|
|
|
|
|
|
|
|
func handleMarkAsRead(userInfo: [AnyHashable: Any]) {
|
|
|
|
|
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
|
|
|
|
|
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
|
|
|
|
|
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let account = AccountManager.shared.existingAccount(with: accountID)
|
|
|
|
|
guard account != nil else {
|
|
|
|
|
os_log(.debug, "No account found from notification.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let article = try? account!.fetchArticles(.articleIDs([articleID]))
|
|
|
|
|
guard article != nil else {
|
|
|
|
|
os_log(.debug, "No article found from search using %@", articleID)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
account!.markArticles(article!, statusKey: .read, flag: true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func handleMarkAsStarred(userInfo: [AnyHashable: Any]) {
|
|
|
|
|
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
|
|
|
|
|
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
|
|
|
|
|
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let account = AccountManager.shared.existingAccount(with: accountID)
|
|
|
|
|
guard account != nil else {
|
|
|
|
|
os_log(.debug, "No account found from notification.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let article = try? account!.fetchArticles(.articleIDs([articleID]))
|
|
|
|
|
guard article != nil else {
|
|
|
|
|
os_log(.debug, "No article found from search using %@", articleID)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
account!.markArticles(article!, statusKey: .starred, flag: true)
|
|
|
|
|
}
|
|
|
|
|
}
|