2017-05-22 22:00:45 +02:00
|
|
|
|
//
|
|
|
|
|
// AppDelegate.swift
|
2018-08-29 07:18:24 +02:00
|
|
|
|
// NetNewsWire
|
2017-05-22 22:00:45 +02:00
|
|
|
|
//
|
2017-05-27 19:43:27 +02:00
|
|
|
|
// Created by Brent Simmons on 7/11/15.
|
|
|
|
|
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
|
2017-05-22 22:00:45 +02:00
|
|
|
|
//
|
|
|
|
|
|
2018-02-03 07:51:32 +01:00
|
|
|
|
import AppKit
|
2018-07-24 03:29:08 +02:00
|
|
|
|
import Articles
|
2017-05-27 19:43:27 +02:00
|
|
|
|
import RSTree
|
2017-05-27 22:37:50 +02:00
|
|
|
|
import RSWeb
|
2017-09-18 02:12:42 +02:00
|
|
|
|
import Account
|
2017-11-14 03:33:23 +01:00
|
|
|
|
import RSCore
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2017-11-15 06:31:17 +01:00
|
|
|
|
var appDelegate: AppDelegate!
|
2017-05-22 22:00:45 +02:00
|
|
|
|
|
|
|
|
|
@NSApplicationMain
|
2017-11-19 22:57:42 +01:00
|
|
|
|
class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UnreadCountProvider {
|
2017-05-22 22:00:45 +02:00
|
|
|
|
|
2017-11-25 06:39:59 +01:00
|
|
|
|
var faviconDownloader: FaviconDownloader!
|
2017-11-26 05:12:53 +01:00
|
|
|
|
var imageDownloader: ImageDownloader!
|
2017-11-26 22:16:32 +01:00
|
|
|
|
var authorAvatarDownloader: AuthorAvatarDownloader!
|
2017-11-27 04:57:45 +01:00
|
|
|
|
var feedIconDownloader: FeedIconDownloader!
|
2017-11-25 06:39:59 +01:00
|
|
|
|
var appName: String!
|
2019-05-16 00:21:58 +02:00
|
|
|
|
|
|
|
|
|
var refreshTimer: AccountRefreshTimer?
|
|
|
|
|
var syncTimer: ArticleStatusSyncTimer?
|
|
|
|
|
|
2019-01-28 03:00:09 +01:00
|
|
|
|
var shuttingDown = false {
|
|
|
|
|
didSet {
|
|
|
|
|
if shuttingDown {
|
2019-04-23 18:20:44 +02:00
|
|
|
|
refreshTimer?.shuttingDown = shuttingDown
|
|
|
|
|
refreshTimer?.invalidate()
|
2019-05-16 00:21:58 +02:00
|
|
|
|
syncTimer?.shuttingDown = shuttingDown
|
|
|
|
|
syncTimer?.invalidate()
|
2019-01-28 03:00:09 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 20:32:59 +01:00
|
|
|
|
@IBOutlet var debugMenuItem: NSMenuItem!
|
2018-01-28 00:11:02 +01:00
|
|
|
|
@IBOutlet var sortByOldestArticleOnTopMenuItem: NSMenuItem!
|
|
|
|
|
@IBOutlet var sortByNewestArticleOnTopMenuItem: NSMenuItem!
|
2018-12-09 21:12:55 +01:00
|
|
|
|
@IBOutlet var checkForUpdatesMenuItem: NSMenuItem!
|
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
var unreadCount = 0 {
|
|
|
|
|
didSet {
|
2017-10-06 03:12:58 +02:00
|
|
|
|
if unreadCount != oldValue {
|
2018-12-28 06:19:19 +01:00
|
|
|
|
CoalescingQueue.standard.add(self, #selector(updateDockBadge))
|
2017-11-19 22:57:42 +01:00
|
|
|
|
postUnreadCountDidChangeNotification()
|
2017-10-06 03:12:58 +02:00
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-25 06:39:59 +01:00
|
|
|
|
private var preferencesWindowController: NSWindowController?
|
2018-01-27 21:39:07 +01:00
|
|
|
|
private var mainWindowController: MainWindowController?
|
2017-11-25 06:39:59 +01:00
|
|
|
|
private var addFeedController: AddFeedController?
|
|
|
|
|
private var addFolderWindowController: AddFolderWindowController?
|
2019-05-01 23:04:56 +02:00
|
|
|
|
private var importOPMLController: ImportOPMLWindowController?
|
|
|
|
|
private var exportOPMLController: ExportOPMLWindowController?
|
2017-11-25 06:39:59 +01:00
|
|
|
|
private var keyboardShortcutsWindowController: WebViewWindowController?
|
|
|
|
|
private var inspectorWindowController: InspectorWindowController?
|
2018-12-29 21:31:39 +01:00
|
|
|
|
private var crashReportWindowController: CrashReportWindowController? // For testing only
|
2017-11-25 06:39:59 +01:00
|
|
|
|
private let log = Log()
|
2018-09-07 05:16:12 +02:00
|
|
|
|
private let appNewsURLString = "https://nnw.ranchero.com/feed.json"
|
2019-09-06 15:13:09 +02:00
|
|
|
|
private let appMovementMonitor = RSAppMovementMonitor()
|
2017-11-25 06:39:59 +01:00
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
override init() {
|
|
|
|
|
NSWindow.allowsAutomaticWindowTabbing = false
|
|
|
|
|
super.init()
|
2017-10-19 03:37:45 +02:00
|
|
|
|
|
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
2019-02-18 03:46:28 +01:00
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(inspectableObjectsDidChange(_:)), name: .InspectableObjectsDidChange, object: nil)
|
2018-01-21 21:46:22 +01:00
|
|
|
|
|
2017-11-15 06:31:17 +01:00
|
|
|
|
appDelegate = self
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 22:13:40 +01:00
|
|
|
|
// MARK: - API
|
|
|
|
|
|
2017-11-15 06:31:17 +01:00
|
|
|
|
func logMessage(_ message: String, type: LogItem.ItemType) {
|
|
|
|
|
|
2017-11-26 01:15:17 +01:00
|
|
|
|
#if DEBUG
|
2018-09-11 07:06:50 +02:00
|
|
|
|
if type == .debug {
|
2017-11-26 01:15:17 +01:00
|
|
|
|
print("logMessage: \(message) - \(type)")
|
2018-09-11 07:06:50 +02:00
|
|
|
|
}
|
2017-11-26 01:15:17 +01:00
|
|
|
|
#endif
|
|
|
|
|
|
2017-11-15 06:31:17 +01:00
|
|
|
|
let logItem = LogItem(type: type, message: message)
|
|
|
|
|
log.add(logItem)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func logDebugMessage(_ message: String) {
|
|
|
|
|
|
|
|
|
|
logMessage(message, type: .debug)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 22:13:40 +01:00
|
|
|
|
func showAddFolderSheetOnWindow(_ window: NSWindow) {
|
|
|
|
|
|
|
|
|
|
addFolderWindowController = AddFolderWindowController()
|
|
|
|
|
addFolderWindowController!.runSheetOnWindow(window)
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-02 00:33:08 +02:00
|
|
|
|
func showAddFeedSheetOnWindow(_ window: NSWindow, urlString: String?, name: String?, account: Account?, folder: Folder?) {
|
2018-02-04 06:30:30 +01:00
|
|
|
|
|
|
|
|
|
addFeedController = AddFeedController(hostWindow: window)
|
2019-05-02 00:33:08 +02:00
|
|
|
|
addFeedController?.showAddFeedSheet(urlString, name, account, folder)
|
2018-02-04 06:30:30 +01:00
|
|
|
|
}
|
2018-09-13 17:04:20 +02:00
|
|
|
|
|
2017-09-19 07:00:35 +02:00
|
|
|
|
// MARK: - NSApplicationDelegate
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2018-11-27 04:22:05 +01:00
|
|
|
|
func applicationWillFinishLaunching(_ notification: Notification) {
|
|
|
|
|
installAppleEventHandlers()
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
func applicationDidFinishLaunching(_ note: Notification) {
|
|
|
|
|
|
2018-12-09 21:12:55 +01:00
|
|
|
|
#if MAC_APP_STORE
|
|
|
|
|
checkForUpdatesMenuItem.isHidden = true
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-07-15 21:05:00 +02:00
|
|
|
|
appName = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String)
|
2017-11-25 06:39:59 +01:00
|
|
|
|
|
2018-12-28 06:34:21 +01:00
|
|
|
|
AppDefaults.registerDefaults()
|
|
|
|
|
let isFirstRun = AppDefaults.isFirstRun
|
2017-11-26 01:10:19 +01:00
|
|
|
|
if isFirstRun {
|
|
|
|
|
logDebugMessage("Is first run.")
|
|
|
|
|
}
|
2019-05-01 12:53:18 +02:00
|
|
|
|
let localAccount = AccountManager.shared.defaultAccount
|
2017-09-27 06:43:40 +02:00
|
|
|
|
DefaultFeedsImporter.importIfNeeded(isFirstRun, account: localAccount)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2017-11-25 20:14:06 +01:00
|
|
|
|
let tempDirectory = NSTemporaryDirectory()
|
2018-12-12 06:49:04 +01:00
|
|
|
|
let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String)
|
|
|
|
|
let cacheFolder = (tempDirectory as NSString).appendingPathComponent(bundleIdentifier)
|
2017-11-26 05:12:53 +01:00
|
|
|
|
|
2017-11-25 22:47:26 +01:00
|
|
|
|
let faviconsFolder = (cacheFolder as NSString).appendingPathComponent("Favicons")
|
2017-11-25 20:14:06 +01:00
|
|
|
|
let faviconsFolderURL = URL(fileURLWithPath: faviconsFolder)
|
|
|
|
|
try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil)
|
2017-11-25 06:39:59 +01:00
|
|
|
|
faviconDownloader = FaviconDownloader(folder: faviconsFolder)
|
|
|
|
|
|
2017-11-26 05:12:53 +01:00
|
|
|
|
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)
|
|
|
|
|
|
2017-11-26 22:16:32 +01:00
|
|
|
|
authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader)
|
2019-08-27 20:29:34 +02:00
|
|
|
|
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
|
2017-12-16 20:16:32 +01:00
|
|
|
|
|
2018-01-28 00:24:33 +01:00
|
|
|
|
updateSortMenuItems()
|
2018-02-11 10:20:30 +01:00
|
|
|
|
createAndShowMainWindow()
|
2019-08-24 03:30:28 +02:00
|
|
|
|
if isFirstRun {
|
|
|
|
|
mainWindowController?.window?.center()
|
|
|
|
|
}
|
2017-10-19 03:37:45 +02:00
|
|
|
|
|
2017-11-26 22:16:32 +01:00
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
|
2018-01-28 00:24:33 +01:00
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
|
2017-11-26 22:16:32 +01:00
|
|
|
|
|
2017-10-19 03:37:45 +02:00
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.unreadCount = AccountManager.shared.unreadCount
|
|
|
|
|
}
|
2017-12-25 19:23:12 +01:00
|
|
|
|
|
2018-01-21 22:27:17 +01:00
|
|
|
|
if InspectorWindowController.shouldOpenAtStartup {
|
2018-01-21 22:30:26 +01:00
|
|
|
|
self.toggleInspectorWindow(self)
|
2018-01-21 22:27:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-16 00:21:58 +02:00
|
|
|
|
refreshTimer = AccountRefreshTimer()
|
|
|
|
|
syncTimer = ArticleStatusSyncTimer()
|
2019-04-24 21:46:01 +02:00
|
|
|
|
|
2017-12-25 19:23:12 +01:00
|
|
|
|
#if RELEASE
|
2018-01-27 20:32:59 +01:00
|
|
|
|
debugMenuItem.menu?.removeItem(debugMenuItem)
|
2017-12-25 19:23:12 +01:00
|
|
|
|
DispatchQueue.main.async {
|
2019-04-24 21:46:01 +02:00
|
|
|
|
self.refreshTimer!.timedRefresh(nil)
|
2019-05-16 00:21:58 +02:00
|
|
|
|
self.syncTimer!.timedRefresh(nil)
|
2017-12-25 19:23:12 +01:00
|
|
|
|
}
|
|
|
|
|
#endif
|
2018-12-21 07:21:42 +01:00
|
|
|
|
|
2019-01-28 03:00:09 +01:00
|
|
|
|
#if DEBUG
|
2019-04-23 18:20:44 +02:00
|
|
|
|
refreshTimer!.update()
|
2019-05-16 00:21:58 +02:00
|
|
|
|
syncTimer!.update()
|
2019-01-28 03:00:09 +01:00
|
|
|
|
#endif
|
|
|
|
|
|
2019-01-12 08:18:48 +01:00
|
|
|
|
#if !MAC_APP_STORE
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
CrashReporter.check(appName: "NetNewsWire")
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
2019-01-28 05:25:09 +01: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 19:43:27 +02:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 03:00:09 +01:00
|
|
|
|
func applicationDidBecomeActive(_ notification: Notification) {
|
|
|
|
|
// It’s possible there’s a refresh timer set to go off in the past.
|
|
|
|
|
// In that case, refresh now and update the timer.
|
2019-04-23 18:20:44 +02:00
|
|
|
|
refreshTimer?.fireOldTimer()
|
2019-05-16 00:21:58 +02:00
|
|
|
|
syncTimer?.fireOldTimer()
|
2019-01-28 03:00:09 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
func applicationDidResignActive(_ notification: Notification) {
|
|
|
|
|
|
2018-12-02 19:51:32 +01:00
|
|
|
|
TimelineStringFormatter.emptyCaches()
|
2018-01-21 22:27:17 +01:00
|
|
|
|
|
|
|
|
|
saveState()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func applicationWillTerminate(_ notification: Notification) {
|
2019-01-28 03:00:09 +01:00
|
|
|
|
shuttingDown = true
|
2018-01-21 22:27:17 +01:00
|
|
|
|
saveState()
|
2019-05-22 00:20:49 +02:00
|
|
|
|
|
|
|
|
|
let group = DispatchGroup()
|
|
|
|
|
|
|
|
|
|
group.enter()
|
|
|
|
|
AccountManager.shared.syncArticleStatusAll() {
|
|
|
|
|
group.leave()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let timeout = DispatchTime.now() + .seconds(1)
|
|
|
|
|
_ = group.wait(timeout: timeout)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Notifications
|
|
|
|
|
|
2017-10-19 03:37:45 +02:00
|
|
|
|
@objc func unreadCountDidChange(_ note: Notification) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2017-10-19 03:37:45 +02:00
|
|
|
|
if note.object is AccountManager {
|
|
|
|
|
unreadCount = AccountManager.shared.unreadCount
|
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-26 22:16:32 +01:00
|
|
|
|
@objc func feedSettingDidChange(_ note: Notification) {
|
|
|
|
|
|
2019-03-17 21:54:30 +01:00
|
|
|
|
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
|
2017-11-26 22:16:32 +01:00
|
|
|
|
return
|
|
|
|
|
}
|
2019-03-17 21:54:30 +01:00
|
|
|
|
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
|
|
|
|
|
let _ = faviconDownloader.favicon(for: feed)
|
|
|
|
|
}
|
2017-11-26 22:16:32 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-18 03:46:28 +01:00
|
|
|
|
@objc func inspectableObjectsDidChange(_ note: Notification) {
|
2018-01-21 21:46:22 +01:00
|
|
|
|
|
|
|
|
|
guard let inspectorWindowController = inspectorWindowController, inspectorWindowController.isOpen else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
inspectorWindowController.objects = objectsForInspector()
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-28 00:24:33 +01:00
|
|
|
|
@objc func userDefaultsDidChange(_ note: Notification) {
|
|
|
|
|
updateSortMenuItems()
|
2019-04-23 18:20:44 +02:00
|
|
|
|
refreshTimer?.update()
|
2019-09-04 07:17:31 +02:00
|
|
|
|
updateDockBadge()
|
2018-01-28 00:24:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
// MARK: Main Window
|
|
|
|
|
|
|
|
|
|
func windowControllerWithName(_ storyboardName: String) -> NSWindowController {
|
|
|
|
|
|
2018-12-09 21:32:33 +01:00
|
|
|
|
let storyboard = NSStoryboard(name: NSStoryboard.Name(storyboardName), bundle: nil)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
return storyboard.instantiateInitialController()! as! NSWindowController
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createAndShowMainWindow() {
|
|
|
|
|
|
|
|
|
|
if mainWindowController == nil {
|
2017-11-14 22:18:25 +01:00
|
|
|
|
mainWindowController = createReaderWindow()
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mainWindowController!.showWindow(self)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: NSUserInterfaceValidations
|
|
|
|
|
|
|
|
|
|
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
2019-01-28 03:00:09 +01:00
|
|
|
|
if shuttingDown {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2018-02-03 19:56:12 +01:00
|
|
|
|
let isDisplayingSheet = mainWindowController?.isDisplayingSheet ?? false
|
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
if item.action == #selector(refreshAll(_:)) {
|
2019-05-19 21:23:54 +02:00
|
|
|
|
return !AccountManager.shared.refreshInProgress && !AccountManager.shared.activeAccounts.isEmpty
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
if item.action == #selector(addAppNews(_:)) {
|
2019-05-19 21:23:54 +02:00
|
|
|
|
return !isDisplayingSheet && !AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) && !AccountManager.shared.activeAccounts.isEmpty
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
2018-01-28 00:13:45 +01:00
|
|
|
|
if item.action == #selector(sortByNewestArticleOnTop(_:)) || item.action == #selector(sortByOldestArticleOnTop(_:)) {
|
|
|
|
|
return mainWindowController?.isOpen ?? false
|
|
|
|
|
}
|
2018-02-03 19:56:12 +01:00
|
|
|
|
if item.action == #selector(showAddFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) {
|
2019-05-19 17:21:42 +02:00
|
|
|
|
return !isDisplayingSheet && !AccountManager.shared.activeAccounts.isEmpty
|
2018-02-03 19:56:12 +01:00
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Add Feed
|
|
|
|
|
|
2019-05-02 00:33:08 +02:00
|
|
|
|
func addFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
|
2017-05-22 22:00:45 +02:00
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
createAndShowMainWindow()
|
2018-02-03 19:56:12 +01:00
|
|
|
|
if mainWindowController!.isDisplayingSheet {
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2019-05-02 00:33:08 +02:00
|
|
|
|
showAddFeedSheetOnWindow(mainWindowController!.window!, urlString: urlString, name: name, account: account, folder: folder)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-28 06:19:19 +01:00
|
|
|
|
// MARK: - Dock Badge
|
|
|
|
|
|
|
|
|
|
@objc func updateDockBadge() {
|
2019-03-08 20:35:37 +01:00
|
|
|
|
let label = unreadCount > 0 && !AppDefaults.hideDockUnreadCount ? "\(unreadCount)" : ""
|
2018-12-28 06:19:19 +01:00
|
|
|
|
NSApplication.shared.dockTile.badgeLabel = label
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-23 21:17:14 +02:00
|
|
|
|
// MARK: - Actions
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func showPreferences(_ sender: Any?) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
if preferencesWindowController == nil {
|
|
|
|
|
preferencesWindowController = windowControllerWithName("Preferences")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
preferencesWindowController!.showWindow(self)
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func showMainWindow(_ sender: Any?) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
createAndShowMainWindow()
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func refreshAll(_ sender: Any?) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2019-05-26 18:54:32 +02:00
|
|
|
|
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func showAddFeedWindow(_ sender: Any?) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
addFeed(nil)
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func showAddFolderWindow(_ sender: Any?) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
|
|
createAndShowMainWindow()
|
2017-11-15 22:13:40 +01:00
|
|
|
|
showAddFolderSheetOnWindow(mainWindowController!.window!)
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-14 03:33:23 +01:00
|
|
|
|
@IBAction func showKeyboardShortcutsWindow(_ sender: Any?) {
|
|
|
|
|
|
|
|
|
|
if keyboardShortcutsWindowController == nil {
|
|
|
|
|
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-21 02:23:46 +01:00
|
|
|
|
|
2017-12-22 20:13:20 +01:00
|
|
|
|
if let window = keyboardShortcutsWindowController?.window {
|
|
|
|
|
let point = NSPoint(x: 128, y: 64)
|
2019-03-04 21:39:30 +01:00
|
|
|
|
let size = NSSize(width: 620, height: 1100)
|
2017-12-22 20:13:20 +01:00
|
|
|
|
let minSize = NSSize(width: 400, height: 400)
|
|
|
|
|
window.setPointAndSizeAdjustingForScreen(point: point, size: size, minimumSize: minSize)
|
2017-12-21 02:23:46 +01:00
|
|
|
|
}
|
2017-11-14 03:33:23 +01:00
|
|
|
|
}
|
2017-12-21 02:23:46 +01:00
|
|
|
|
|
2017-11-14 03:33:23 +01:00
|
|
|
|
keyboardShortcutsWindowController!.showWindow(self)
|
|
|
|
|
}
|
2017-11-16 07:33:35 +01:00
|
|
|
|
|
|
|
|
|
@IBAction func toggleInspectorWindow(_ sender: Any?) {
|
|
|
|
|
|
|
|
|
|
if inspectorWindowController == nil {
|
2018-01-21 04:06:07 +01:00
|
|
|
|
inspectorWindowController = (windowControllerWithName("Inspector") as! InspectorWindowController)
|
2017-11-16 07:33:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if inspectorWindowController!.isOpen {
|
|
|
|
|
inspectorWindowController!.window!.performClose(self)
|
|
|
|
|
}
|
|
|
|
|
else {
|
2018-01-21 21:46:22 +01:00
|
|
|
|
inspectorWindowController!.objects = objectsForInspector()
|
2017-11-16 07:33:35 +01:00
|
|
|
|
inspectorWindowController!.showWindow(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func importOPMLFromFile(_ sender: Any?) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2019-05-01 23:04:56 +02:00
|
|
|
|
createAndShowMainWindow()
|
|
|
|
|
if mainWindowController!.isDisplayingSheet {
|
|
|
|
|
return
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
2019-05-01 23:04:56 +02:00
|
|
|
|
|
|
|
|
|
importOPMLController = ImportOPMLWindowController()
|
|
|
|
|
importOPMLController?.runSheetOnWindow(mainWindowController!.window!)
|
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
2017-09-23 21:17:14 +02:00
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func exportOPML(_ sender: Any?) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2019-05-01 23:04:56 +02:00
|
|
|
|
createAndShowMainWindow()
|
|
|
|
|
if mainWindowController!.isDisplayingSheet {
|
|
|
|
|
return
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
2019-05-01 23:04:56 +02:00
|
|
|
|
|
|
|
|
|
exportOPMLController = ExportOPMLWindowController()
|
|
|
|
|
exportOPMLController?.runSheetOnWindow(mainWindowController!.window!)
|
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
2017-09-23 21:17:14 +02:00
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func addAppNews(_ sender: Any?) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
2017-09-23 21:17:14 +02:00
|
|
|
|
if AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
2018-09-22 21:46:24 +02:00
|
|
|
|
addFeed(appNewsURLString, name: "NetNewsWire News")
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
2017-05-27 22:37:50 +02:00
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func openWebsite(_ sender: Any?) {
|
2017-05-27 22:37:50 +02:00
|
|
|
|
|
2018-08-29 07:18:24 +02:00
|
|
|
|
Browser.open("https://ranchero.com/netnewswire/", inBackground: false)
|
2017-05-27 22:37:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-15 00:38:00 +02:00
|
|
|
|
@IBAction func openHowToSupport(_ sender: Any?) {
|
|
|
|
|
|
|
|
|
|
Browser.open("https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/HowToSupportNetNewsWire.markdown", inBackground: false)
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func openRepository(_ sender: Any?) {
|
2017-05-27 22:37:50 +02:00
|
|
|
|
|
2018-08-29 07:18:24 +02:00
|
|
|
|
Browser.open("https://github.com/brentsimmons/NetNewsWire", inBackground: false)
|
2017-05-27 22:37:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func openBugTracker(_ sender: Any?) {
|
2017-05-27 22:37:50 +02:00
|
|
|
|
|
2018-08-29 07:18:24 +02:00
|
|
|
|
Browser.open("https://github.com/brentsimmons/NetNewsWire/issues", inBackground: false)
|
2017-05-27 22:37:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-25 19:21:25 +02:00
|
|
|
|
@IBAction func openSlackGroup(_ sender: Any?) {
|
|
|
|
|
Browser.open("https://join.slack.com/t/netnewswire/shared_invite/enQtNjM4MDA1MjQzMDkzLTNlNjBhOWVhYzdhYjA4ZWFhMzQ1MTUxYjU0NTE5ZGY0YzYwZWJhNjYwNTNmNTg2NjIwYWY4YzhlYzk5NmU3ZTc", inBackground: false)
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-21 06:23:48 +01:00
|
|
|
|
@IBAction func openTechnotes(_ sender: Any?) {
|
|
|
|
|
|
2018-08-29 07:18:24 +02:00
|
|
|
|
Browser.open("https://github.com/brentsimmons/NetNewsWire/tree/master/Technotes", inBackground: false)
|
2017-12-21 06:23:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-12 03:58:50 +01:00
|
|
|
|
@IBAction func showHelp(_ sender: Any?) {
|
2017-05-27 22:37:50 +02:00
|
|
|
|
|
2019-08-10 23:26:51 +02:00
|
|
|
|
Browser.open("https://ranchero.com/netnewswire/help/mac/5.0/en/", inBackground: false)
|
2017-05-27 22:37:50 +02:00
|
|
|
|
}
|
2017-11-17 03:23:07 +01:00
|
|
|
|
|
2018-02-12 22:10:13 +01:00
|
|
|
|
@IBAction func donateToAppCampForGirls(_ sender: Any?) {
|
|
|
|
|
Browser.open("https://appcamp4girls.com/contribute/", inBackground: false)
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-29 22:59:14 +01:00
|
|
|
|
@IBAction func showPrivacyPolicy(_ sender: Any?) {
|
|
|
|
|
Browser.open("https://ranchero.com/netnewswire/privacypolicy", inBackground: false)
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-20 07:39:13 +01:00
|
|
|
|
@IBAction func debugDropConditionalGetInfo(_ sender: Any?) {
|
|
|
|
|
#if DEBUG
|
2019-05-02 13:01:30 +02:00
|
|
|
|
AccountManager.shared.activeAccounts.forEach{ $0.debugDropConditionalGetInfo() }
|
2017-11-20 07:39:13 +01:00
|
|
|
|
#endif
|
|
|
|
|
}
|
2018-01-27 21:39:07 +01:00
|
|
|
|
|
2018-12-29 21:31:39 +01:00
|
|
|
|
@IBAction func debugTestCrashReporterWindow(_ sender: Any?) {
|
|
|
|
|
#if DEBUG
|
|
|
|
|
crashReportWindowController = CrashReportWindowController(crashLogText: "This is a test crash log.")
|
|
|
|
|
crashReportWindowController!.testing = true
|
|
|
|
|
crashReportWindowController!.showWindow(self)
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-12 07:06:24 +01:00
|
|
|
|
@IBAction func debugTestCrashReportSending(_ sender: Any?) {
|
|
|
|
|
#if DEBUG
|
|
|
|
|
CrashReporter.sendCrashLogText("This is a test. Hi, Brent.")
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 21:39:07 +01:00
|
|
|
|
@IBAction func gotoToday(_ sender: Any?) {
|
|
|
|
|
|
|
|
|
|
createAndShowMainWindow()
|
|
|
|
|
mainWindowController!.gotoToday(sender)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func gotoAllUnread(_ sender: Any?) {
|
|
|
|
|
|
|
|
|
|
createAndShowMainWindow()
|
|
|
|
|
mainWindowController!.gotoAllUnread(sender)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func gotoStarred(_ sender: Any?) {
|
|
|
|
|
|
|
|
|
|
createAndShowMainWindow()
|
|
|
|
|
mainWindowController!.gotoStarred(sender)
|
|
|
|
|
}
|
2018-01-28 00:11:02 +01:00
|
|
|
|
|
|
|
|
|
@IBAction func sortByOldestArticleOnTop(_ sender: Any?) {
|
|
|
|
|
|
2018-12-28 06:34:21 +01:00
|
|
|
|
AppDefaults.timelineSortDirection = .orderedAscending
|
2018-01-28 00:11:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@IBAction func sortByNewestArticleOnTop(_ sender: Any?) {
|
|
|
|
|
|
2018-12-28 06:34:21 +01:00
|
|
|
|
AppDefaults.timelineSortDirection = .orderedDescending
|
2018-01-28 00:11:02 +01:00
|
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-19 07:29:43 +01:00
|
|
|
|
// MARK: - Debug Menu
|
|
|
|
|
extension AppDelegate {
|
|
|
|
|
|
|
|
|
|
@IBAction func debugSearch(_ sender: Any?) {
|
2019-05-01 12:53:18 +02:00
|
|
|
|
AccountManager.shared.defaultAccount.debugRunSearch()
|
2019-02-19 07:29:43 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-14 22:18:25 +01:00
|
|
|
|
private extension AppDelegate {
|
2017-05-22 22:00:45 +02:00
|
|
|
|
|
2018-01-27 21:39:07 +01:00
|
|
|
|
func createReaderWindow() -> MainWindowController {
|
2017-11-14 22:18:25 +01:00
|
|
|
|
|
2018-01-27 21:39:07 +01:00
|
|
|
|
return windowControllerWithName("MainWindow") as! MainWindowController
|
2017-11-14 22:18:25 +01:00
|
|
|
|
}
|
2018-01-21 21:46:22 +01: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 22:27:17 +01:00
|
|
|
|
|
|
|
|
|
func saveState() {
|
|
|
|
|
|
2018-02-12 22:31:43 +01:00
|
|
|
|
inspectorWindowController?.saveState()
|
2019-02-26 07:25:29 +01:00
|
|
|
|
mainWindowController?.saveState()
|
2018-01-21 22:27:17 +01:00
|
|
|
|
}
|
2018-01-28 00:24:33 +01:00
|
|
|
|
|
|
|
|
|
func updateSortMenuItems() {
|
|
|
|
|
|
2018-12-28 06:34:21 +01:00
|
|
|
|
let sortByNewestOnTop = AppDefaults.timelineSortDirection == .orderedDescending
|
2018-01-28 00:24:33 +01:00
|
|
|
|
sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off
|
|
|
|
|
sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on
|
|
|
|
|
}
|
2017-11-14 22:18:25 +01:00
|
|
|
|
}
|
2018-02-08 09:11:52 +01: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 ?? []
|
|
|
|
|
}
|
|
|
|
|
}
|