Merge.
This commit is contained in:
commit
e0514cb665
|
@ -53,7 +53,6 @@ struct AppDefaults {
|
||||||
static let timelineSortDirection = "timelineSortDirection"
|
static let timelineSortDirection = "timelineSortDirection"
|
||||||
static let detailFontSize = "detailFontSize"
|
static let detailFontSize = "detailFontSize"
|
||||||
static let openInBrowserInBackground = "openInBrowserInBackground"
|
static let openInBrowserInBackground = "openInBrowserInBackground"
|
||||||
static let mainWindowWidths = "mainWindowWidths"
|
|
||||||
static let refreshInterval = "refreshInterval"
|
static let refreshInterval = "refreshInterval"
|
||||||
|
|
||||||
// Hidden prefs
|
// Hidden prefs
|
||||||
|
@ -120,15 +119,6 @@ struct AppDefaults {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mainWindowWidths: [Int]? {
|
|
||||||
get {
|
|
||||||
return UserDefaults.standard.object(forKey: Key.mainWindowWidths) as? [Int]
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
UserDefaults.standard.set(newValue, forKey: Key.mainWindowWidths)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static var refreshInterval: RefreshInterval {
|
static var refreshInterval: RefreshInterval {
|
||||||
get {
|
get {
|
||||||
let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval)
|
let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval)
|
||||||
|
@ -143,6 +133,18 @@ struct AppDefaults {
|
||||||
let defaults: [String : Any] = [Key.sidebarFontSize: FontSize.medium.rawValue, Key.timelineFontSize: FontSize.medium.rawValue, Key.detailFontSize: FontSize.medium.rawValue, Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, "NSScrollViewShouldScrollUnderTitlebar": false, Key.refreshInterval: RefreshInterval.everyHour.rawValue]
|
let defaults: [String : Any] = [Key.sidebarFontSize: FontSize.medium.rawValue, Key.timelineFontSize: FontSize.medium.rawValue, Key.detailFontSize: FontSize.medium.rawValue, Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, "NSScrollViewShouldScrollUnderTitlebar": false, Key.refreshInterval: RefreshInterval.everyHour.rawValue]
|
||||||
|
|
||||||
UserDefaults.standard.register(defaults: defaults)
|
UserDefaults.standard.register(defaults: defaults)
|
||||||
|
|
||||||
|
// It seems that registering a default for NSQuitAlwaysKeepsWindows to true
|
||||||
|
// is not good enough to get the system to respect it, so we have to literally
|
||||||
|
// set it as the default to get it to take effect. This overrides a system-wide
|
||||||
|
// setting in the System Preferences, which is ostensibly meant to "close windows"
|
||||||
|
// in an app, but has the side-effect of also not preserving or restoring any state
|
||||||
|
// for the window. Since we've switched to using the standard state preservation and
|
||||||
|
// restoration mechanisms, and because it seems highly unlikely any user would object
|
||||||
|
// to NetNewsWire preserving this state, we'll force the preference on. If this becomes
|
||||||
|
// an issue, this could be changed to proactively look for whether the default has been
|
||||||
|
// set _by the user_ to false, and respect that default if it is so-set.
|
||||||
|
UserDefaults.standard.set(true, forKey: "NSQuitAlwaysKeepsWindows")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func actualFontSize(for fontSize: FontSize) -> CGFloat {
|
static func actualFontSize(for fontSize: FontSize) -> CGFloat {
|
||||||
|
|
|
@ -576,7 +576,6 @@ private extension AppDelegate {
|
||||||
func saveState() {
|
func saveState() {
|
||||||
|
|
||||||
inspectorWindowController?.saveState()
|
inspectorWindowController?.saveState()
|
||||||
mainWindowController?.saveState()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSortMenuItems() {
|
func updateSortMenuItems() {
|
||||||
|
|
|
@ -51,6 +51,10 @@ body.light .articleDateline, body.light .articleDateLine.a:link, body.light .art
|
||||||
color: rgba(0, 0, 0, 0.3);
|
color: rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.light code, body.light pre {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
.light > .systemMessage {
|
.light > .systemMessage {
|
||||||
color: #cbcbcb;
|
color: #cbcbcb;
|
||||||
}
|
}
|
||||||
|
@ -81,6 +85,10 @@ body.dark .articleDateline, body.dark .articleDateLine.a:link, body.dark .articl
|
||||||
color: #d2d2d2;
|
color: #d2d2d2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.dark code, body.dark pre {
|
||||||
|
color: #b2b2b2;
|
||||||
|
}
|
||||||
|
|
||||||
.dark > .systemMessage {
|
.dark > .systemMessage {
|
||||||
color: #5f5f5f;
|
color: #5f5f5f;
|
||||||
}
|
}
|
||||||
|
@ -120,7 +128,6 @@ h1 {
|
||||||
code, pre {
|
code, pre {
|
||||||
font-family: "SF Mono", Menlo, "Courier New", Courier, monospace;
|
font-family: "SF Mono", Menlo, "Courier New", Courier, monospace;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
|
||||||
}
|
}
|
||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
|
@ -15,12 +15,11 @@ enum TimelineSourceMode {
|
||||||
case regular, search
|
case regular, search
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
class MainWindowController : NSWindowController, NSUserInterfaceValidations, NSWindowDelegate {
|
||||||
|
|
||||||
@IBOutlet var toolbarDelegate: MainWindowToolbarDelegate?
|
@IBOutlet var toolbarDelegate: MainWindowToolbarDelegate?
|
||||||
private var sharingServicePickerDelegate: NSSharingServicePickerDelegate?
|
private var sharingServicePickerDelegate: NSSharingServicePickerDelegate?
|
||||||
|
|
||||||
private let windowAutosaveName = NSWindow.FrameAutosaveName("MainWindow")
|
|
||||||
static var didPositionWindowOnFirstRun = false
|
static var didPositionWindowOnFirstRun = false
|
||||||
|
|
||||||
private var currentFeedOrFolder: AnyObject? {
|
private var currentFeedOrFolder: AnyObject? {
|
||||||
|
@ -52,7 +51,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||||
window?.titleVisibility = .hidden
|
window?.titleVisibility = .hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
window?.setFrameUsingName(windowAutosaveName, force: true)
|
|
||||||
if AppDefaults.isFirstRun && !MainWindowController.didPositionWindowOnFirstRun {
|
if AppDefaults.isFirstRun && !MainWindowController.didPositionWindowOnFirstRun {
|
||||||
|
|
||||||
if let window = window {
|
if let window = window {
|
||||||
|
@ -65,7 +63,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||||
}
|
}
|
||||||
|
|
||||||
detailSplitViewItem?.minimumThickness = CGFloat(MainWindowController.detailViewMinimumThickness)
|
detailSplitViewItem?.minimumThickness = CGFloat(MainWindowController.detailViewMinimumThickness)
|
||||||
restoreSplitViewState()
|
|
||||||
|
|
||||||
sidebarViewController = splitViewController?.splitViewItems[0].viewController as? SidebarViewController
|
sidebarViewController = splitViewController?.splitViewItems[0].viewController as? SidebarViewController
|
||||||
sidebarViewController!.delegate = self
|
sidebarViewController!.delegate = self
|
||||||
|
@ -75,8 +72,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||||
|
|
||||||
detailViewController = splitViewController?.splitViewItems[2].viewController as? DetailViewController
|
detailViewController = splitViewController?.splitViewItems[2].viewController as? DetailViewController
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillTerminate(_:)), name: NSApplication.willTerminateNotification, object: nil)
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidBegin, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidBegin, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidFinish, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidFinish, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
|
||||||
|
@ -91,12 +86,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
|
|
||||||
func saveState() {
|
|
||||||
|
|
||||||
saveSplitViewState()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func selectedObjectsInSidebar() -> [AnyObject]? {
|
func selectedObjectsInSidebar() -> [AnyObject]? {
|
||||||
|
|
||||||
return sidebarViewController?.selectedObjects
|
return sidebarViewController?.selectedObjects
|
||||||
|
@ -104,10 +93,18 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
|
|
||||||
@objc func applicationWillTerminate(_ note: Notification) {
|
func window(_ window: NSWindow, willEncodeRestorableState state: NSCoder) {
|
||||||
|
|
||||||
saveState()
|
saveSplitViewState(to: state)
|
||||||
window?.saveFrame(usingName: windowAutosaveName)
|
}
|
||||||
|
|
||||||
|
func window(_ window: NSWindow, didDecodeRestorableState state: NSCoder) {
|
||||||
|
|
||||||
|
restoreSplitViewState(from: state)
|
||||||
|
|
||||||
|
// Make sure the timeline view is first responder if possible, to start out viewing
|
||||||
|
// whatever preserved selection might have been restored
|
||||||
|
makeTimelineViewFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func refreshProgressDidChange(_ note: Notification) {
|
@objc func refreshProgressDidChange(_ note: Notification) {
|
||||||
|
@ -403,7 +400,9 @@ extension MainWindowController : ScriptingMainWindowController {
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private extension MainWindowController {
|
private extension MainWindowController {
|
||||||
|
|
||||||
|
static let mainWindowWidthsStateKey = "mainWindowWidths"
|
||||||
|
|
||||||
var splitViewController: NSSplitViewController? {
|
var splitViewController: NSSplitViewController? {
|
||||||
guard let viewController = contentViewController else {
|
guard let viewController = contentViewController else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -582,7 +581,7 @@ private extension MainWindowController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveSplitViewState() {
|
func saveSplitViewState(to coder: NSCoder) {
|
||||||
|
|
||||||
// TODO: Update this for multiple windows.
|
// TODO: Update this for multiple windows.
|
||||||
|
|
||||||
|
@ -591,16 +590,25 @@ private extension MainWindowController {
|
||||||
}
|
}
|
||||||
|
|
||||||
let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) }
|
let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) }
|
||||||
if AppDefaults.mainWindowWidths != widths {
|
coder.encode(widths, forKey: MainWindowController.mainWindowWidthsStateKey)
|
||||||
AppDefaults.mainWindowWidths = widths
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreSplitViewState() {
|
func arrayOfIntFromCoder(_ coder: NSCoder, withKey: String) -> [Int]? {
|
||||||
|
let decodedFloats: [Int]?
|
||||||
|
do {
|
||||||
|
decodedFloats = try coder.decodeTopLevelObject(forKey: MainWindowController.mainWindowWidthsStateKey) as? [Int]? ?? nil
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
decodedFloats = nil
|
||||||
|
}
|
||||||
|
return decodedFloats
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreSplitViewState(from coder: NSCoder) {
|
||||||
|
|
||||||
// TODO: Update this for multiple windows.
|
// TODO: Update this for multiple windows.
|
||||||
|
guard let splitView = splitViewController?.splitView, let widths = arrayOfIntFromCoder(coder, withKey: MainWindowController.mainWindowWidthsStateKey), widths.count == 3, let window = window else {
|
||||||
guard let splitView = splitViewController?.splitView, let widths = AppDefaults.mainWindowWidths, widths.count == 3, let window = window else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -617,5 +625,6 @@ private extension MainWindowController {
|
||||||
splitView.setPosition(CGFloat(sidebarWidth), ofDividerAt: 0)
|
splitView.setPosition(CGFloat(sidebarWidth), ofDividerAt: 0)
|
||||||
splitView.setPosition(CGFloat(sidebarWidth + dividerThickness + timelineWidth), ofDividerAt: 1)
|
splitView.setPosition(CGFloat(sidebarWidth + dividerThickness + timelineWidth), ofDividerAt: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,26 @@ protocol SidebarDelegate: class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: State Restoration
|
||||||
|
|
||||||
|
private static let stateRestorationSelectedRowIndexes = "selectedRowIndexes"
|
||||||
|
|
||||||
|
override func encodeRestorableState(with coder: NSCoder) {
|
||||||
|
|
||||||
|
super.encodeRestorableState(with: coder)
|
||||||
|
|
||||||
|
coder.encode(outlineView.selectedRowIndexes, forKey: SidebarViewController.stateRestorationSelectedRowIndexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func restoreState(with coder: NSCoder) {
|
||||||
|
|
||||||
|
super.restoreState(with: coder)
|
||||||
|
|
||||||
|
if let restoredRowIndexes = coder.decodeObject(of: [NSIndexSet.self], forKey: SidebarViewController.stateRestorationSelectedRowIndexes) as? IndexSet {
|
||||||
|
outlineView.selectRowIndexes(restoredRowIndexes, byExtendingSelection: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
|
|
||||||
@objc func unreadCountDidChange(_ note: Notification) {
|
@objc func unreadCountDidChange(_ note: Notification) {
|
||||||
|
@ -283,6 +303,7 @@ protocol SidebarDelegate: class {
|
||||||
|
|
||||||
func outlineViewSelectionDidChange(_ notification: Notification) {
|
func outlineViewSelectionDidChange(_ notification: Notification) {
|
||||||
selectionDidChange(selectedObjects.isEmpty ? nil : selectedObjects)
|
selectionDidChange(selectedObjects.isEmpty ? nil : selectedObjects)
|
||||||
|
self.invalidateRestorableState()
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - Node Manipulation
|
//MARK: - Node Manipulation
|
||||||
|
|
|
@ -154,6 +154,26 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner {
|
||||||
sharingServiceDelegate = SharingServiceDelegate(self.view.window)
|
sharingServiceDelegate = SharingServiceDelegate(self.view.window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: State Restoration
|
||||||
|
|
||||||
|
private static let stateRestorationSelectedArticles = "selectedArticles"
|
||||||
|
|
||||||
|
override func encodeRestorableState(with coder: NSCoder) {
|
||||||
|
|
||||||
|
super.encodeRestorableState(with: coder)
|
||||||
|
|
||||||
|
coder.encode(self.selectedArticleIDs(), forKey: TimelineViewController.stateRestorationSelectedArticles)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func restoreState(with coder: NSCoder) {
|
||||||
|
|
||||||
|
super.restoreState(with: coder)
|
||||||
|
|
||||||
|
if let restoredArticleIDs = (try? coder.decodeTopLevelObject(forKey: TimelineViewController.stateRestorationSelectedArticles)) as? [String] {
|
||||||
|
self.restoreSelection(restoredArticleIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Appearance Change
|
// MARK: Appearance Change
|
||||||
|
|
||||||
private func fontSizeDidChange() {
|
private func fontSizeDidChange() {
|
||||||
|
@ -627,6 +647,8 @@ extension TimelineViewController: NSTableViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
postTimelineSelectionDidChangeNotification(selectedArticles)
|
postTimelineSelectionDidChangeNotification(selectedArticles)
|
||||||
|
|
||||||
|
self.invalidateRestorableState()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func postTimelineSelectionDidChangeNotification(_ selectedArticles: ArticleArray?) {
|
private func postTimelineSelectionDidChangeNotification(_ selectedArticles: ArticleArray?) {
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<opml version="2.0">
|
||||||
|
<head><!-- <editor scale-mac="1.5">
|
||||||
|
<sidebar width="203"/>
|
||||||
|
<column name="text" width="907"/>
|
||||||
|
</editor> -->
|
||||||
|
<title>HelpBook</title>
|
||||||
|
<dateCreated>Sun, 17 Feb 2019 22:26:12 GMT</dateCreated>
|
||||||
|
<expansionState>24,27</expansionState>
|
||||||
|
<vertScrollState>10</vertScrollState>
|
||||||
|
<windowTop>207</windowTop>
|
||||||
|
<windowLeft>46</windowLeft>
|
||||||
|
<windowRight>983</windowRight>
|
||||||
|
<windowBottom>910</windowBottom>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<outline text="How to add feeds"/>
|
||||||
|
<outline text="Installing Safari extension"/>
|
||||||
|
<outline text="How to go through articles"/>
|
||||||
|
<outline text="What is a feed?"/>
|
||||||
|
<outline text="Keyboard shortcuts"/>
|
||||||
|
<outline text="Contributing - Slack group, GitHub"/>
|
||||||
|
<outline text="Getting NetNewsWire news"/>
|
||||||
|
<outline text="Privacy"/>
|
||||||
|
<outline text="About the On My Mac account"/>
|
||||||
|
<outline text="Adding a syncing account"/>
|
||||||
|
<outline text="Importing subscriptions from another app - OPML"/>
|
||||||
|
<outline text="Exporting subscriptions to use in a another app - OPML"/>
|
||||||
|
<outline text="Sharing with other apps, particularly blogging apps"/>
|
||||||
|
<outline text="Adding microblog feeds"/>
|
||||||
|
<outline text="Renaming feeds and folders"/>
|
||||||
|
<outline text="Getting and copying feed information"/>
|
||||||
|
<outline text="OPML file on disk"/>
|
||||||
|
<outline text="Other app data on disk"/>
|
||||||
|
<outline text="Searching"/>
|
||||||
|
<outline text="Customizing the toolbar"/>
|
||||||
|
<outline text="Hidden prefs"/>
|
||||||
|
<outline text="Updating NetNewsWire"/>
|
||||||
|
<outline text="Sorting the timeline"/>
|
||||||
|
<outline text="Smart feeds - Today, All Unread, Starred"/>
|
||||||
|
<outline text="Philosophy of saving articles">
|
||||||
|
<outline text="NetNewsWire is not a personal backup of the internet"/>
|
||||||
|
<outline text="Articles automatically marked as read"/>
|
||||||
|
</outline>
|
||||||
|
<outline text="Notes for feed publishers">
|
||||||
|
<outline text="Supported formats"/>
|
||||||
|
<outline text="Conditional GET"/>
|
||||||
|
<outline text="Where app gets favicons and images"/>
|
||||||
|
</outline>
|
||||||
|
</body>
|
||||||
|
</opml>
|
Loading…
Reference in New Issue