Merge remote-tracking branch 'brentsimmons/master'
This commit is contained in:
commit
cc43b4be7a
|
@ -6,6 +6,105 @@
|
|||
<description>Most recent Evergreen changes with links to updates.</description>
|
||||
<language>en</language>
|
||||
|
||||
<item>
|
||||
<title>Evergreen 1.0d40</title>
|
||||
<description><![CDATA[
|
||||
|
||||
<p>Improve font sizing in the timeline.</p>
|
||||
<p>Show no-selection or multiple-selection text in the detail view when appropriate.</p>
|
||||
<p>Give the source list an almost-white background (96%) instead of using the blueish blur view. Reason: favicons are often created with the expectation that they will be placed on a white background — they often don’t include transparency. They end up looking better on this almost-white background.</p>
|
||||
|
||||
]]></description>
|
||||
<pubDate>Sun, 18 Feb 2018 22:10:00 -0800</pubDate>
|
||||
<enclosure url="https://ranchero.com/downloads/Evergreen1.0d40.zip" sparkle:version="1040" sparkle:shortVersionString="1.0d40" length="7357549" type="application/zip" />
|
||||
<sparkle:minimumSystemVersion>10.13</sparkle:minimumSystemVersion>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>Evergreen 1.0d39</title>
|
||||
<description><![CDATA[
|
||||
|
||||
<h4>Timeline</h4>
|
||||
<p>Moved avatars and feed icons to the right.</p>
|
||||
<p>Tweaked some colors and margins.</p>
|
||||
<p>Removed the grid line.</p>
|
||||
<p>Made the text for no-title articles darker.</p>
|
||||
|
||||
]]></description>
|
||||
<pubDate>Sun, 18 Feb 2018 17:00:00 -0800</pubDate>
|
||||
<enclosure url="https://ranchero.com/downloads/Evergreen1.0d39.zip" sparkle:version="1034" sparkle:shortVersionString="1.0d39" length="7351715" type="application/zip" />
|
||||
<sparkle:minimumSystemVersion>10.13</sparkle:minimumSystemVersion>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>Evergreen 1.0d38</title>
|
||||
<description><![CDATA[
|
||||
|
||||
<h4>Starring</h4>
|
||||
<p>You can mark articles starred and unstarred — via the menu, contextual menu in the timeline, and toolbar button.</p>
|
||||
<p>The Starred smart feed shows all your starred articles. Tip: cmd-3 takes you to the Starred feed.</p>
|
||||
|
||||
<h4>Misc.</h4>
|
||||
<p>Added special cases to the JSON Feed parser for known feeds that put HTML entities in article titles. (Kottke, Pixel Envy.)</p>
|
||||
<p>Revised the JSON parser feed detector to allow for a version property that uses the incorrect scheme. (Makes Pixel Envy’s feed work.)</p>
|
||||
<p>Made Kottke’s feed a default for new users.</p>
|
||||
]]></description>
|
||||
<pubDate>Sun, 18 Feb 2018 12:30:00 -0800</pubDate>
|
||||
<enclosure url="https://ranchero.com/downloads/Evergreen1.0d38.zip" sparkle:version="1027" sparkle:shortVersionString="1.0d38" length="7349333" type="application/zip" />
|
||||
<sparkle:minimumSystemVersion>10.13</sparkle:minimumSystemVersion>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>Evergreen 1.0d37</title>
|
||||
<description><![CDATA[
|
||||
|
||||
<h4>Misc.</h4>
|
||||
<p>Make the Copy command work in the sidebar and timeline.</p>
|
||||
<p>Add Donate to App Camp for Girls item to the Help menu. The Help menu is now a place where <i>you</i> can help.</p>
|
||||
<p>When selecting multiple items in the sidebar, show articles in the timeline from the entire selection, not just from one thing.</p>
|
||||
<p>Update folder unread count when a feed it contains is deleted.</p>
|
||||
<p>Update copyright date in About box.</p>
|
||||
<p>Disallow blurring behind the title bar, since it’s buggy.</p>
|
||||
<p>Don’t clear undo stack when sidebar changes selection.</p>
|
||||
<p>Remember Feed Directory’s window frame between runs.</p>
|
||||
|
||||
<h4>Puntings</h4>
|
||||
<p>Font size is punted till after 1.0, and its preferences have been removed until then.</p>
|
||||
<p>Feed Directory is simplified — the feed preview feature has been punted till after 1.0.</p>
|
||||
|
||||
]]></description>
|
||||
<pubDate>Wed, 14 Feb 2018 13:25:00 -0800</pubDate>
|
||||
<enclosure url="https://ranchero.com/downloads/Evergreen1.0d37.zip" sparkle:version="981" sparkle:shortVersionString="1.0d37" length="7374447" type="application/zip" />
|
||||
<sparkle:minimumSystemVersion>10.13</sparkle:minimumSystemVersion>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>Evergreen 1.0d36</title>
|
||||
<description><![CDATA[
|
||||
|
||||
<h4>AppleScript!</h4>
|
||||
<p>Evergreen is scriptable — use Script Editor to see the app’s scripting dictionary.</p>
|
||||
|
||||
<h4>Sidebar</h4>
|
||||
<p>Fetch and display articles for smart feeds. (The Starred feed is empty for now, though, because you can’t actually star anything yet.)</p>
|
||||
<p>Display a contextual menu for feeds, folders, and smart feeds.</p>
|
||||
|
||||
<h4>Timeline</h4>
|
||||
<p>Display a contextual menu for articles.</p>
|
||||
|
||||
<h4>Detail</h4>
|
||||
<p>Remove contextual menu items from the web view that don’t make sense for Evergren — Reload, for example.</p>
|
||||
|
||||
<h4>Misc.</h4>
|
||||
<p>Rearrange the default toolbar to put Search in the middle.</p>
|
||||
<p>Update the name of the Show/Hide Sidebar menu command when needed.</p>
|
||||
|
||||
]]></description>
|
||||
<pubDate>Sun, 11 Feb 2018 14:35:00 -0800</pubDate>
|
||||
<enclosure url="https://ranchero.com/downloads/Evergreen1.0d36.zip" sparkle:version="959" sparkle:shortVersionString="1.0d36" length="8813422" type="application/zip" />
|
||||
<sparkle:minimumSystemVersion>10.13</sparkle:minimumSystemVersion>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>Evergreen 1.0d35</title>
|
||||
<description><![CDATA[
|
||||
|
|
|
@ -17,9 +17,7 @@ final class DeleteFromSidebarCommand: UndoableCommand {
|
|||
let undoManager: UndoManager
|
||||
let undoActionName: String
|
||||
var redoActionName: String {
|
||||
get {
|
||||
return undoActionName
|
||||
}
|
||||
return undoActionName
|
||||
}
|
||||
|
||||
private let itemSpecifiers: [SidebarItemSpecifier]
|
||||
|
@ -94,15 +92,13 @@ private struct SidebarItemSpecifier {
|
|||
private let path: ContainerPath
|
||||
|
||||
private var container: Container? {
|
||||
get {
|
||||
if let parentFolder = parentFolder {
|
||||
return parentFolder
|
||||
}
|
||||
if let account = account {
|
||||
return account
|
||||
}
|
||||
return nil
|
||||
if let parentFolder = parentFolder {
|
||||
return parentFolder
|
||||
}
|
||||
if let account = account {
|
||||
return account
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
init?(node: Node) {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// MarkCommandValidationStatus.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/17/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum MarkCommandValidationStatus {
|
||||
|
||||
case canMark, canUnmark, canDoNothing
|
||||
|
||||
static func statusFor(_ articles: ArticleArray, _ canMarkTest: ((ArticleArray) -> Bool)) -> MarkCommandValidationStatus {
|
||||
|
||||
if articles.isEmpty {
|
||||
return .canDoNothing
|
||||
}
|
||||
return canMarkTest(articles) ? .canMark : .canUnmark
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
//
|
||||
// MarkReadOrUnreadCommand.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 10/26/17.
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSCore
|
||||
import Data
|
||||
|
||||
final class MarkReadOrUnreadCommand: UndoableCommand {
|
||||
|
||||
static private let markReadActionName = NSLocalizedString("Mark Read", comment: "command")
|
||||
static private let markUnreadActionName = NSLocalizedString("Mark Unread", comment: "command")
|
||||
let undoActionName: String
|
||||
let redoActionName: String
|
||||
let articles: Set<Article>
|
||||
let undoManager: UndoManager
|
||||
let markingRead: Bool
|
||||
|
||||
init?(initialArticles: [Article], markingRead: Bool, undoManager: UndoManager) {
|
||||
|
||||
// Filter out articles already read.
|
||||
let articlesToMark = initialArticles.filter { markingRead ? !$0.status.read : $0.status.read }
|
||||
if articlesToMark.isEmpty {
|
||||
return nil
|
||||
}
|
||||
self.articles = Set(articlesToMark)
|
||||
|
||||
self.markingRead = markingRead
|
||||
|
||||
self.undoManager = undoManager
|
||||
|
||||
if markingRead {
|
||||
self.undoActionName = MarkReadOrUnreadCommand.markReadActionName
|
||||
self.redoActionName = MarkReadOrUnreadCommand.markReadActionName
|
||||
}
|
||||
else {
|
||||
self.undoActionName = MarkReadOrUnreadCommand.markUnreadActionName
|
||||
self.redoActionName = MarkReadOrUnreadCommand.markUnreadActionName
|
||||
}
|
||||
}
|
||||
|
||||
func perform() {
|
||||
mark(read: markingRead)
|
||||
registerUndo()
|
||||
}
|
||||
|
||||
func undo() {
|
||||
mark(read: !markingRead)
|
||||
registerRedo()
|
||||
}
|
||||
}
|
||||
|
||||
private extension MarkReadOrUnreadCommand {
|
||||
|
||||
func mark(read: Bool) {
|
||||
|
||||
markArticles(articles, statusKey: .read, flag: read)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// MarkStatusCommand.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 10/26/17.
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSCore
|
||||
import Data
|
||||
|
||||
// Mark articles read/unread, starred/unstarred, deleted/undeleted.
|
||||
|
||||
final class MarkStatusCommand: UndoableCommand {
|
||||
|
||||
let undoActionName: String
|
||||
let redoActionName: String
|
||||
let articles: Set<Article>
|
||||
let undoManager: UndoManager
|
||||
let flag: Bool
|
||||
let statusKey: ArticleStatus.Key
|
||||
|
||||
init?(initialArticles: [Article], statusKey: ArticleStatus.Key, flag: Bool, undoManager: UndoManager) {
|
||||
|
||||
// Filter out articles that already have the desired status.
|
||||
let articlesToMark = MarkStatusCommand.filteredArticles(initialArticles, statusKey, flag)
|
||||
if articlesToMark.isEmpty {
|
||||
return nil
|
||||
}
|
||||
self.articles = Set(articlesToMark)
|
||||
|
||||
self.flag = flag
|
||||
self.statusKey = statusKey
|
||||
self.undoManager = undoManager
|
||||
|
||||
let actionName = MarkStatusCommand.actionName(statusKey, flag)
|
||||
self.undoActionName = actionName
|
||||
self.redoActionName = actionName
|
||||
}
|
||||
|
||||
convenience init?(initialArticles: [Article], markingRead: Bool, undoManager: UndoManager) {
|
||||
|
||||
self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, undoManager: undoManager)
|
||||
}
|
||||
|
||||
convenience init?(initialArticles: [Article], markingStarred: Bool, undoManager: UndoManager) {
|
||||
|
||||
self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, undoManager: undoManager)
|
||||
}
|
||||
|
||||
func perform() {
|
||||
mark(statusKey, flag)
|
||||
registerUndo()
|
||||
}
|
||||
|
||||
func undo() {
|
||||
mark(statusKey, !flag)
|
||||
registerRedo()
|
||||
}
|
||||
}
|
||||
|
||||
private extension MarkStatusCommand {
|
||||
|
||||
func mark(_ statusKey: ArticleStatus.Key, _ flag: Bool) {
|
||||
|
||||
markArticles(articles, statusKey: statusKey, flag: flag)
|
||||
}
|
||||
|
||||
static private let markReadActionName = NSLocalizedString("Mark Read", comment: "command")
|
||||
static private let markUnreadActionName = NSLocalizedString("Mark Unread", comment: "command")
|
||||
static private let markStarredActionName = NSLocalizedString("Mark Starred", comment: "command")
|
||||
static private let markUnstarredActionName = NSLocalizedString("Mark Unstarred", comment: "command")
|
||||
static private let markDeletedActionName = NSLocalizedString("Delete", comment: "command")
|
||||
static private let markUndeletedActionName = NSLocalizedString("Undelete", comment: "command")
|
||||
|
||||
static func actionName(_ statusKey: ArticleStatus.Key, _ flag: Bool) -> String {
|
||||
|
||||
switch statusKey {
|
||||
case .read:
|
||||
return flag ? markReadActionName : markUnreadActionName
|
||||
case .starred:
|
||||
return flag ? markStarredActionName : markUnstarredActionName
|
||||
case .userDeleted:
|
||||
return flag ? markDeletedActionName : markUndeletedActionName
|
||||
}
|
||||
}
|
||||
|
||||
static func filteredArticles(_ articles: [Article], _ statusKey: ArticleStatus.Key, _ flag: Bool) -> [Article] {
|
||||
|
||||
return articles.filter{ $0.status.boolStatus(forKey: statusKey) != flag }
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ class MasterViewController: UITableViewController {
|
|||
}
|
||||
|
||||
@objc
|
||||
func insertNewObject(_ sender: Any) {
|
||||
func insertNewObject(_ sender: Any?) {
|
||||
objects.insert(NSDate(), at: 0)
|
||||
let indexPath = IndexPath(row: 0, section: 0)
|
||||
tableView.insertRows(at: [indexPath], with: .automatic)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
8403E75B201C4A79007F7246 /* FeedListKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8403E75A201C4A79007F7246 /* FeedListKeyboardDelegate.swift */; };
|
||||
840D617F2029031C009BC708 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D617E2029031C009BC708 /* AppDelegate.swift */; };
|
||||
840D61812029031C009BC708 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61802029031C009BC708 /* MasterViewController.swift */; };
|
||||
840D61832029031C009BC708 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61822029031C009BC708 /* DetailViewController.swift */; };
|
||||
|
@ -17,6 +16,8 @@
|
|||
840D61962029031D009BC708 /* Evergreen_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61952029031D009BC708 /* Evergreen_iOSTests.swift */; };
|
||||
840D61A12029031E009BC708 /* Evergreen_iOSUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61A02029031E009BC708 /* Evergreen_iOSUITests.swift */; };
|
||||
8414AD251FCF5A1E00955102 /* TimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8414AD241FCF5A1E00955102 /* TimelineHeaderView.swift */; };
|
||||
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; };
|
||||
84162A252038C1E000035290 /* TimelineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A242038C1E000035290 /* TimelineDataSource.swift */; };
|
||||
841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */; };
|
||||
841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; };
|
||||
841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */; };
|
||||
|
@ -29,6 +30,7 @@
|
|||
842E45E31ED8C681000A8B52 /* KeyboardDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45E21ED8C681000A8B52 /* KeyboardDelegateProtocol.swift */; };
|
||||
842E45E51ED8C6B7000A8B52 /* MainWindowSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45E41ED8C6B7000A8B52 /* MainWindowSplitView.swift */; };
|
||||
842E45E71ED8C747000A8B52 /* DB5.plist in Resources */ = {isa = PBXBuildFile; fileRef = 842E45E61ED8C747000A8B52 /* DB5.plist */; };
|
||||
843A3B5620311E7700BF76EC /* FeedListOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843A3B5520311E7700BF76EC /* FeedListOutlineView.swift */; };
|
||||
84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; };
|
||||
8444C8F21FED81840051386C /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; };
|
||||
844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B581FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift */; };
|
||||
|
@ -36,7 +38,6 @@
|
|||
844B5B651FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */; };
|
||||
844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B661FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift */; };
|
||||
844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */; };
|
||||
84513F901FAA63950023A1A9 /* FeedListControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */; };
|
||||
845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; };
|
||||
845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; };
|
||||
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; };
|
||||
|
@ -49,7 +50,7 @@
|
|||
846E773E1F6EF67A00A165E2 /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E773A1F6EF5D700A165E2 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
846E77411F6EF6A100A165E2 /* Database.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846E77211F6EF5D100A165E2 /* Database.framework */; };
|
||||
846E77421F6EF6A100A165E2 /* Database.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E77211F6EF5D100A165E2 /* Database.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */; };
|
||||
84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; };
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; };
|
||||
847FA121202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */; };
|
||||
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; };
|
||||
|
@ -98,12 +99,18 @@
|
|||
849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849C64671ED37A5D003D8FC0 /* Assets.xcassets */; };
|
||||
849C646B1ED37A5D003D8FC0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 849C64691ED37A5D003D8FC0 /* Main.storyboard */; };
|
||||
849C64761ED37A5D003D8FC0 /* EvergreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C64751ED37A5D003D8FC0 /* EvergreenTests.swift */; };
|
||||
849EE70F203919360082A1EA /* AppImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE70E203919360082A1EA /* AppImages.swift */; };
|
||||
849EE71F20391DF20082A1EA /* MainWindowToolbarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE71E20391DF20082A1EA /* MainWindowToolbarDelegate.swift */; };
|
||||
849EE72120391F560082A1EA /* MainWindowSharingServicePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE72020391F560082A1EA /* MainWindowSharingServicePickerDelegate.swift */; };
|
||||
84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; };
|
||||
84A1500320048D660046AD9A /* SendToCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500220048D660046AD9A /* SendToCommand.swift */; };
|
||||
84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; };
|
||||
84A37CB5201ECD610087C5AF /* RenameWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */; };
|
||||
84A37CBB201ECE590087C5AF /* RenameSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84A37CB9201ECE590087C5AF /* RenameSheet.xib */; };
|
||||
84AAF2BF202CF684004A0BC4 /* TimelineContextualMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AAF2BE202CF684004A0BC4 /* TimelineContextualMenuDelegate.swift */; };
|
||||
84AD1EAA2031617300BC20B7 /* FolderPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EA92031617300BC20B7 /* FolderPasteboardWriter.swift */; };
|
||||
84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; };
|
||||
84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */; };
|
||||
84B06FAE1ED37DBD00F0B54B /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FA91ED37DAD00F0B54B /* RSCore.framework */; };
|
||||
84B06FAF1ED37DBD00F0B54B /* RSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FA91ED37DAD00F0B54B /* RSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
84B06FB21ED37DBD00F0B54B /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06F9D1ED37DA000F0B54B /* RSDatabase.framework */; };
|
||||
|
@ -128,7 +135,6 @@
|
|||
84BBB12D20142A4700F054F5 /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84BBB12B20142A4700F054F5 /* Inspector.storyboard */; };
|
||||
84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */; };
|
||||
84C12A151FF5B0080009A267 /* FeedList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84C12A141FF5B0080009A267 /* FeedList.storyboard */; };
|
||||
84CC08061FF5D2E000C0C0ED /* FeedListSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC08051FF5D2E000C0C0ED /* FeedListSplitViewController.swift */; };
|
||||
84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; };
|
||||
84D52E951FE588BB00D14F5B /* DetailStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */; };
|
||||
84D5BA20201E8FB6009092BD /* SidebarGearMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */; };
|
||||
|
@ -531,6 +537,8 @@
|
|||
840D61A02029031E009BC708 /* Evergreen_iOSUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Evergreen_iOSUITests.swift; sourceTree = "<group>"; };
|
||||
840D61A22029031E009BC708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
8414AD241FCF5A1E00955102 /* TimelineHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineHeaderView.swift; sourceTree = "<group>"; };
|
||||
84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkCommandValidationStatus.swift; sourceTree = "<group>"; };
|
||||
84162A242038C1E000035290 /* TimelineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineDataSource.swift; sourceTree = "<group>"; };
|
||||
841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NothingInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuiltinSmartFeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -543,6 +551,7 @@
|
|||
842E45E21ED8C681000A8B52 /* KeyboardDelegateProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardDelegateProtocol.swift; sourceTree = "<group>"; };
|
||||
842E45E41ED8C6B7000A8B52 /* MainWindowSplitView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowSplitView.swift; sourceTree = "<group>"; };
|
||||
842E45E61ED8C747000A8B52 /* DB5.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = DB5.plist; path = Evergreen/Resources/DB5.plist; sourceTree = "<group>"; };
|
||||
843A3B5520311E7700BF76EC /* FeedListOutlineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListOutlineView.swift; sourceTree = "<group>"; };
|
||||
84411E701FE5FBFA004B527F /* SmallIconProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallIconProvider.swift; sourceTree = "<group>"; };
|
||||
8444C8F11FED81840051386C /* OPMLExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLExporter.swift; sourceTree = "<group>"; };
|
||||
844B5B581FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarKeyboardDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -562,7 +571,7 @@
|
|||
845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedPasteboardWriter.swift; sourceTree = "<group>"; };
|
||||
846E77161F6EF5D000A165E2 /* Database.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Database.xcodeproj; path = Frameworks/Database/Database.xcodeproj; sourceTree = "<group>"; };
|
||||
846E77301F6EF5D600A165E2 /* Account.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Account.xcodeproj; path = Frameworks/Account/Account.xcodeproj; sourceTree = "<group>"; };
|
||||
84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkReadOrUnreadCommand.swift; sourceTree = "<group>"; };
|
||||
84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkStatusCommand.swift; sourceTree = "<group>"; };
|
||||
8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
|
||||
847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarContextualMenuDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -615,6 +624,9 @@
|
|||
849C64711ED37A5D003D8FC0 /* EvergreenTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EvergreenTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
849C64751ED37A5D003D8FC0 /* EvergreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvergreenTests.swift; sourceTree = "<group>"; };
|
||||
849C64771ED37A5D003D8FC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
849EE70E203919360082A1EA /* AppImages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppImages.swift; path = Evergreen/AppImages.swift; sourceTree = "<group>"; };
|
||||
849EE71E20391DF20082A1EA /* MainWindowToolbarDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindowToolbarDelegate.swift; sourceTree = "<group>"; };
|
||||
849EE72020391F560082A1EA /* MainWindowSharingServicePickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindowSharingServicePickerDelegate.swift; sourceTree = "<group>"; };
|
||||
84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToMicroBlogCommand.swift; sourceTree = "<group>"; };
|
||||
84A1500220048D660046AD9A /* SendToCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToCommand.swift; sourceTree = "<group>"; };
|
||||
84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToMarsEditCommand.swift; sourceTree = "<group>"; };
|
||||
|
@ -623,6 +635,9 @@
|
|||
84A6B6931FB8D43C006754AC /* DinosaursWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DinosaursWindow.xib; sourceTree = "<group>"; };
|
||||
84A6B6951FB8DBD2006754AC /* DinosaursWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DinosaursWindowController.swift; sourceTree = "<group>"; };
|
||||
84AAF2BE202CF684004A0BC4 /* TimelineContextualMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineContextualMenuDelegate.swift; sourceTree = "<group>"; };
|
||||
84AD1EA92031617300BC20B7 /* FolderPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderPasteboardWriter.swift; sourceTree = "<group>"; };
|
||||
84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedPasteboardWriter.swift; sourceTree = "<group>"; };
|
||||
84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarOutlineDataSource.swift; sourceTree = "<group>"; };
|
||||
84B06F961ED37DA000F0B54B /* RSDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSDatabase.xcodeproj; path = Frameworks/RSDatabase/RSDatabase.xcodeproj; sourceTree = "<group>"; };
|
||||
84B06FA21ED37DAC00F0B54B /* RSCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSCore.xcodeproj; path = Frameworks/RSCore/RSCore.xcodeproj; sourceTree = "<group>"; };
|
||||
84B06FB61ED37E8B00F0B54B /* RSWeb.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSWeb.xcodeproj; path = Frameworks/RSWeb/RSWeb.xcodeproj; sourceTree = "<group>"; };
|
||||
|
@ -802,6 +817,8 @@
|
|||
849A97B01ED9FA69007D329B /* MainWindow.storyboard */,
|
||||
842E45E21ED8C681000A8B52 /* KeyboardDelegateProtocol.swift */,
|
||||
849A975D1ED9EB72007D329B /* MainWindowController.swift */,
|
||||
849EE71E20391DF20082A1EA /* MainWindowToolbarDelegate.swift */,
|
||||
849EE72020391F560082A1EA /* MainWindowSharingServicePickerDelegate.swift */,
|
||||
842E45E41ED8C6B7000A8B52 /* MainWindowSplitView.swift */,
|
||||
844B5B6B1FEA224B00C7C76A /* Keyboard */,
|
||||
849A975F1ED9EB95007D329B /* Sidebar */,
|
||||
|
@ -893,7 +910,8 @@
|
|||
84702AB31FA27AE8006B8943 /* Commands */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */,
|
||||
84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */,
|
||||
84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */,
|
||||
84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */,
|
||||
84A1500220048D660046AD9A /* SendToCommand.swift */,
|
||||
84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */,
|
||||
|
@ -950,10 +968,12 @@
|
|||
children = (
|
||||
849A97621ED9EB96007D329B /* SidebarViewController.swift */,
|
||||
84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */,
|
||||
84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */,
|
||||
849A97601ED9EB96007D329B /* SidebarOutlineView.swift */,
|
||||
849A97611ED9EB96007D329B /* SidebarTreeControllerDelegate.swift */,
|
||||
849A97631ED9EB96007D329B /* UnreadCountView.swift */,
|
||||
845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */,
|
||||
84AD1EA92031617300BC20B7 /* FolderPasteboardWriter.swift */,
|
||||
849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */,
|
||||
84D5BA1F201E8FB6009092BD /* SidebarGearMenuDelegate.swift */,
|
||||
847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */,
|
||||
|
@ -969,6 +989,7 @@
|
|||
children = (
|
||||
849A976B1ED9EBC8007D329B /* TimelineViewController.swift */,
|
||||
84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */,
|
||||
84162A242038C1E000035290 /* TimelineDataSource.swift */,
|
||||
84F204DF1FAACBB30076E152 /* ArticleArray.swift */,
|
||||
849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */,
|
||||
849A976A1ED9EBC8007D329B /* TimelineTableView.swift */,
|
||||
|
@ -1022,14 +1043,13 @@
|
|||
children = (
|
||||
84C12A141FF5B0080009A267 /* FeedList.storyboard */,
|
||||
849A978C1ED9EE4D007D329B /* FeedListWindowController.swift */,
|
||||
84CC08051FF5D2E000C0C0ED /* FeedListSplitViewController.swift */,
|
||||
84F204CD1FAACB660076E152 /* FeedListViewController.swift */,
|
||||
8403E75A201C4A79007F7246 /* FeedListKeyboardDelegate.swift */,
|
||||
84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */,
|
||||
843A3B5520311E7700BF76EC /* FeedListOutlineView.swift */,
|
||||
84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */,
|
||||
84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */,
|
||||
84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */,
|
||||
84E95CF61FABB3C800552D99 /* FeedList.plist */,
|
||||
84DC413A20310AEE00198AD4 /* UnusedIn1.0 */,
|
||||
);
|
||||
name = "Feed List";
|
||||
path = Evergreen/FeedList;
|
||||
|
@ -1085,6 +1105,7 @@
|
|||
849C64631ED37A5D003D8FC0 /* AppDelegate.swift */,
|
||||
84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */,
|
||||
842E45CD1ED8C308000A8B52 /* AppNotifications.swift */,
|
||||
849EE70E203919360082A1EA /* AppImages.swift */,
|
||||
84DAEE311F870B390058304B /* DockBadge.swift */,
|
||||
842E45DC1ED8C54B000A8B52 /* Browser.swift */,
|
||||
84702AB31FA27AE8006B8943 /* Commands */,
|
||||
|
@ -1267,6 +1288,16 @@
|
|||
path = Importers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
84DC413A20310AEE00198AD4 /* UnusedIn1.0 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
84CC08051FF5D2E000C0C0ED /* FeedListSplitViewController.swift */,
|
||||
8403E75A201C4A79007F7246 /* FeedListKeyboardDelegate.swift */,
|
||||
84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */,
|
||||
);
|
||||
path = UnusedIn1.0;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
84EB380F1FBA8B9F000D2111 /* KeyboardShortcuts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1285,6 +1316,7 @@
|
|||
845EE7C01FC2488C00854A1F /* SmartFeed.swift */,
|
||||
84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */,
|
||||
845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */,
|
||||
84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */,
|
||||
);
|
||||
name = SmartFeeds;
|
||||
path = Evergreen/SmartFeeds;
|
||||
|
@ -1870,7 +1902,6 @@
|
|||
files = (
|
||||
84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */,
|
||||
849C64641ED37A5D003D8FC0 /* AppDelegate.swift in Sources */,
|
||||
84513F901FAA63950023A1A9 /* FeedListControlsView.swift in Sources */,
|
||||
84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */,
|
||||
84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */,
|
||||
D5907D972004B7EB005947E5 /* Account+Scriptability.swift in Sources */,
|
||||
|
@ -1888,20 +1919,21 @@
|
|||
842E45E51ED8C6B7000A8B52 /* MainWindowSplitView.swift in Sources */,
|
||||
84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */,
|
||||
845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */,
|
||||
84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */,
|
||||
84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */,
|
||||
849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */,
|
||||
849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */,
|
||||
D5A2678C20130ECF00A8D3C0 /* Author+Scriptability.swift in Sources */,
|
||||
84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */,
|
||||
D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */,
|
||||
8403E75B201C4A79007F7246 /* FeedListKeyboardDelegate.swift in Sources */,
|
||||
845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */,
|
||||
84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */,
|
||||
84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */,
|
||||
D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */,
|
||||
849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */,
|
||||
849A97651ED9EB96007D329B /* SidebarTreeControllerDelegate.swift in Sources */,
|
||||
849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */,
|
||||
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */,
|
||||
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */,
|
||||
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */,
|
||||
849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */,
|
||||
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */,
|
||||
|
@ -1917,12 +1949,17 @@
|
|||
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
|
||||
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */,
|
||||
849A97791ED9EC04007D329B /* TimelineStringUtilities.swift in Sources */,
|
||||
843A3B5620311E7700BF76EC /* FeedListOutlineView.swift in Sources */,
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */,
|
||||
84AD1EAA2031617300BC20B7 /* FolderPasteboardWriter.swift in Sources */,
|
||||
84F204CE1FAACB660076E152 /* FeedListViewController.swift in Sources */,
|
||||
84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */,
|
||||
845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */,
|
||||
845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */,
|
||||
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */,
|
||||
849EE72120391F560082A1EA /* MainWindowSharingServicePickerDelegate.swift in Sources */,
|
||||
849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */,
|
||||
849EE70F203919360082A1EA /* AppImages.swift in Sources */,
|
||||
849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */,
|
||||
84AAF2BF202CF684004A0BC4 /* TimelineContextualMenuDelegate.swift in Sources */,
|
||||
849A97831ED9EC63007D329B /* SidebarStatusBarView.swift in Sources */,
|
||||
|
@ -1945,12 +1982,12 @@
|
|||
842611A01FCB72600086A189 /* FeaturedImageDownloader.swift in Sources */,
|
||||
849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */,
|
||||
84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */,
|
||||
84CC08061FF5D2E000C0C0ED /* FeedListSplitViewController.swift in Sources */,
|
||||
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */,
|
||||
849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */,
|
||||
84B99C9D1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift in Sources */,
|
||||
849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */,
|
||||
849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */,
|
||||
84162A252038C1E000035290 /* TimelineDataSource.swift in Sources */,
|
||||
84D52E951FE588BB00D14F5B /* DetailStatusBarView.swift in Sources */,
|
||||
D5E4CC64202C1AC1009B4FFC /* MainWindowController+Scriptability.swift in Sources */,
|
||||
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */,
|
||||
|
@ -1965,6 +2002,7 @@
|
|||
D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */,
|
||||
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */,
|
||||
8414AD251FCF5A1E00955102 /* TimelineHeaderView.swift in Sources */,
|
||||
849EE71F20391DF20082A1EA /* MainWindowToolbarDelegate.swift in Sources */,
|
||||
849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */,
|
||||
849A97761ED9EC04007D329B /* TimelineCellAppearance.swift in Sources */,
|
||||
849A97A21ED9F180007D329B /* InitialFeedDownloader.swift in Sources */,
|
||||
|
|
|
@ -128,21 +128,24 @@ private extension AppDefaults {
|
|||
|
||||
static func registerDefaults() {
|
||||
|
||||
let defaults = [Key.sidebarFontSize: FontSize.medium.rawValue, Key.timelineFontSize: FontSize.medium.rawValue, Key.detailFontSize: FontSize.medium.rawValue, Key.timelineSortDirection: ComparisonResult.orderedDescending.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]
|
||||
|
||||
UserDefaults.standard.register(defaults: defaults)
|
||||
}
|
||||
|
||||
func fontSize(for key: String) -> FontSize {
|
||||
|
||||
var rawFontSize = int(for: key)
|
||||
if rawFontSize < smallestFontSizeRawValue {
|
||||
rawFontSize = smallestFontSizeRawValue
|
||||
}
|
||||
if rawFontSize > largestFontSizeRawValue {
|
||||
rawFontSize = largestFontSizeRawValue
|
||||
}
|
||||
return FontSize(rawValue: rawFontSize)!
|
||||
// Punted till after 1.0.
|
||||
return .medium
|
||||
|
||||
// var rawFontSize = int(for: key)
|
||||
// if rawFontSize < smallestFontSizeRawValue {
|
||||
// rawFontSize = smallestFontSizeRawValue
|
||||
// }
|
||||
// if rawFontSize > largestFontSizeRawValue {
|
||||
// rawFontSize = largestFontSizeRawValue
|
||||
// }
|
||||
// return FontSize(rawValue: rawFontSize)!
|
||||
}
|
||||
|
||||
func setFontSize(for key: String, _ fontSize: FontSize) {
|
||||
|
|
|
@ -26,17 +26,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||
var authorAvatarDownloader: AuthorAvatarDownloader!
|
||||
var feedIconDownloader: FeedIconDownloader!
|
||||
var appName: String!
|
||||
|
||||
|
||||
@IBOutlet var debugMenuItem: NSMenuItem!
|
||||
@IBOutlet var sortByOldestArticleOnTopMenuItem: NSMenuItem!
|
||||
@IBOutlet var sortByNewestArticleOnTopMenuItem: NSMenuItem!
|
||||
|
||||
lazy var genericFeedImage: NSImage? = {
|
||||
let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/BookmarkIcon.icns"
|
||||
let image = NSImage(contentsOfFile: path)
|
||||
return image
|
||||
}()
|
||||
|
||||
lazy var sendToCommands: [SendToCommand] = {
|
||||
return [SendToMicroBlogCommand(), SendToMarsEditCommand()]
|
||||
}()
|
||||
|
@ -273,7 +267,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||
readerWindow.showWindow(self)
|
||||
}
|
||||
|
||||
@IBAction func showPreferences(_ sender: AnyObject) {
|
||||
@IBAction func showPreferences(_ sender: Any?) {
|
||||
|
||||
if preferencesWindowController == nil {
|
||||
preferencesWindowController = windowControllerWithName("Preferences")
|
||||
|
@ -282,28 +276,28 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||
preferencesWindowController!.showWindow(self)
|
||||
}
|
||||
|
||||
@IBAction func showMainWindow(_ sender: AnyObject) {
|
||||
@IBAction func showMainWindow(_ sender: Any?) {
|
||||
|
||||
createAndShowMainWindow()
|
||||
}
|
||||
|
||||
@IBAction func refreshAll(_ sender: AnyObject) {
|
||||
@IBAction func refreshAll(_ sender: Any?) {
|
||||
|
||||
AccountManager.shared.refreshAll()
|
||||
}
|
||||
|
||||
@IBAction func showAddFeedWindow(_ sender: AnyObject) {
|
||||
@IBAction func showAddFeedWindow(_ sender: Any?) {
|
||||
|
||||
addFeed(nil)
|
||||
}
|
||||
|
||||
@IBAction func showAddFolderWindow(_ sender: AnyObject) {
|
||||
@IBAction func showAddFolderWindow(_ sender: Any?) {
|
||||
|
||||
createAndShowMainWindow()
|
||||
showAddFolderSheetOnWindow(mainWindowController!.window!)
|
||||
}
|
||||
|
||||
@IBAction func showFeedList(_ sender: AnyObject) {
|
||||
@IBAction func showFeedList(_ sender: Any?) {
|
||||
|
||||
if feedListWindowController == nil {
|
||||
feedListWindowController = windowControllerWithName("FeedList")
|
||||
|
@ -353,7 +347,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||
logWindowController!.showWindow(self)
|
||||
}
|
||||
|
||||
@IBAction func importOPMLFromFile(_ sender: AnyObject) {
|
||||
@IBAction func importOPMLFromFile(_ sender: Any?) {
|
||||
|
||||
let panel = NSOpenPanel()
|
||||
panel.canDownloadUbiquitousContents = true
|
||||
|
@ -378,11 +372,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||
}
|
||||
}
|
||||
|
||||
@IBAction func importOPMLFromURL(_ sender: AnyObject) {
|
||||
@IBAction func importOPMLFromURL(_ sender: Any?) {
|
||||
|
||||
}
|
||||
|
||||
@IBAction func exportOPML(_ sender: AnyObject) {
|
||||
@IBAction func exportOPML(_ sender: Any?) {
|
||||
|
||||
let panel = NSSavePanel()
|
||||
panel.allowedFileTypes = ["opml"]
|
||||
|
@ -409,7 +403,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||
}
|
||||
}
|
||||
|
||||
@IBAction func addAppNews(_ sender: AnyObject) {
|
||||
@IBAction func addAppNews(_ sender: Any?) {
|
||||
|
||||
if AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) {
|
||||
return
|
||||
|
@ -417,17 +411,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||
addFeed(appNewsURLString, "Evergreen News")
|
||||
}
|
||||
|
||||
@IBAction func openWebsite(_ sender: AnyObject) {
|
||||
@IBAction func openWebsite(_ sender: Any?) {
|
||||
|
||||
Browser.open("https://ranchero.com/evergreen/", inBackground: false)
|
||||
}
|
||||
|
||||
@IBAction func openRepository(_ sender: AnyObject) {
|
||||
@IBAction func openRepository(_ sender: Any?) {
|
||||
|
||||
Browser.open("https://github.com/brentsimmons/Evergreen", inBackground: false)
|
||||
}
|
||||
|
||||
@IBAction func openBugTracker(_ sender: AnyObject) {
|
||||
@IBAction func openBugTracker(_ sender: Any?) {
|
||||
|
||||
Browser.open("https://github.com/brentsimmons/Evergreen/issues", inBackground: false)
|
||||
}
|
||||
|
@ -437,11 +431,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||
Browser.open("https://github.com/brentsimmons/Evergreen/tree/master/Technotes", inBackground: false)
|
||||
}
|
||||
|
||||
@IBAction func showHelp(_ sender: AnyObject) {
|
||||
@IBAction func showHelp(_ sender: Any?) {
|
||||
|
||||
Browser.open("https://ranchero.com/evergreen/help/1.0/", inBackground: false)
|
||||
}
|
||||
|
||||
@IBAction func donateToAppCampForGirls(_ sender: Any?) {
|
||||
|
||||
Browser.open("https://appcamp4girls.com/contribute/", inBackground: false)
|
||||
}
|
||||
|
||||
@IBAction func debugDropConditionalGetInfo(_ sender: Any?) {
|
||||
#if DEBUG
|
||||
AccountManager.shared.accounts.forEach{ $0.debugDropConditionalGetInfo() }
|
||||
|
@ -494,9 +493,8 @@ private extension AppDelegate {
|
|||
|
||||
func saveState() {
|
||||
|
||||
if let inspectorWindowController = inspectorWindowController {
|
||||
inspectorWindowController.saveState()
|
||||
}
|
||||
inspectorWindowController?.saveState()
|
||||
mainWindowController?.saveState()
|
||||
}
|
||||
|
||||
func updateSortMenuItems() {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// AppImages.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/17/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
extension NSImage.Name {
|
||||
static let star = NSImage.Name(rawValue: "star")
|
||||
static let unstar = NSImage.Name(rawValue: "unstar")
|
||||
static let timelineStar = NSImage.Name(rawValue: "timelineStar")
|
||||
}
|
||||
|
||||
struct AppImages {
|
||||
|
||||
static var genericFeedImage: NSImage? = {
|
||||
let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/BookmarkIcon.icns"
|
||||
let image = NSImage(contentsOfFile: path)
|
||||
return image
|
||||
}()
|
||||
|
||||
static var timelineStar: NSImage! = {
|
||||
return NSImage(named: .timelineStar)
|
||||
}()
|
||||
}
|
|
@ -14,8 +14,6 @@ extension Notification.Name {
|
|||
static let SidebarSelectionDidChange = Notification.Name("SidebarSelectionDidChangeNotification")
|
||||
static let TimelineSelectionDidChange = Notification.Name("TimelineSelectionDidChangeNotification")
|
||||
|
||||
static let AppNavigationKeyPressed = Notification.Name("AppNavigationKeyPressedNotification")
|
||||
|
||||
static let UserDidAddFeed = Notification.Name("UserDidAddFeedNotification")
|
||||
|
||||
// Sent by DetailViewController when mouse hovers over link in web view.
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "timelineStar.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "timelineStar@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 981 B |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14092" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14092"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
|
@ -426,7 +426,11 @@
|
|||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="gB0-WX-2Gd"/>
|
||||
<menuItem title="Mark as Starred" keyEquivalent="L" id="vvo-ZM-8kl"/>
|
||||
<menuItem title="Mark as Starred" keyEquivalent="L" id="vvo-ZM-8kl">
|
||||
<connections>
|
||||
<action selector="toggleStarred:" target="Ady-hI-5gd" id="vIK-kP-re7"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="qgr-Gl-Xhw"/>
|
||||
<menuItem title="Open in Browser" keyEquivalent="" id="4iQ-1v-dTa">
|
||||
<connections>
|
||||
|
@ -538,6 +542,13 @@
|
|||
<action selector="openTechnotes:" target="Ady-hI-5gd" id="M7A-Qg-mH8"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="sHh-z2-8AO"/>
|
||||
<menuItem title="Donate to App Camp for Girls" id="Vrr-S1-18B">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="donateToAppCampForGirls:" target="Ady-hI-5gd" id="aYY-R7-b1U"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14092" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14092"/>
|
||||
<capability name="box content view" minToolsVersion="7.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
<capability name="system font weights other than Regular or Bold" minToolsVersion="7.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Window Controller-->
|
||||
|
@ -134,7 +136,7 @@
|
|||
</buttonCell>
|
||||
</button>
|
||||
<connections>
|
||||
<action selector="markRead:" target="Oky-zY-oP4" id="fYI-jM-htG"/>
|
||||
<action selector="toggleStarred:" target="Oky-zY-oP4" id="44J-UK-89l"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="4DED27B7-8961-48F3-A995-9C961E2C0257" label="Open in Browser" paletteLabel="Open in Browser" toolTip="Open in Browser" image="openInBrowser" id="tid-SB-me3" customClass="RSToolbarItem" customModule="RSCore">
|
||||
|
@ -183,7 +185,7 @@
|
|||
<toolbarItem reference="nv0-Ju-lP7"/>
|
||||
</defaultToolbarItems>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="B8D-0N-5wS" id="hrC-d9-Cge"/>
|
||||
<outlet property="delegate" destination="V3e-nc-6hW" id="ncd-Wh-qZU"/>
|
||||
</connections>
|
||||
</toolbar>
|
||||
<connections>
|
||||
|
@ -191,10 +193,12 @@
|
|||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<outlet property="toolbarDelegate" destination="V3e-nc-6hW" id="0rl-4U-DkU"/>
|
||||
<segue destination="reS-fe-pD8" kind="relationship" relationship="window.shadowedContentViewController" id="WS2-WB-dc4"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<customObject id="V3e-nc-6hW" customClass="MainWindowToolbarDelegate" customModule="Evergreen" customModuleProvider="target"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-666" y="-124"/>
|
||||
</scene>
|
||||
|
@ -267,7 +271,7 @@
|
|||
<subviews>
|
||||
<scrollView appearanceType="aqua" borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="cJj-Wv-9ep">
|
||||
<rect key="frame" x="0.0" y="28" width="166" height="272"/>
|
||||
<clipView key="contentView" drawsBackground="NO" id="2eU-Wz-F9g">
|
||||
<clipView key="contentView" id="2eU-Wz-F9g">
|
||||
<rect key="frame" x="0.0" y="0.0" width="166" height="272"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
|
@ -275,7 +279,7 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="167" height="272"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.95999999999999996" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="" width="164" minWidth="16" maxWidth="1000" id="ih9-mJ-EA7">
|
||||
|
@ -337,7 +341,6 @@
|
|||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="XML-A3-pDn" id="04v-0e-BM6"/>
|
||||
<outlet property="delegate" destination="XML-A3-pDn" id="fPE-cv-p5c"/>
|
||||
<outlet property="keyboardDelegate" destination="h5K-zR-cUa" id="BlT-aW-sea"/>
|
||||
<outlet property="menu" destination="p3f-EZ-sSD" id="KTA-tl-UrO"/>
|
||||
|
@ -358,7 +361,7 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="HZs-Zf-G8s" customClass="SidebarStatusBarView" customModule="Evergreen" customModuleProvider="target">
|
||||
<customView appearanceType="aqua" translatesAutoresizingMaskIntoConstraints="NO" id="HZs-Zf-G8s" customClass="SidebarStatusBarView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="166" height="28"/>
|
||||
<subviews>
|
||||
<popUpButton translatesAutoresizingMaskIntoConstraints="NO" id="gZE-LB-FdW">
|
||||
|
@ -532,18 +535,17 @@
|
|||
<constraint firstItem="KZz-oC-IY4" firstAttribute="leading" secondItem="rE6-fd-xjY" secondAttribute="leading" id="dqI-OO-8A0"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="98" horizontalPageScroll="10" verticalLineScroll="98" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Kfs-n2-RYk">
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="96" horizontalPageScroll="10" verticalLineScroll="96" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Kfs-n2-RYk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="198"/>
|
||||
<clipView key="contentView" id="yAN-Ex-RC7">
|
||||
<clipView key="contentView" copiesOnScroll="NO" id="yAN-Ex-RC7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="198"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="96" viewBased="YES" id="DRs-j8-R9a" customClass="TimelineTableView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="198"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<color key="gridColor" white="0.90000000000000002" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="gridColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="" width="447" minWidth="40" maxWidth="1000" id="5h5-G1-xGq">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
|
@ -559,18 +561,18 @@
|
|||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="timelineCell" id="58o-U2-ss4" customClass="TimelineTableCellView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="1" y="1" width="447" height="68"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="447" height="68"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</tableCellView>
|
||||
<customView identifier="timelineRow" id="54E-Vz-WND" customClass="TimelineTableRowView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="1" y="71" width="447" height="96"/>
|
||||
<rect key="frame" x="0.0" y="68" width="447" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</customView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="36G-bQ-b96" id="OpB-zC-ItJ"/>
|
||||
<outlet property="dataSource" destination="7kA-tA-2n7" id="Tdx-DB-Zjs"/>
|
||||
<outlet property="delegate" destination="36G-bQ-b96" id="s1m-42-GQ4"/>
|
||||
<outlet property="keyboardDelegate" destination="ZOV-xh-WJE" id="HiG-Bz-vD0"/>
|
||||
<outlet property="menu" destination="gb5-z4-YPr" id="pey-0u-ogu"/>
|
||||
|
@ -606,6 +608,7 @@
|
|||
</view>
|
||||
<connections>
|
||||
<outlet property="contextualMenuDelegate" destination="iD1-KK-gFc" id="b0j-aW-e4B"/>
|
||||
<outlet property="dataSource" destination="7kA-tA-2n7" id="heZ-eT-SZ6"/>
|
||||
<outlet property="tableView" destination="DRs-j8-R9a" id="2AG-SP-7n2"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
|
@ -636,6 +639,7 @@
|
|||
<outlet property="timelineViewController" destination="36G-bQ-b96" id="oE9-uV-TNi"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="7kA-tA-2n7" customClass="TimelineDataSource" customModule="Evergreen" customModuleProvider="target"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="62" y="394"/>
|
||||
</scene>
|
||||
|
@ -650,7 +654,7 @@
|
|||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="cJ9-6s-66u" customClass="DetailContainerView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="xI5-lx-RD8" customClass="DetailStatusBarView" customModule="Evergreen" customModuleProvider="target">
|
||||
<customView hidden="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xI5-lx-RD8" customClass="DetailStatusBarView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="6" y="2" width="12" height="20"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="850" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dim-ed-Dcz" userLabel="URL Label">
|
||||
|
@ -697,11 +701,61 @@
|
|||
</customView>
|
||||
<connections>
|
||||
<outlet property="containerView" destination="cJ9-6s-66u" id="gXc-Pz-9sQ"/>
|
||||
<outlet property="noSelectionView" destination="aEc-1k-VmJ" id="KLS-ln-T1K"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="vzM-Vn-mEn" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<customView id="aEc-1k-VmJ" customClass="NoSelectionView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="613" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<box boxType="custom" borderType="none" borderWidth="0.0" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="Zb8-hH-gIZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="613" height="96"/>
|
||||
<view key="contentView" id="Gmh-qL-lA5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="613" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ECt-Wi-zkb">
|
||||
<rect key="frame" x="266" y="40" width="109" height="22"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="No selection" usesSingleLineMode="YES" id="oNn-so-m7W">
|
||||
<font key="font" metaFont="systemSemibold" size="18"/>
|
||||
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1AW-Ay-XS8">
|
||||
<rect key="frame" x="408" y="39" width="154" height="22"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Multiple selection" usesSingleLineMode="YES" id="vhD-gM-NUy">
|
||||
<font key="font" metaFont="systemSemibold" size="18"/>
|
||||
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="1AW-Ay-XS8" firstAttribute="centerY" secondItem="Gmh-qL-lA5" secondAttribute="centerY" constant="-64" id="9uN-K3-d35"/>
|
||||
<constraint firstItem="ECt-Wi-zkb" firstAttribute="centerX" secondItem="Gmh-qL-lA5" secondAttribute="centerX" id="QDc-Y3-Nn4"/>
|
||||
<constraint firstItem="1AW-Ay-XS8" firstAttribute="centerX" secondItem="Gmh-qL-lA5" secondAttribute="centerX" id="QV5-qP-Ys6"/>
|
||||
<constraint firstItem="ECt-Wi-zkb" firstAttribute="centerY" secondItem="Gmh-qL-lA5" secondAttribute="centerY" constant="-64" id="yQr-wJ-pBL"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<color key="borderColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<color key="fillColor" white="0.95999999999999996" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</box>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Zb8-hH-gIZ" firstAttribute="leading" secondItem="aEc-1k-VmJ" secondAttribute="leading" id="5Ay-6o-LZ1"/>
|
||||
<constraint firstItem="Zb8-hH-gIZ" firstAttribute="top" secondItem="aEc-1k-VmJ" secondAttribute="top" id="Wb7-Zp-hj0"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Zb8-hH-gIZ" secondAttribute="trailing" id="cYM-up-kUN"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Zb8-hH-gIZ" secondAttribute="bottom" id="gCy-5R-aK2"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="multipleSelectionLabel" destination="1AW-Ay-XS8" id="gK2-Ya-Dz7"/>
|
||||
<outlet property="noSelectionLabel" destination="ECt-Wi-zkb" id="Dvq-DU-MxC"/>
|
||||
</connections>
|
||||
</customView>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="62" y="774"/>
|
||||
<point key="canvasLocation" x="68" y="946"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="mPU-HG-I4u">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14092" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="mPU-HG-I4u">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14092"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
|
@ -31,11 +31,11 @@
|
|||
<objects>
|
||||
<viewController title="General" storyboardIdentifier="General" id="iuH-lz-18x" sceneMemberID="viewController">
|
||||
<view key="view" id="WnV-px-wCT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="223"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="98"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ucw-vG-yLt">
|
||||
<rect key="frame" x="98" y="185" width="92" height="17"/>
|
||||
<rect key="frame" x="98" y="60" width="92" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Refresh feeds:" id="F7c-lm-g97">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -44,7 +44,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SFF-mL-yc8">
|
||||
<rect key="frame" x="194" y="179" width="212" height="26"/>
|
||||
<rect key="frame" x="194" y="54" width="212" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Every 30 minutes" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="rZU-Tg-xwo" id="Jwn-HD-eP6">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -64,7 +64,7 @@
|
|||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wsb-Lr-8Q7">
|
||||
<rect key="frame" x="114" y="145" width="76" height="17"/>
|
||||
<rect key="frame" x="114" y="20" width="76" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Web pages:" id="CgU-dE-Qtb">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -73,7 +73,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ubm-Pk-l7x">
|
||||
<rect key="frame" x="194" y="144" width="211" height="18"/>
|
||||
<rect key="frame" x="194" y="19" width="211" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Open in background in browser" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="t0a-LN-rCv">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
@ -93,127 +93,13 @@
|
|||
</binding>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="37V-fn-0pB">
|
||||
<rect key="frame" x="80" y="104" width="110" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Sidebar font size:" id="3fQ-t1-Q7u">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jDZ-h5-3Jf">
|
||||
<rect key="frame" x="194" y="99" width="124" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Medium" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="dnp-1z-Uvr" id="rIw-e4-7Aq">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="o7F-Qd-W5X">
|
||||
<items>
|
||||
<menuItem title="Small" id="W5Y-pQ-VJH"/>
|
||||
<menuItem title="Medium" state="on" id="dnp-1z-Uvr"/>
|
||||
<menuItem title="Large" id="Nxt-uw-D4P"/>
|
||||
<menuItem title="Very Large" id="IzO-QD-xth"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<binding destination="mAF-gO-1PI" name="selectedIndex" keyPath="values.sidebarFontSize" id="HM0-fC-V0e">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
<bool key="NSConditionallySetsEnabled" value="NO"/>
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<bool key="NSRaisesForNotApplicableKeys" value="NO"/>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jZa-qi-vJE">
|
||||
<rect key="frame" x="75" y="63" width="115" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Timeline font size:" id="W38-nc-va6">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZiV-it-cLB">
|
||||
<rect key="frame" x="194" y="58" width="124" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Medium" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="KBG-NC-Npp" id="GXY-Lm-RXR">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="Mp5-tg-0Sm">
|
||||
<items>
|
||||
<menuItem title="Small" id="DW6-5v-CHd"/>
|
||||
<menuItem title="Medium" state="on" id="KBG-NC-Npp"/>
|
||||
<menuItem title="Large" id="pu9-sC-1Gu"/>
|
||||
<menuItem title="Very Large" id="Dtx-X2-XO6"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<binding destination="mAF-gO-1PI" name="selectedIndex" keyPath="values.timelineFontSize" id="Aze-zk-HxT">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
<bool key="NSConditionallySetsEnabled" value="NO"/>
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<bool key="NSRaisesForNotApplicableKeys" value="NO"/>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PWb-nl-apa">
|
||||
<rect key="frame" x="87" y="22" width="103" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Article font size:" id="LLW-Wb-ZoV">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yun-nK-7NI">
|
||||
<rect key="frame" x="194" y="17" width="124" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Medium" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="FFd-Jt-Lnz" id="TkT-fo-bT9">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="qqy-w5-kEQ">
|
||||
<items>
|
||||
<menuItem title="Small" id="kum-vW-RC1"/>
|
||||
<menuItem title="Medium" state="on" id="FFd-Jt-Lnz"/>
|
||||
<menuItem title="Large" id="Vym-ki-9jl"/>
|
||||
<menuItem title="Very Large" id="Sk0-Bz-8sN"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<binding destination="mAF-gO-1PI" name="selectedIndex" keyPath="values.articleFontSize" id="1e0-rF-4xY">
|
||||
<dictionary key="options">
|
||||
<bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/>
|
||||
<bool key="NSConditionallySetsEnabled" value="NO"/>
|
||||
<integer key="NSMultipleValuesPlaceholder" value="0"/>
|
||||
<integer key="NSNoSelectionPlaceholder" value="0"/>
|
||||
<integer key="NSNotApplicablePlaceholder" value="0"/>
|
||||
<integer key="NSNullPlaceholder" value="0"/>
|
||||
<bool key="NSRaisesForNotApplicableKeys" value="NO"/>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
</view>
|
||||
</viewController>
|
||||
<customObject id="bSQ-tq-wd3" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<userDefaultsController representsSharedInstance="YES" id="mAF-gO-1PI"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-61" y="475"/>
|
||||
<point key="canvasLocation" x="-61" y="412"/>
|
||||
</scene>
|
||||
<!--Container-->
|
||||
<scene sceneID="fzS-hg-3TF">
|
||||
|
|
|
@ -42,26 +42,18 @@ private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String:
|
|||
extension Article {
|
||||
|
||||
var feed: Feed? {
|
||||
get {
|
||||
return account?.existingFeed(with: feedID)
|
||||
}
|
||||
return account?.existingFeed(with: feedID)
|
||||
}
|
||||
|
||||
var preferredLink: String? {
|
||||
get {
|
||||
return url ?? externalURL
|
||||
}
|
||||
return url ?? externalURL
|
||||
}
|
||||
|
||||
var body: String? {
|
||||
get {
|
||||
return contentHTML ?? contentText ?? summary
|
||||
}
|
||||
return contentHTML ?? contentText ?? summary
|
||||
}
|
||||
|
||||
var logicalDatePublished: Date {
|
||||
get {
|
||||
return datePublished ?? dateModified ?? status.dateArrived
|
||||
}
|
||||
return datePublished ?? dateModified ?? status.dateArrived
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,27 @@
|
|||
//
|
||||
|
||||
import AppKit
|
||||
import Data
|
||||
import Account
|
||||
|
||||
protocol SmallIconProvider {
|
||||
|
||||
var smallIcon: NSImage? { get }
|
||||
}
|
||||
|
||||
extension Feed: SmallIconProvider {
|
||||
|
||||
var smallIcon: NSImage? {
|
||||
if let image = appDelegate.faviconDownloader.favicon(for: self) {
|
||||
return image
|
||||
}
|
||||
return AppImages.genericFeedImage
|
||||
}
|
||||
}
|
||||
|
||||
extension Folder: SmallIconProvider {
|
||||
|
||||
var smallIcon: NSImage? {
|
||||
return NSImage(named: NSImage.Name.folder)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,16 +15,12 @@ import RSCore
|
|||
|
||||
func update() {
|
||||
|
||||
performSelectorCoalesced(#selector(updateBadge), with: nil, delay: 0.01)
|
||||
CoalescingQueue.standard.add(self, #selector(updateBadge))
|
||||
}
|
||||
|
||||
@objc dynamic func updateBadge() {
|
||||
@objc func updateBadge() {
|
||||
|
||||
guard let appDelegate = appDelegate else {
|
||||
return
|
||||
}
|
||||
|
||||
let unreadCount = appDelegate.unreadCount
|
||||
let unreadCount = appDelegate?.unreadCount ?? 0
|
||||
let label = unreadCount > 0 ? "\(unreadCount)" : ""
|
||||
NSApplication.shared.dockTile.badgeLabel = label
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="z5E-aV-xMb">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14092" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="z5E-aV-xMb">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14092"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
|
@ -9,12 +9,12 @@
|
|||
<scene sceneID="joN-Oe-nep">
|
||||
<objects>
|
||||
<windowController showSeguePresentationStyle="single" id="z5E-aV-xMb" customClass="FeedListWindowController" customModule="Evergreen" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<window key="window" identifier="feedDirectoryWindow" title="Feed Directory" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="FeedDirectoryWindow" animationBehavior="default" id="Ty3-Oi-cUp">
|
||||
<window key="window" identifier="feedDirectoryWindow" title="Feed Directory" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" tabbingMode="disallowed" id="Ty3-Oi-cUp">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="425" y="461" width="480" height="600"/>
|
||||
<rect key="contentRect" x="425" y="461" width="300" height="600"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||
<value key="minSize" type="size" width="480" height="480"/>
|
||||
<value key="minSize" type="size" width="300" height="480"/>
|
||||
<value key="maxSize" type="size" width="768" height="2048"/>
|
||||
<contentBorderThickness minY="32"/>
|
||||
<connections>
|
||||
|
@ -22,118 +22,35 @@
|
|||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<segue destination="ju5-jM-WKk" kind="relationship" relationship="window.shadowedContentViewController" id="vHc-9b-sEw"/>
|
||||
<segue destination="QX3-Wg-cdZ" kind="relationship" relationship="window.shadowedContentViewController" id="LR5-Ry-Pi6"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="l74-pi-xxL" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-828" y="-22"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="b5P-LI-56y">
|
||||
<objects>
|
||||
<viewController id="ju5-jM-WKk" sceneMemberID="viewController">
|
||||
<view key="view" id="46a-ok-dQm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<containerView translatesAutoresizingMaskIntoConstraints="NO" id="YFk-Bu-Nnc">
|
||||
<rect key="frame" x="0.0" y="32" width="480" height="238"/>
|
||||
<connections>
|
||||
<segue destination="d1q-Ba-ygg" kind="embed" id="07r-H9-ueY"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="xKq-9P-ibo" customClass="FeedListControlsView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="32"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bv8-Eq-0cT">
|
||||
<rect key="frame" x="20" y="3" width="120" height="25"/>
|
||||
<buttonCell key="cell" type="roundTextured" title="Add to Feeds" bezelStyle="texturedRounded" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="0gj-Cw-KCE">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ohy-a3-XkT">
|
||||
<rect key="frame" x="148" y="3" width="120" height="25"/>
|
||||
<buttonCell key="cell" type="roundTextured" title="Open Home Page" bezelStyle="texturedRounded" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="hom-If-fbd">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Ohy-a3-XkT" firstAttribute="width" secondItem="Bv8-Eq-0cT" secondAttribute="width" id="1Kl-sg-GHU"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Ohy-a3-XkT" secondAttribute="trailing" constant="20" symbolic="YES" id="2Py-y7-lRy"/>
|
||||
<constraint firstItem="Ohy-a3-XkT" firstAttribute="leading" secondItem="Bv8-Eq-0cT" secondAttribute="trailing" constant="8" symbolic="YES" id="5Og-09-12P"/>
|
||||
<constraint firstItem="Ohy-a3-XkT" firstAttribute="centerY" secondItem="xKq-9P-ibo" secondAttribute="centerY" id="7Yu-Ab-dfe"/>
|
||||
<constraint firstItem="Bv8-Eq-0cT" firstAttribute="leading" secondItem="xKq-9P-ibo" secondAttribute="leading" constant="20" symbolic="YES" id="Iph-MK-E6k"/>
|
||||
<constraint firstAttribute="height" constant="32" id="dH4-pR-OmT"/>
|
||||
<constraint firstItem="Bv8-Eq-0cT" firstAttribute="centerY" secondItem="xKq-9P-ibo" secondAttribute="centerY" id="qgr-sL-IFQ"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="YFk-Bu-Nnc" firstAttribute="top" secondItem="46a-ok-dQm" secondAttribute="top" id="71x-bl-RZk"/>
|
||||
<constraint firstItem="YFk-Bu-Nnc" firstAttribute="leading" secondItem="46a-ok-dQm" secondAttribute="leading" id="7lT-YG-EN5"/>
|
||||
<constraint firstAttribute="trailing" secondItem="YFk-Bu-Nnc" secondAttribute="trailing" id="LZY-8D-poG"/>
|
||||
<constraint firstAttribute="trailing" secondItem="xKq-9P-ibo" secondAttribute="trailing" id="cYk-h2-Zcf"/>
|
||||
<constraint firstAttribute="bottom" secondItem="xKq-9P-ibo" secondAttribute="bottom" id="uIU-wj-1Wk"/>
|
||||
<constraint firstItem="xKq-9P-ibo" firstAttribute="leading" secondItem="46a-ok-dQm" secondAttribute="leading" id="vhS-uE-V8H"/>
|
||||
<constraint firstItem="xKq-9P-ibo" firstAttribute="top" secondItem="YFk-Bu-Nnc" secondAttribute="bottom" id="yB2-aB-byH"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<customObject id="ANr-fb-gv3" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-989" y="535"/>
|
||||
</scene>
|
||||
<!--Feed List Split View Controller-->
|
||||
<scene sceneID="E8t-Vq-sKF">
|
||||
<objects>
|
||||
<splitViewController identifier="FeedListSplitViewRestorationID" storyboardIdentifier="FeedListSplitViewStoryboardID" showSeguePresentationStyle="single" id="d1q-Ba-ygg" customClass="FeedListSplitViewController" customModule="Evergreen" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<splitViewItems>
|
||||
<splitViewItem canCollapse="YES" holdingPriority="260" behavior="sidebar" id="ae2-iY-wHE"/>
|
||||
<splitViewItem holdingPriority="255" behavior="contentList" id="GY8-gt-RfC"/>
|
||||
</splitViewItems>
|
||||
<splitView key="splitView" identifier="feedListSplitViewIdentifier" autosaveName="FeedListSplitViewAutosave" dividerStyle="thin" vertical="YES" id="hA6-5F-1ud">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="d1q-Ba-ygg" id="sZG-eZ-Txl"/>
|
||||
</connections>
|
||||
</splitView>
|
||||
<connections>
|
||||
<outlet property="splitView" destination="hA6-5F-1ud" id="Arf-Fq-2x6"/>
|
||||
<segue destination="QX3-Wg-cdZ" kind="relationship" relationship="splitItems" id="zwC-8l-e4m"/>
|
||||
<segue destination="5oM-Nv-I7I" kind="relationship" relationship="splitItems" id="d9f-34-ExA"/>
|
||||
</connections>
|
||||
</splitViewController>
|
||||
<customObject id="RVJ-c8-HXT" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-974" y="962"/>
|
||||
<point key="canvasLocation" x="-1275" y="-36"/>
|
||||
</scene>
|
||||
<!--Feed List View Controller-->
|
||||
<scene sceneID="TKm-CD-zMs">
|
||||
<objects>
|
||||
<viewController id="QX3-Wg-cdZ" customClass="FeedListViewController" customModule="Evergreen" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="vp3-VV-Mzw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="285" height="300"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="328" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rir-se-YCO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="285" height="300"/>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="27" horizontalPageScroll="10" verticalLineScroll="27" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rir-se-YCO">
|
||||
<rect key="frame" x="0.0" y="32" width="328" height="268"/>
|
||||
<clipView key="contentView" drawsBackground="NO" id="vli-sv-HLg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="285" height="300"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="328" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" rowSizeStyle="medium" viewBased="YES" floatsGroupRows="NO" indentationPerLevel="23" outlineTableColumn="Ytm-dS-0WJ" id="Hxu-8i-6rp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="285" height="300"/>
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" autosaveColumns="NO" rowHeight="24" rowSizeStyle="medium" viewBased="YES" floatsGroupRows="NO" indentationPerLevel="27" outlineTableColumn="Ytm-dS-0WJ" id="Hxu-8i-6rp" customClass="FeedListOutlineView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="328" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="intercellSpacing" width="2" height="3"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="" width="282" minWidth="16" maxWidth="1000" id="Ytm-dS-0WJ">
|
||||
<tableColumn identifier="" width="326" minWidth="16" maxWidth="1000" id="Ytm-dS-0WJ">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -147,11 +64,11 @@
|
|||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="HeaderCell" id="Do1-0K-GCV">
|
||||
<rect key="frame" x="1" y="1" width="282" height="17"/>
|
||||
<rect key="frame" x="1" y="1" width="326" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vm6-1A-a5G">
|
||||
<rect key="frame" x="0.0" y="1" width="282" height="14"/>
|
||||
<rect key="frame" x="0.0" y="1" width="326" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="HEADER CELL" id="eL0-h5-mFm">
|
||||
<font key="font" metaFont="smallSystemBold"/>
|
||||
|
@ -165,7 +82,7 @@
|
|||
</connections>
|
||||
</tableCellView>
|
||||
<tableCellView identifier="FeedListCell" id="M2x-Bb-n1x" customClass="SidebarCell" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="1" y="20" width="282" height="17"/>
|
||||
<rect key="frame" x="1" y="21" width="326" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iAS-DV-SKL">
|
||||
|
@ -212,114 +129,61 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<customView horizontalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="xKq-9P-ibo">
|
||||
<rect key="frame" x="20" y="0.0" width="288" height="32"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bv8-Eq-0cT">
|
||||
<rect key="frame" x="20" y="3" width="120" height="25"/>
|
||||
<buttonCell key="cell" type="roundTextured" title="Add to Feeds" bezelStyle="texturedRounded" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="0gj-Cw-KCE">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="addToFeeds:" target="MKk-xD-0Fh" id="43Z-VU-PTO"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ohy-a3-XkT">
|
||||
<rect key="frame" x="148" y="3" width="120" height="25"/>
|
||||
<buttonCell key="cell" type="roundTextured" title="Open Home Page" bezelStyle="texturedRounded" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="hom-If-fbd">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="openHomePage:" target="MKk-xD-0Fh" id="vJr-gf-SMd"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Ohy-a3-XkT" firstAttribute="width" secondItem="Bv8-Eq-0cT" secondAttribute="width" id="1Kl-sg-GHU"/>
|
||||
<constraint firstItem="Ohy-a3-XkT" firstAttribute="leading" secondItem="Bv8-Eq-0cT" secondAttribute="trailing" constant="8" symbolic="YES" id="5Og-09-12P"/>
|
||||
<constraint firstItem="Ohy-a3-XkT" firstAttribute="centerY" secondItem="xKq-9P-ibo" secondAttribute="centerY" id="7Yu-Ab-dfe"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Ohy-a3-XkT" secondAttribute="trailing" constant="20" symbolic="YES" id="ACJ-M3-QR1"/>
|
||||
<constraint firstItem="Bv8-Eq-0cT" firstAttribute="leading" secondItem="xKq-9P-ibo" secondAttribute="leading" constant="20" symbolic="YES" id="Iph-MK-E6k"/>
|
||||
<constraint firstAttribute="height" constant="32" id="dH4-pR-OmT"/>
|
||||
<constraint firstItem="Bv8-Eq-0cT" firstAttribute="centerY" secondItem="xKq-9P-ibo" secondAttribute="centerY" id="qgr-sL-IFQ"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="xKq-9P-ibo" secondAttribute="bottom" id="2cb-L2-JEt"/>
|
||||
<constraint firstAttribute="trailing" secondItem="rir-se-YCO" secondAttribute="trailing" id="7WI-u5-sU6"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rir-se-YCO" secondAttribute="bottom" id="B0Q-Ma-ojH"/>
|
||||
<constraint firstItem="xKq-9P-ibo" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="vp3-VV-Mzw" secondAttribute="leading" constant="20" symbolic="YES" id="I6a-ro-vaw"/>
|
||||
<constraint firstItem="rir-se-YCO" firstAttribute="top" secondItem="vp3-VV-Mzw" secondAttribute="top" id="Koo-lp-EvO"/>
|
||||
<constraint firstItem="xKq-9P-ibo" firstAttribute="centerX" secondItem="vp3-VV-Mzw" secondAttribute="centerX" id="Mv4-A4-oeJ"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xKq-9P-ibo" secondAttribute="trailing" constant="20" symbolic="YES" id="TYr-VP-3lc"/>
|
||||
<constraint firstItem="xKq-9P-ibo" firstAttribute="top" secondItem="rir-se-YCO" secondAttribute="bottom" id="WN7-F2-8Cn"/>
|
||||
<constraint firstItem="rir-se-YCO" firstAttribute="leading" secondItem="vp3-VV-Mzw" secondAttribute="leading" id="bGY-bf-MdA"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="addToFeedsButton" destination="Bv8-Eq-0cT" id="ZD6-YN-9oa"/>
|
||||
<outlet property="openHomePageButton" destination="Ohy-a3-XkT" id="G55-UY-RQ8"/>
|
||||
<outlet property="outlineView" destination="Hxu-8i-6rp" id="Hm2-on-0JP"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="MKk-xD-0Fh" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-967" y="1488"/>
|
||||
</scene>
|
||||
<!--Timeline View Controller-->
|
||||
<scene sceneID="hOx-Zn-uli">
|
||||
<objects>
|
||||
<viewController id="5oM-Nv-I7I" customClass="TimelineViewController" customModule="Evergreen" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="Ayc-tI-OzD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eGF-yH-oea">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<clipView key="contentView" id="QTo-1K-1u2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowSizeStyle="automatic" viewBased="YES" floatsGroupRows="NO" id="D1H-xQ-37M" customClass="TimelineTableView" customModule="Evergreen" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="" width="116" minWidth="40" maxWidth="1000" id="qm9-Co-lJr">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="hUD-7s-U2u">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="hg5-83-a5Z">
|
||||
<rect key="frame" x="1" y="1" width="116" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cDm-zV-x6N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="116" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="ZY6-YU-rrU">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="cDm-zV-x6N" id="nDu-d9-1w9"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="5oM-Nv-I7I" id="ZmN-vl-ubI"/>
|
||||
<outlet property="delegate" destination="5oM-Nv-I7I" id="lSh-Ow-oO3"/>
|
||||
<outlet property="keyboardDelegate" destination="2ud-3a-p3A" id="nts-O0-WYq"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="pOU-wV-ftq">
|
||||
<rect key="frame" x="1" y="119" width="223" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="zCx-Fz-yEd">
|
||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="eGF-yH-oea" secondAttribute="bottom" id="L2H-cP-qWd"/>
|
||||
<constraint firstAttribute="trailing" secondItem="eGF-yH-oea" secondAttribute="trailing" id="WAK-gU-jrD"/>
|
||||
<constraint firstItem="eGF-yH-oea" firstAttribute="top" secondItem="Ayc-tI-OzD" secondAttribute="top" id="mBn-j8-wXF"/>
|
||||
<constraint firstItem="eGF-yH-oea" firstAttribute="leading" secondItem="Ayc-tI-OzD" secondAttribute="leading" id="xE0-7D-thL"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="tableView" destination="D1H-xQ-37M" id="6DC-Fc-yeT"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="Xw8-6t-9F9" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<customObject id="2ud-3a-p3A" customClass="FeedListTimelineKeyboardDelegate" customModule="Evergreen" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="timelineViewController" destination="5oM-Nv-I7I" id="Xqu-94-MvJ"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-261" y="1477"/>
|
||||
<point key="canvasLocation" x="-1268" y="547"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
|
|
@ -31,9 +31,7 @@ final class FeedListFeed: Hashable, DisplayNameProvider {
|
|||
}
|
||||
|
||||
var nameForDisplay: String { // DisplayNameProvider
|
||||
get {
|
||||
return name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
init(name: String, url: String, homePageURL: String) {
|
||||
|
@ -62,15 +60,17 @@ final class FeedListFeed: Hashable, DisplayNameProvider {
|
|||
|
||||
func downloadIfNeeded() {
|
||||
|
||||
guard let lastDownloadAttemptDate = lastDownloadAttemptDate else {
|
||||
downloadFeed()
|
||||
return
|
||||
}
|
||||
// Not doing feed previews until after 1.0.
|
||||
|
||||
let cutoffDate = Date().addingTimeInterval(-(30 * 60)) // 30 minutes in the past
|
||||
if lastDownloadAttemptDate < cutoffDate {
|
||||
downloadFeed()
|
||||
}
|
||||
// guard let lastDownloadAttemptDate = lastDownloadAttemptDate else {
|
||||
// downloadFeed()
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let cutoffDate = Date().addingTimeInterval(-(30 * 60)) // 30 minutes in the past
|
||||
// if lastDownloadAttemptDate < cutoffDate {
|
||||
// downloadFeed()
|
||||
// }
|
||||
}
|
||||
|
||||
static func ==(lhs: FeedListFeed, rhs: FeedListFeed) -> Bool {
|
||||
|
@ -83,29 +83,29 @@ private extension FeedListFeed {
|
|||
|
||||
func postFeedListFeedDidBecomeAvailableNotification() {
|
||||
|
||||
NotificationCenter.default.post(name: .FeedListFeedDidBecomeAvailable, object: self, userInfo: nil)
|
||||
// NotificationCenter.default.post(name: .FeedListFeedDidBecomeAvailable, object: self, userInfo: nil)
|
||||
}
|
||||
|
||||
func downloadFeed() {
|
||||
|
||||
lastDownloadAttemptDate = Date()
|
||||
guard let feedURL = URL(string: url) else {
|
||||
return
|
||||
}
|
||||
|
||||
downloadUsingCache(feedURL) { (data, response, error) in
|
||||
|
||||
guard let data = data, error == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
let parserData = ParserData(url: self.url, data: data)
|
||||
FeedParser.parse(parserData) { (parsedFeed, error) in
|
||||
|
||||
if let parsedFeed = parsedFeed, parsedFeed.items.count > 0 {
|
||||
self.parsedFeed = parsedFeed
|
||||
}
|
||||
}
|
||||
}
|
||||
// lastDownloadAttemptDate = Date()
|
||||
// guard let feedURL = URL(string: url) else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// downloadUsingCache(feedURL) { (data, response, error) in
|
||||
//
|
||||
// guard let data = data, error == nil else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let parserData = ParserData(url: self.url, data: data)
|
||||
// FeedParser.parse(parserData) { (parsedFeed, error) in
|
||||
//
|
||||
// if let parsedFeed = parsedFeed, parsedFeed.items.count > 0 {
|
||||
// self.parsedFeed = parsedFeed
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,7 @@ final class FeedListFolder: Hashable, DisplayNameProvider {
|
|||
let hashValue: Int
|
||||
|
||||
var nameForDisplay: String { // DisplayNameProvider
|
||||
get {
|
||||
return name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
init(name: String, feeds: Set<FeedListFeed>) {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// FeedListOutlineView.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/11/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import RSTree
|
||||
|
||||
final class FeedListOutlineView: NSOutlineView {
|
||||
|
||||
override func frameOfCell(atColumn column: Int, row: Int) -> NSRect {
|
||||
|
||||
// Adjust top-level cells — they were too close to the disclosure indicator.
|
||||
|
||||
var frame = super.frameOfCell(atColumn: column, row: row)
|
||||
|
||||
let node = item(atRow: row) as! Node
|
||||
guard let parentNode = node.parent, parentNode.isRoot else {
|
||||
return frame
|
||||
}
|
||||
|
||||
let adjustment: CGFloat = 4.0
|
||||
frame.origin.x += adjustment
|
||||
frame.size.width -= adjustment
|
||||
return frame
|
||||
}
|
||||
}
|
|
@ -23,12 +23,26 @@ struct FeedListUserInfoKey {
|
|||
final class FeedListViewController: NSViewController {
|
||||
|
||||
@IBOutlet var outlineView: NSOutlineView!
|
||||
@IBOutlet var openHomePageButton: NSButton!
|
||||
@IBOutlet var addToFeedsButton: NSButton!
|
||||
|
||||
private var sidebarCellAppearance: SidebarCellAppearance!
|
||||
private let treeControllerDelegate = FeedListTreeControllerDelegate()
|
||||
lazy var treeController: TreeController = {
|
||||
TreeController(delegate: treeControllerDelegate)
|
||||
}()
|
||||
|
||||
private var selectedNodes: [Node] {
|
||||
if let nodes = outlineView.selectedItems as? [Node] {
|
||||
return nodes
|
||||
}
|
||||
return [Node]()
|
||||
}
|
||||
|
||||
private var selectedObjects: [AnyObject] {
|
||||
return selectedNodes.representedObjects()
|
||||
}
|
||||
|
||||
// MARK: NSViewController
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
@ -38,6 +52,7 @@ final class FeedListViewController: NSViewController {
|
|||
sidebarCellAppearance = SidebarCellAppearance(theme: appDelegate.currentTheme, fontSize: AppDefaults.shared.sidebarFontSize)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
outlineView.needsLayout = true
|
||||
updateUI()
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
@ -48,6 +63,23 @@ final class FeedListViewController: NSViewController {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
extension FeedListViewController {
|
||||
|
||||
@IBAction func openHomePage(_ sender: Any?) {
|
||||
|
||||
guard let homePageURL = singleSelectedHomePageURL() else {
|
||||
return
|
||||
}
|
||||
Browser.open(homePageURL, inBackground: false)
|
||||
}
|
||||
|
||||
@IBAction func addToFeeds(_ sender: Any?) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSOutlineViewDataSource
|
||||
|
||||
extension FeedListViewController: NSOutlineViewDataSource {
|
||||
|
@ -92,6 +124,8 @@ extension FeedListViewController: NSOutlineViewDelegate {
|
|||
|
||||
func outlineViewSelectionDidChange(_ notification: Notification) {
|
||||
|
||||
updateUI()
|
||||
|
||||
let selectedRow = self.outlineView.selectedRow
|
||||
|
||||
if selectedRow < 0 || selectedRow == NSNotFound {
|
||||
|
@ -103,8 +137,11 @@ extension FeedListViewController: NSOutlineViewDelegate {
|
|||
postSidebarSelectionDidChangeNotification(selectedNode.representedObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func configure(_ cell: SidebarCell, _ node: Node) {
|
||||
private extension FeedListViewController {
|
||||
|
||||
func configure(_ cell: SidebarCell, _ node: Node) {
|
||||
|
||||
cell.cellAppearance = sidebarCellAppearance
|
||||
cell.objectValue = node
|
||||
|
@ -122,7 +159,7 @@ extension FeedListViewController: NSOutlineViewDelegate {
|
|||
if let image = appDelegate.faviconDownloader.favicon(withHomePageURL: feed.homePageURL) {
|
||||
return image
|
||||
}
|
||||
return appDelegate.genericFeedImage
|
||||
return AppImages.genericFeedImage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -134,9 +171,6 @@ extension FeedListViewController: NSOutlineViewDelegate {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private extension FeedListViewController {
|
||||
|
||||
func nodeForRow(_ row: Int) -> Node? {
|
||||
|
||||
|
@ -176,4 +210,37 @@ private extension FeedListViewController {
|
|||
|
||||
NotificationCenter.default.post(name: .FeedListSidebarSelectionDidChange, object: self, userInfo: userInfo)
|
||||
}
|
||||
|
||||
func updateUI() {
|
||||
|
||||
updateButtons()
|
||||
}
|
||||
|
||||
func updateButtons() {
|
||||
|
||||
let objects = selectedObjects
|
||||
|
||||
if objects.isEmpty {
|
||||
openHomePageButton.isEnabled = false
|
||||
addToFeedsButton.isEnabled = false
|
||||
return
|
||||
}
|
||||
|
||||
addToFeedsButton.isEnabled = true
|
||||
|
||||
if let _ = singleSelectedHomePageURL() {
|
||||
openHomePageButton.isEnabled = true
|
||||
}
|
||||
else {
|
||||
openHomePageButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
func singleSelectedHomePageURL() -> String? {
|
||||
|
||||
guard selectedObjects.count == 1, let homePageURL = (selectedObjects.first! as? FeedListFeed)?.homePageURL, !homePageURL.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
return homePageURL
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,17 +10,12 @@ import AppKit
|
|||
|
||||
class FeedListWindowController : NSWindowController {
|
||||
|
||||
|
||||
override func windowDidLoad() {
|
||||
|
||||
}
|
||||
|
||||
@IBAction func addToFeeds(_ sender: AnyObject) {
|
||||
|
||||
}
|
||||
|
||||
@IBAction func openHomePage(_ sender: AnyObject) {
|
||||
// window!.appearance = NSAppearance(named: .vibrantDark)
|
||||
|
||||
let windowAutosaveName = NSWindow.FrameAutosaveName(rawValue: "FeedDirectoryWindow")
|
||||
window?.setFrameUsingName(windowAutosaveName, force: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
import AppKit
|
||||
|
||||
// Unused, at least for now.
|
||||
|
||||
@objc final class FeedListControlsView: NSView {
|
||||
|
||||
@IBOutlet var addToFeedsButton: NSButton!
|
||||
@IBOutlet var openHomePageButton: NSButton!
|
||||
}
|
|
@ -17,13 +17,13 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0d35</string>
|
||||
<string>1.0d40</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>522</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2017 Ranchero Software, LLC. All rights reserved.</string>
|
||||
<string>Copyright © 2017-2018 Ranchero Software, LLC. All rights reserved.</string>
|
||||
<key>NSMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
|
|
@ -112,7 +112,7 @@ private extension FeedInspectorViewController {
|
|||
return
|
||||
}
|
||||
|
||||
imageView?.image = appDelegate.genericFeedImage
|
||||
imageView?.image = AppImages.genericFeedImage
|
||||
}
|
||||
|
||||
func updateName() {
|
||||
|
|
|
@ -32,12 +32,6 @@ final class InspectorWindowController: NSWindowController {
|
|||
}
|
||||
}
|
||||
|
||||
var isOpen: Bool {
|
||||
get {
|
||||
return isWindowLoaded && window!.isVisible
|
||||
}
|
||||
}
|
||||
|
||||
private var inspectors: [InspectorViewController]!
|
||||
|
||||
private var currentInspector: InspectorViewController! {
|
||||
|
|
|
@ -126,14 +126,12 @@ class AddFeedController: AddFeedWindowControllerDelegate, FeedFinderDelegate {
|
|||
private extension AddFeedController {
|
||||
|
||||
var urlStringFromPasteboard: String? {
|
||||
get {
|
||||
if let urlString = NSPasteboard.rs_urlString(from: NSPasteboard.general) {
|
||||
return urlString.rs_normalizedURL()
|
||||
}
|
||||
return nil
|
||||
if let urlString = NSPasteboard.rs_urlString(from: NSPasteboard.general) {
|
||||
return urlString.rs_normalizedURL()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
struct AccountAndFolderSpecifier {
|
||||
let account: Account
|
||||
let folder: Folder?
|
||||
|
|
|
@ -33,14 +33,12 @@ class AddFeedWindowController : NSWindowController {
|
|||
fileprivate var folderTreeController: TreeController!
|
||||
|
||||
private var userEnteredTitle: String? {
|
||||
get {
|
||||
var s = nameTextField.stringValue
|
||||
s = s.rs_stringWithCollapsedWhitespace()
|
||||
if s.isEmpty {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
var s = nameTextField.stringValue
|
||||
s = s.rs_stringWithCollapsedWhitespace()
|
||||
if s.isEmpty {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var hostWindow: NSWindow!
|
||||
|
@ -87,12 +85,12 @@ class AddFeedWindowController : NSWindowController {
|
|||
|
||||
// MARK: Actions
|
||||
|
||||
@IBAction func cancel(_ sender: AnyObject) {
|
||||
@IBAction func cancel(_ sender: Any?) {
|
||||
|
||||
cancelSheet()
|
||||
}
|
||||
|
||||
@IBAction func addFeed(_ sender: AnyObject) {
|
||||
@IBAction func addFeed(_ sender: Any?) {
|
||||
|
||||
let urlString = urlTextField.stringValue
|
||||
let normalizedURLString = (urlString as NSString).rs_normalizedURL()
|
||||
|
@ -109,7 +107,7 @@ class AddFeedWindowController : NSWindowController {
|
|||
delegate?.addFeedWindowController(self, userEnteredURL: url, userEnteredTitle: userEnteredTitle, container: selectedContainer()!)
|
||||
}
|
||||
|
||||
@IBAction func localShowFeedList(_ sender: AnyObject) {
|
||||
@IBAction func localShowFeedList(_ sender: Any?) {
|
||||
|
||||
NSApplication.shared.sendAction(NSSelectorFromString("showFeedList:"), to: nil, from: sender)
|
||||
hostWindow.endSheet(window!, returnCode: NSApplication.ModalResponse.continue)
|
||||
|
|
|
@ -68,12 +68,12 @@ class AddFolderWindowController : NSWindowController {
|
|||
|
||||
// MARK: Actions
|
||||
|
||||
@IBAction func cancel(_ sender: AnyObject) {
|
||||
@IBAction func cancel(_ sender: Any?) {
|
||||
|
||||
hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel)
|
||||
}
|
||||
|
||||
@IBAction func addFolder(_ sender: AnyObject) {
|
||||
@IBAction func addFolder(_ sender: Any?) {
|
||||
|
||||
hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK)
|
||||
}
|
||||
|
|
|
@ -12,14 +12,30 @@ import RSCore
|
|||
import Data
|
||||
import RSWeb
|
||||
|
||||
final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDelegate {
|
||||
final class DetailViewController: NSViewController, WKUIDelegate {
|
||||
|
||||
@IBOutlet var containerView: DetailContainerView!
|
||||
@IBOutlet var noSelectionView: NoSelectionView!
|
||||
|
||||
var webview: DetailWebView!
|
||||
var noSelectionView: NoSelectionView!
|
||||
|
||||
var article: Article? {
|
||||
var articles: [Article]? {
|
||||
didSet {
|
||||
if let articles = articles, articles.count == 1 {
|
||||
article = articles.first!
|
||||
return
|
||||
}
|
||||
article = nil
|
||||
if let _ = articles {
|
||||
noSelectionView.showMultipleSelection()
|
||||
}
|
||||
else {
|
||||
noSelectionView.showNoSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var article: Article? {
|
||||
didSet {
|
||||
reloadHTML()
|
||||
showOrHideWebView()
|
||||
|
@ -62,8 +78,6 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
|
|||
webview.customUserAgent = userAgent
|
||||
}
|
||||
|
||||
noSelectionView = NoSelectionView(frame: self.view.bounds)
|
||||
|
||||
containerView.viewController = self
|
||||
|
||||
showOrHideWebView()
|
||||
|
@ -91,7 +105,7 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
|
|||
webview.scrollPageDown(sender)
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func timelineSelectionDidChange(_ notification: Notification) {
|
||||
|
||||
|
@ -102,8 +116,8 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
|
|||
return
|
||||
}
|
||||
|
||||
let timelineArticle = userInfo[UserInfoKey.article] as? Article
|
||||
article = timelineArticle
|
||||
let timelineArticles = userInfo[UserInfoKey.articles] as? ArticleArray
|
||||
articles = timelineArticles
|
||||
}
|
||||
|
||||
func viewWillStartLiveResize() {
|
||||
|
@ -115,56 +129,30 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
|
|||
|
||||
webview.evaluateJavaScript("document.body.style.overflow = 'visible';", completionHandler: nil)
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
}
|
||||
|
||||
private func reloadHTML() {
|
||||
// MARK: - WKNavigationDelegate
|
||||
|
||||
if let article = article {
|
||||
let articleRenderer = ArticleRenderer(article: article, style: ArticleStylesManager.shared.currentStyle)
|
||||
webview.loadHTMLString(articleRenderer.html, baseURL: articleRenderer.baseURL)
|
||||
}
|
||||
else {
|
||||
webview.loadHTMLString("", baseURL: nil)
|
||||
}
|
||||
}
|
||||
extension DetailViewController: WKNavigationDelegate {
|
||||
|
||||
private func showOrHideWebView() {
|
||||
|
||||
if let _ = article {
|
||||
switchToView(webview)
|
||||
}
|
||||
else {
|
||||
switchToView(noSelectionView)
|
||||
}
|
||||
}
|
||||
|
||||
private func switchToView(_ view: NSView) {
|
||||
|
||||
if containerView.contentView == view {
|
||||
return
|
||||
}
|
||||
containerView.contentView = view
|
||||
}
|
||||
|
||||
// MARK: WKNavigationDelegate
|
||||
|
||||
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
|
||||
|
||||
if navigationAction.navigationType == .linkActivated {
|
||||
|
||||
|
||||
if let url = navigationAction.request.url {
|
||||
Browser.open(url.absoluteString)
|
||||
}
|
||||
|
||||
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WKScriptMessageHandler
|
||||
|
||||
extension DetailViewController: WKScriptMessageHandler {
|
||||
|
||||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||
|
@ -200,8 +188,39 @@ extension DetailViewController: WKScriptMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private extension DetailViewController {
|
||||
|
||||
func reloadHTML() {
|
||||
|
||||
if let article = article {
|
||||
let articleRenderer = ArticleRenderer(article: article, style: ArticleStylesManager.shared.currentStyle)
|
||||
webview.loadHTMLString(articleRenderer.html, baseURL: articleRenderer.baseURL)
|
||||
}
|
||||
else {
|
||||
webview.loadHTMLString("", baseURL: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func showOrHideWebView() {
|
||||
|
||||
if let _ = article {
|
||||
switchToView(webview)
|
||||
}
|
||||
else {
|
||||
switchToView(noSelectionView)
|
||||
}
|
||||
}
|
||||
|
||||
func switchToView(_ view: NSView) {
|
||||
|
||||
if containerView.contentView == view {
|
||||
return
|
||||
}
|
||||
containerView.contentView = view
|
||||
}
|
||||
|
||||
func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) {
|
||||
|
||||
let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x"
|
||||
|
@ -222,6 +241,8 @@ private extension DetailViewController {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
final class DetailContainerView: NSView {
|
||||
|
||||
@IBOutlet var detailStatusBarView: DetailStatusBarView!
|
||||
|
@ -270,27 +291,28 @@ final class DetailContainerView: NSView {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
final class NoSelectionView: NSView {
|
||||
|
||||
private var didConfigureLayer = false
|
||||
@IBOutlet var noSelectionLabel: NSTextField!
|
||||
@IBOutlet var multipleSelectionLabel: NSTextField!
|
||||
|
||||
override var wantsUpdateLayer: Bool {
|
||||
return true
|
||||
func showMultipleSelection() {
|
||||
|
||||
noSelectionLabel.isHidden = true
|
||||
multipleSelectionLabel.isHidden = false
|
||||
}
|
||||
|
||||
override func updateLayer() {
|
||||
func showNoSelection() {
|
||||
|
||||
guard !didConfigureLayer else {
|
||||
return
|
||||
}
|
||||
if let layer = layer {
|
||||
let color = appDelegate.currentTheme.color(forKey: "MainWindow.Detail.noSelectionView.backgroundColor")
|
||||
layer.backgroundColor = color.cgColor
|
||||
didConfigureLayer = true
|
||||
}
|
||||
noSelectionLabel.isHidden = false
|
||||
multipleSelectionLabel.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private struct ScrollInfo {
|
||||
|
||||
let contentHeight: CGFloat
|
||||
|
|
|
@ -9,25 +9,16 @@
|
|||
import AppKit
|
||||
import Data
|
||||
import Account
|
||||
|
||||
private let kWindowFrameKey = "MainWindow"
|
||||
import RSCore
|
||||
|
||||
class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||
|
||||
var isOpen: Bool {
|
||||
return isWindowLoaded && window!.isVisible
|
||||
}
|
||||
@IBOutlet var toolbarDelegate: MainWindowToolbarDelegate?
|
||||
private let sharingServicePickerDelegate = MainWindowSharingServicePickerDelegate()
|
||||
|
||||
var isDisplayingSheet: Bool {
|
||||
if let _ = window?.attachedSheet {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
private let windowAutosaveName = NSWindow.FrameAutosaveName(rawValue: "MainWindow")
|
||||
static var didPositionWindowOnFirstRun = false
|
||||
|
||||
// MARK: NSWindowController
|
||||
|
||||
private let windowAutosaveName = NSWindow.FrameAutosaveName(rawValue: kWindowFrameKey)
|
||||
private var unreadCount: Int = 0 {
|
||||
didSet {
|
||||
if unreadCount != oldValue {
|
||||
|
@ -36,7 +27,11 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
}
|
||||
}
|
||||
|
||||
static var didPositionWindowOnFirstRun = false
|
||||
private var shareToolbarItem: NSToolbarItem? {
|
||||
return window?.toolbar?.existingItem(withIdentifier: .Share)
|
||||
}
|
||||
|
||||
// MARK: - NSWindowController
|
||||
|
||||
override func windowDidLoad() {
|
||||
|
||||
|
@ -62,8 +57,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillTerminate(_:)), name: NSApplication.willTerminateNotification, object: nil)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(appNavigationKeyPressed(_:)), name: .AppNavigationKeyPressed, 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: .AccountRefreshProgressDidChange, object: nil)
|
||||
|
@ -75,40 +68,30 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Sidebar
|
||||
// MARK: - API
|
||||
|
||||
func saveState() {
|
||||
|
||||
// TODO: save width of split view and anything else that should be saved.
|
||||
|
||||
|
||||
}
|
||||
|
||||
func selectedObjectsInSidebar() -> [AnyObject]? {
|
||||
|
||||
return sidebarViewController?.selectedObjects
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func applicationWillTerminate(_ note: Notification) {
|
||||
|
||||
window?.saveFrame(usingName: windowAutosaveName)
|
||||
}
|
||||
|
||||
@objc func appNavigationKeyPressed(_ note: Notification) {
|
||||
|
||||
guard let navigationKey = note.userInfo?[UserInfoKey.navigationKeyPressed] as? Int else {
|
||||
return
|
||||
}
|
||||
guard let contentView = window?.contentView, let view = note.object as? NSView, view.isDescendant(of: contentView) else {
|
||||
return
|
||||
}
|
||||
|
||||
if navigationKey == NSRightArrowFunctionKey {
|
||||
handleRightArrowFunctionKey(in: view)
|
||||
}
|
||||
if navigationKey == NSLeftArrowFunctionKey {
|
||||
handleLeftArrowFunctionKey(in: view)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func refreshProgressDidChange(_ note: Notification) {
|
||||
|
||||
performSelectorCoalesced(#selector(MainWindowController.makeToolbarValidate(_:)), with: nil, delay: 0.1)
|
||||
|
||||
CoalescingQueue.standard.add(self, #selector(makeToolbarValidate))
|
||||
}
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
|
@ -118,14 +101,14 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Toolbar
|
||||
// MARK: - Toolbar
|
||||
|
||||
@objc func makeToolbarValidate(_ sender: Any?) {
|
||||
@objc func makeToolbarValidate() {
|
||||
|
||||
window?.toolbar?.validateVisibleItems()
|
||||
}
|
||||
|
||||
// MARK: NSUserInterfaceValidations
|
||||
// MARK: - NSUserInterfaceValidations
|
||||
|
||||
public func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
|
||||
|
@ -145,6 +128,10 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
return canMarkRead()
|
||||
}
|
||||
|
||||
if item.action == #selector(toggleStarred(_:)) {
|
||||
return validateToggleStarred(item)
|
||||
}
|
||||
|
||||
if item.action == #selector(markOlderArticlesAsRead(_:)) {
|
||||
return canMarkOlderArticlesAsRead()
|
||||
}
|
||||
|
@ -185,13 +172,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func showAddFolderWindow(_ sender: Any) {
|
||||
@IBAction func showAddFolderWindow(_ sender: Any?) {
|
||||
|
||||
appDelegate.showAddFolderSheetOnWindow(window!)
|
||||
}
|
||||
|
||||
@IBAction func showAddFeedWindow(_ sender: Any) {
|
||||
@IBAction func showAddFeedWindow(_ sender: Any?) {
|
||||
|
||||
appDelegate.showAddFeedSheetOnWindow(window!, urlString: nil, name: nil)
|
||||
}
|
||||
|
@ -208,14 +194,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
openArticleInBrowser(sender)
|
||||
}
|
||||
|
||||
func makeTimelineViewFirstResponder() {
|
||||
|
||||
guard let window = window, let timelineViewController = timelineViewController else {
|
||||
return
|
||||
}
|
||||
window.makeFirstResponderUnlessDescendantIsFirstResponder(timelineViewController.tableView)
|
||||
}
|
||||
|
||||
@IBAction func nextUnread(_ sender: Any?) {
|
||||
|
||||
guard let timelineViewController = timelineViewController, let sidebarViewController = sidebarViewController else {
|
||||
|
@ -233,18 +211,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
}
|
||||
}
|
||||
|
||||
func goToNextUnreadInTimeline() {
|
||||
|
||||
guard let timelineViewController = timelineViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
if timelineViewController.canGoToNextUnread() {
|
||||
timelineViewController.goToNextUnread()
|
||||
makeTimelineViewFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func markAllAsRead(_ sender: Any?) {
|
||||
|
||||
timelineViewController?.markAllAsRead()
|
||||
|
@ -260,6 +226,11 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
timelineViewController?.markSelectedArticlesAsUnread(sender)
|
||||
}
|
||||
|
||||
@IBAction func toggleStarred(_ sender: Any?) {
|
||||
|
||||
timelineViewController?.toggleStarredStatusForSelectedArticles()
|
||||
}
|
||||
|
||||
@IBAction func markAllAsReadAndGoToNextUnread(_ sender: Any?) {
|
||||
|
||||
markAllAsRead(sender)
|
||||
|
@ -340,67 +311,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
|
||||
let items = selectedArticles.map { ArticlePasteboardWriter(article: $0) }
|
||||
let sharingServicePicker = NSSharingServicePicker(items: items)
|
||||
sharingServicePicker.delegate = self
|
||||
sharingServicePicker.delegate = sharingServicePickerDelegate
|
||||
sharingServicePicker.show(relativeTo: view.bounds, of: view, preferredEdge: .minY)
|
||||
}
|
||||
|
||||
private func canShowShareMenu() -> Bool {
|
||||
|
||||
guard let selectedArticles = selectedArticles else {
|
||||
return false
|
||||
}
|
||||
return !selectedArticles.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSSharingServicePickerDelegate
|
||||
|
||||
extension MainWindowController: NSSharingServicePickerDelegate {
|
||||
|
||||
func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] {
|
||||
|
||||
let sendToServices = appDelegate.sendToCommands.compactMap { (sendToCommand) -> NSSharingService? in
|
||||
|
||||
guard let object = items.first else {
|
||||
return nil
|
||||
}
|
||||
guard sendToCommand.canSendObject(object, selectedText: nil) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let image = sendToCommand.image ?? appDelegate.genericFeedImage ?? NSImage()
|
||||
return NSSharingService(title: sendToCommand.title, image: image, alternateImage: nil) {
|
||||
sendToCommand.sendObject(object, selectedText: nil)
|
||||
}
|
||||
}
|
||||
return proposedServices + sendToServices
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSToolbarDelegate
|
||||
|
||||
extension NSToolbarItem.Identifier {
|
||||
static let Share = NSToolbarItem.Identifier("share")
|
||||
}
|
||||
|
||||
extension MainWindowController: NSToolbarDelegate {
|
||||
|
||||
func toolbarWillAddItem(_ notification: Notification) {
|
||||
|
||||
// The share button should send its action on mouse down, not mouse up.
|
||||
|
||||
guard let item = notification.userInfo?["item"] as? NSToolbarItem else {
|
||||
return
|
||||
}
|
||||
guard item.itemIdentifier == .Share, let button = item.view as? NSButton else {
|
||||
return
|
||||
}
|
||||
|
||||
button.sendAction(on: .leftMouseDown)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Scripting Access
|
||||
|
||||
/*
|
||||
|
@ -468,6 +384,8 @@ private extension MainWindowController {
|
|||
return oneSelectedArticle?.preferredLink
|
||||
}
|
||||
|
||||
// MARK: - Command Validation
|
||||
|
||||
func canGoToNextUnread() -> Bool {
|
||||
|
||||
guard let timelineViewController = timelineViewController, let sidebarViewController = sidebarViewController else {
|
||||
|
@ -492,6 +410,70 @@ private extension MainWindowController {
|
|||
return timelineViewController?.canMarkOlderArticlesAsRead() ?? false
|
||||
}
|
||||
|
||||
func canShowShareMenu() -> Bool {
|
||||
|
||||
guard let selectedArticles = selectedArticles else {
|
||||
return false
|
||||
}
|
||||
return !selectedArticles.isEmpty
|
||||
}
|
||||
|
||||
func validateToggleStarred(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
|
||||
let validationStatus = timelineViewController?.markStarredCommandStatus() ?? .canDoNothing
|
||||
let starring: Bool
|
||||
let result: Bool
|
||||
|
||||
switch validationStatus {
|
||||
case .canMark:
|
||||
starring = true
|
||||
result = true
|
||||
case .canUnmark:
|
||||
starring = false
|
||||
result = true
|
||||
case .canDoNothing:
|
||||
starring = true
|
||||
result = false
|
||||
}
|
||||
|
||||
let commandName = starring ? NSLocalizedString("Mark as Starred", comment: "Command") : NSLocalizedString("Mark as Unstarred", comment: "Command")
|
||||
|
||||
if let toolbarItem = item as? NSToolbarItem {
|
||||
toolbarItem.toolTip = commandName
|
||||
if let button = toolbarItem.view as? NSButton {
|
||||
button.image = NSImage(named: starring ? .star : .unstar)
|
||||
}
|
||||
}
|
||||
|
||||
if let menuItem = item as? NSMenuItem {
|
||||
menuItem.title = commandName
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Misc.
|
||||
|
||||
func goToNextUnreadInTimeline() {
|
||||
|
||||
guard let timelineViewController = timelineViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
if timelineViewController.canGoToNextUnread() {
|
||||
timelineViewController.goToNextUnread()
|
||||
makeTimelineViewFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
func makeTimelineViewFirstResponder() {
|
||||
|
||||
guard let window = window, let timelineViewController = timelineViewController else {
|
||||
return
|
||||
}
|
||||
window.makeFirstResponderUnlessDescendantIsFirstResponder(timelineViewController.tableView)
|
||||
}
|
||||
|
||||
func updateWindowTitle() {
|
||||
|
||||
if unreadCount < 1 {
|
||||
|
@ -501,42 +483,5 @@ private extension MainWindowController {
|
|||
window?.title = "\(appDelegate.appName!) (\(unreadCount))"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Toolbar
|
||||
|
||||
private var shareToolbarItem: NSToolbarItem? {
|
||||
return existingToolbarItem(identifier: .Share)
|
||||
}
|
||||
|
||||
func existingToolbarItem(identifier: NSToolbarItem.Identifier) -> NSToolbarItem? {
|
||||
|
||||
guard let toolbarItems = window?.toolbar?.items else {
|
||||
return nil
|
||||
}
|
||||
for toolbarItem in toolbarItems {
|
||||
if toolbarItem.itemIdentifier == identifier {
|
||||
return toolbarItem
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Navigation
|
||||
|
||||
func handleRightArrowFunctionKey(in view: NSView) {
|
||||
|
||||
guard let outlineView = sidebarViewController?.outlineView, view === outlineView, let timelineViewController = timelineViewController else {
|
||||
return
|
||||
}
|
||||
timelineViewController.focus()
|
||||
}
|
||||
|
||||
func handleLeftArrowFunctionKey(in view: NSView) {
|
||||
|
||||
guard let timelineView = timelineViewController?.tableView, view === timelineView, let sidebarViewController = sidebarViewController else {
|
||||
return
|
||||
}
|
||||
sidebarViewController.focus()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// MainWindowSharingServicePickerDelegate.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/17/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
@objc final class MainWindowSharingServicePickerDelegate: NSObject, NSSharingServicePickerDelegate {
|
||||
|
||||
func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] {
|
||||
|
||||
let sendToServices = appDelegate.sendToCommands.compactMap { (sendToCommand) -> NSSharingService? in
|
||||
|
||||
guard let object = items.first else {
|
||||
return nil
|
||||
}
|
||||
guard sendToCommand.canSendObject(object, selectedText: nil) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let image = sendToCommand.image ?? AppImages.genericFeedImage ?? NSImage()
|
||||
return NSSharingService(title: sendToCommand.title, image: image, alternateImage: nil) {
|
||||
sendToCommand.sendObject(object, selectedText: nil)
|
||||
}
|
||||
}
|
||||
return proposedServices + sendToServices
|
||||
}
|
||||
}
|
|
@ -9,12 +9,10 @@
|
|||
import AppKit
|
||||
|
||||
class MainWindowSplitView: NSSplitView {
|
||||
|
||||
private let splitViewDividerColor = NSColor(calibratedWhite: 0.65, alpha: 1.0)
|
||||
|
||||
|
||||
private let splitViewDividerColor = NSColor(calibratedWhite: 0.60, alpha: 1.0)
|
||||
|
||||
override var dividerColor: NSColor {
|
||||
get {
|
||||
return splitViewDividerColor
|
||||
}
|
||||
return splitViewDividerColor
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// MainWindowToolbarDelegate.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/17/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
extension NSToolbarItem.Identifier {
|
||||
static let Share = NSToolbarItem.Identifier("share")
|
||||
}
|
||||
|
||||
@objc final class MainWindowToolbarDelegate: NSObject, NSToolbarDelegate {
|
||||
|
||||
func toolbarWillAddItem(_ notification: Notification) {
|
||||
|
||||
// The share button should send its action on mouse down, not mouse up.
|
||||
|
||||
guard let item = notification.userInfo?["item"] as? NSToolbarItem else {
|
||||
return
|
||||
}
|
||||
guard item.itemIdentifier == .Share, let button = item.view as? NSButton else {
|
||||
return
|
||||
}
|
||||
|
||||
button.sendAction(on: .leftMouseDown)
|
||||
}
|
||||
}
|
|
@ -75,9 +75,7 @@ class SidebarCell : NSTableCellView {
|
|||
}
|
||||
|
||||
override var isFlipped: Bool {
|
||||
get {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
|
|
|
@ -8,6 +8,14 @@
|
|||
|
||||
import AppKit
|
||||
import Data
|
||||
import RSCore
|
||||
|
||||
extension Feed: PasteboardWriterOwner {
|
||||
|
||||
public var pasteboardWriter: NSPasteboardWriting {
|
||||
return FeedPasteboardWriter(feed: self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc final class FeedPasteboardWriter: NSObject, NSPasteboardWriting {
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// FolderPasteboardWriter.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/11/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
extension Folder: PasteboardWriterOwner {
|
||||
|
||||
public var pasteboardWriter: NSPasteboardWriting {
|
||||
return FolderPasteboardWriter(folder: self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc final class FolderPasteboardWriter: NSObject, NSPasteboardWriting {
|
||||
|
||||
private let folder: Folder
|
||||
static let folderUTIInternal = "com.ranchero.evergreen.internal.folder"
|
||||
static let folderUTIInternalType = NSPasteboard.PasteboardType(rawValue: folderUTIInternal)
|
||||
|
||||
init(folder: Folder) {
|
||||
|
||||
self.folder = folder
|
||||
}
|
||||
|
||||
// MARK: - NSPasteboardWriting
|
||||
|
||||
func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
|
||||
|
||||
return [.string, FolderPasteboardWriter.folderUTIInternalType]
|
||||
}
|
||||
|
||||
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
|
||||
let plist: Any?
|
||||
|
||||
switch type {
|
||||
case .string:
|
||||
plist = folder.nameForDisplay
|
||||
case FolderPasteboardWriter.folderUTIInternalType:
|
||||
plist = internalDictionary()
|
||||
default:
|
||||
plist = nil
|
||||
}
|
||||
|
||||
return plist
|
||||
}
|
||||
}
|
||||
|
||||
private extension FolderPasteboardWriter {
|
||||
|
||||
private struct Key {
|
||||
|
||||
static let name = "name"
|
||||
|
||||
// Internal
|
||||
static let accountID = "accountID"
|
||||
static let folderID = "folderID"
|
||||
}
|
||||
|
||||
func internalDictionary() -> [String: Any] {
|
||||
|
||||
var d = [String: Any]()
|
||||
|
||||
d[Key.folderID] = folder.folderID
|
||||
if let name = folder.name {
|
||||
d[Key.name] = name
|
||||
}
|
||||
if let accountID = folder.account?.accountID {
|
||||
d[Key.accountID] = accountID
|
||||
}
|
||||
|
||||
return d
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -44,12 +44,12 @@ final class RenameWindowController: NSWindowController {
|
|||
|
||||
// MARK: Actions
|
||||
|
||||
@IBAction func cancel(_ sender: AnyObject) {
|
||||
@IBAction func cancel(_ sender: Any?) {
|
||||
|
||||
window?.sheetParent?.endSheet(window!, returnCode: .cancel)
|
||||
}
|
||||
|
||||
@IBAction func rename(_ sender: AnyObject) {
|
||||
@IBAction func rename(_ sender: Any?) {
|
||||
|
||||
guard let representedObject = representedObject else {
|
||||
return
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// SidebarOutlineDataSource.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/12/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import RSTree
|
||||
import Data
|
||||
import RSCore
|
||||
|
||||
@objc final class SidebarOutlineDataSource: NSObject, NSOutlineViewDataSource {
|
||||
|
||||
let treeController: TreeController
|
||||
|
||||
init(treeController: TreeController) {
|
||||
self.treeController = treeController
|
||||
}
|
||||
|
||||
// MARK: - NSOutlineViewDataSource
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
|
||||
return nodeForItem(item as AnyObject?).numberOfChildNodes
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||
|
||||
return nodeForItem(item as AnyObject?).childNodes![index]
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||
|
||||
return nodeForItem(item as AnyObject?).canHaveChildNodes
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
|
||||
|
||||
let node = nodeForItem(item as AnyObject?)
|
||||
return (node.representedObject as? PasteboardWriterOwner)?.pasteboardWriter
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private extension SidebarOutlineDataSource {
|
||||
|
||||
func nodeForItem(_ item: AnyObject?) -> Node {
|
||||
|
||||
if item == nil {
|
||||
return treeController.rootNode
|
||||
}
|
||||
return item as! Node
|
||||
}
|
||||
}
|
|
@ -17,18 +17,27 @@ final class SidebarStatusBarView: NSView {
|
|||
@IBOutlet var progressIndicator: NSProgressIndicator!
|
||||
@IBOutlet var progressLabel: NSTextField!
|
||||
|
||||
private var didConfigureLayer = false
|
||||
|
||||
private var isAnimatingProgress = false {
|
||||
didSet {
|
||||
progressIndicator.isHidden = !isAnimatingProgress
|
||||
progressLabel.isHidden = !isAnimatingProgress
|
||||
}
|
||||
}
|
||||
|
||||
override var isFlipped: Bool {
|
||||
get {
|
||||
return true
|
||||
|
||||
private var progress: CombinedRefreshProgress? = nil {
|
||||
didSet {
|
||||
CoalescingQueue.standard.add(self, #selector(updateUI))
|
||||
}
|
||||
}
|
||||
override var isFlipped: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override var wantsUpdateLayer: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
|
||||
|
@ -42,28 +51,34 @@ final class SidebarStatusBarView: NSView {
|
|||
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
override func updateLayer() {
|
||||
|
||||
@objc dynamic func progressDidChange(_ notification: Notification) {
|
||||
guard let layer = layer, !didConfigureLayer else {
|
||||
return
|
||||
}
|
||||
|
||||
let color = NSColor(calibratedWhite: 0.96, alpha: 1.0)
|
||||
layer.backgroundColor = color.cgColor
|
||||
didConfigureLayer = true
|
||||
}
|
||||
|
||||
@objc func updateUI() {
|
||||
|
||||
guard let progress = progress else {
|
||||
stopProgressIfNeeded()
|
||||
return
|
||||
}
|
||||
|
||||
let progress = AccountManager.shared.combinedRefreshProgress
|
||||
updateProgressIndicator(progress)
|
||||
updateProgressLabel(progress)
|
||||
}
|
||||
|
||||
// MARK: Drawing
|
||||
// MARK: Notifications
|
||||
|
||||
// private let lineColor = NSColor(calibratedWhite: 0.57, alpha: 1.0)
|
||||
//
|
||||
// override func draw(_ dirtyRect: NSRect) {
|
||||
//
|
||||
// let path = NSBezierPath()
|
||||
// path.lineWidth = 1.0
|
||||
// path.move(to: NSPoint(x: NSMinX(bounds), y: NSMinY(bounds) + 0.5))
|
||||
// path.line(to: NSPoint(x: NSMaxX(bounds), y: NSMinY(bounds) + 0.5))
|
||||
// lineColor.set()
|
||||
// path.stroke()
|
||||
// }
|
||||
@objc dynamic func progressDidChange(_ notification: Notification) {
|
||||
|
||||
progress = AccountManager.shared.combinedRefreshProgress
|
||||
}
|
||||
}
|
||||
|
||||
private extension SidebarStatusBarView {
|
||||
|
|
|
@ -19,17 +19,22 @@ extension SidebarViewController {
|
|||
return menuForNoSelection()
|
||||
}
|
||||
|
||||
if objects.count == 1 {
|
||||
if let feed = objects.first as? Feed {
|
||||
return menuForFeed(feed)
|
||||
}
|
||||
if let folder = objects.first as? Folder {
|
||||
return menuForFolder(folder)
|
||||
}
|
||||
return nil
|
||||
if objects.count > 1 {
|
||||
return menuForMultipleObjects(objects)
|
||||
}
|
||||
|
||||
return menuForMultipleObjects(objects)
|
||||
let object = objects.first!
|
||||
|
||||
switch object {
|
||||
case is Feed:
|
||||
return menuForFeed(object as! Feed)
|
||||
case is Folder:
|
||||
return menuForFolder(object as! Folder)
|
||||
case is PseudoFeed:
|
||||
return menuForSmartFeed(object as! PseudoFeed)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,11 +65,7 @@ extension SidebarViewController {
|
|||
}
|
||||
|
||||
let articles = unreadArticles(for: objects)
|
||||
if articles.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markReadCommand)
|
||||
|
@ -160,11 +161,21 @@ private extension SidebarViewController {
|
|||
return menu.numberOfItems > 0 ? menu : nil
|
||||
}
|
||||
|
||||
func menuForSmartFeed(_ smartFeed: PseudoFeed) -> NSMenu? {
|
||||
|
||||
let menu = NSMenu(title: "")
|
||||
|
||||
if smartFeed.unreadCount > 0 {
|
||||
menu.addItem(markAllReadMenuItem([smartFeed]))
|
||||
}
|
||||
return menu.numberOfItems > 0 ? menu : nil
|
||||
}
|
||||
|
||||
func menuForMultipleObjects(_ objects: [Any]) -> NSMenu? {
|
||||
|
||||
guard allObjectsAreFeedsAndOrFolders(objects) else {
|
||||
return nil
|
||||
}
|
||||
// guard allObjectsAreFeedsAndOrFolders(objects) else {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
let menu = NSMenu(title: "")
|
||||
|
||||
|
|
|
@ -20,8 +20,12 @@ import RSCore
|
|||
|
||||
let treeControllerDelegate = SidebarTreeControllerDelegate()
|
||||
lazy var treeController: TreeController = {
|
||||
TreeController(delegate: treeControllerDelegate)
|
||||
return TreeController(delegate: treeControllerDelegate)
|
||||
}()
|
||||
lazy var dataSource: SidebarOutlineDataSource = {
|
||||
return SidebarOutlineDataSource(treeController: treeController)
|
||||
}()
|
||||
|
||||
var undoableCommands = [UndoableCommand]()
|
||||
private var animatingChanges = false
|
||||
private var sidebarCellAppearance: SidebarCellAppearance!
|
||||
|
@ -32,12 +36,13 @@ import RSCore
|
|||
return selectedNodes.representedObjects()
|
||||
}
|
||||
|
||||
//MARK: NSViewController
|
||||
// MARK: - NSViewController
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
sidebarCellAppearance = SidebarCellAppearance(theme: appDelegate.currentTheme, fontSize: AppDefaults.shared.sidebarFontSize)
|
||||
|
||||
outlineView.dataSource = dataSource
|
||||
outlineView.setDraggingSourceOperationMask(.move, forLocal: true)
|
||||
outlineView.setDraggingSourceOperationMask(.copy, forLocal: false)
|
||||
|
||||
|
@ -65,9 +70,9 @@ import RSCore
|
|||
}
|
||||
}
|
||||
|
||||
//MARK: Notifications
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc dynamic func unreadCountDidChange(_ note: Notification) {
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
|
||||
guard let representedObject = note.object else {
|
||||
return
|
||||
|
@ -75,17 +80,17 @@ import RSCore
|
|||
configureUnreadCountForCellsForRepresentedObject(representedObject as AnyObject)
|
||||
}
|
||||
|
||||
@objc dynamic func containerChildrenDidChange(_ note: Notification) {
|
||||
@objc func containerChildrenDidChange(_ note: Notification) {
|
||||
|
||||
rebuildTreeAndReloadDataIfNeeded()
|
||||
}
|
||||
|
||||
@objc dynamic func batchUpdateDidPerform(_ notification: Notification) {
|
||||
@objc func batchUpdateDidPerform(_ notification: Notification) {
|
||||
|
||||
rebuildTreeAndReloadDataIfNeeded()
|
||||
}
|
||||
|
||||
@objc dynamic func userDidAddFeed(_ notification: Notification) {
|
||||
@objc func userDidAddFeed(_ notification: Notification) {
|
||||
|
||||
guard let feed = notification.userInfo?[UserInfoKey.feed] else {
|
||||
return
|
||||
|
@ -114,7 +119,7 @@ import RSCore
|
|||
configureCellsForRepresentedObject(object as AnyObject)
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func delete(_ sender: AnyObject?) {
|
||||
|
||||
|
@ -165,11 +170,16 @@ import RSCore
|
|||
outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.starredFeed, treeController)
|
||||
}
|
||||
|
||||
// MARK: Navigation
|
||||
@IBAction func copy(_ sender: Any?) {
|
||||
|
||||
NSPasteboard.general.copyObjects(selectedObjects)
|
||||
}
|
||||
|
||||
// MARK: - Navigation
|
||||
|
||||
func canGoToNextUnread() -> Bool {
|
||||
|
||||
if let _ = rowContainingNextUnread() {
|
||||
if let _ = nextSelectableRowWithUnreadArticle() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -177,7 +187,7 @@ import RSCore
|
|||
|
||||
func goToNextUnread() {
|
||||
|
||||
guard let row = rowContainingNextUnread() else {
|
||||
guard let row = nextSelectableRowWithUnreadArticle() else {
|
||||
assertionFailure("goToNextUnread called before checking if there is a next unread.")
|
||||
return
|
||||
}
|
||||
|
@ -193,7 +203,7 @@ import RSCore
|
|||
window.makeFirstResponderUnlessDescendantIsFirstResponder(outlineView)
|
||||
}
|
||||
|
||||
// MARK: Contextual Menu
|
||||
// MARK: - Contextual Menu
|
||||
|
||||
func contextualMenuForSelectedObjects() -> NSMenu? {
|
||||
|
||||
|
@ -216,7 +226,7 @@ import RSCore
|
|||
return menu(for: [object])
|
||||
}
|
||||
|
||||
// MARK: NSOutlineViewDelegate
|
||||
// MARK: - NSOutlineViewDelegate
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||
|
||||
|
@ -262,44 +272,20 @@ import RSCore
|
|||
|
||||
func outlineViewSelectionDidChange(_ notification: Notification) {
|
||||
|
||||
// TODO: support multiple selection
|
||||
|
||||
let selectedRow = self.outlineView.selectedRow
|
||||
|
||||
if selectedRow < 0 || selectedRow == NSNotFound {
|
||||
postSidebarSelectionDidChangeNotification(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if let selectedNode = self.outlineView.item(atRow: selectedRow) as? Node {
|
||||
postSidebarSelectionDidChangeNotification([selectedNode.representedObject])
|
||||
}
|
||||
postSidebarSelectionDidChangeNotification(selectedObjects.isEmpty ? nil : selectedObjects)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: NSOutlineViewDataSource
|
||||
// MARK: - NSUserInterfaceValidations
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
|
||||
return nodeForItem(item as AnyObject?).numberOfChildNodes
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||
|
||||
return nodeForItem(item as AnyObject?).childNodes![index]
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||
|
||||
return nodeForItem(item as AnyObject?).canHaveChildNodes
|
||||
}
|
||||
extension SidebarViewController: NSUserInterfaceValidations {
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
|
||||
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
|
||||
let node = nodeForItem(item as AnyObject?)
|
||||
if let feed = node.representedObject as? Feed {
|
||||
return FeedPasteboardWriter(feed: feed)
|
||||
if item.action == #selector(copy(_:)) {
|
||||
return NSPasteboard.general.canCopyAtLeastOneObject(selectedObjects)
|
||||
}
|
||||
return nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,14 +380,24 @@ private extension SidebarViewController {
|
|||
return false
|
||||
}
|
||||
|
||||
func rowContainingNextUnread() -> Int? {
|
||||
|
||||
func rowIsGroupItem(_ row: Int) -> Bool {
|
||||
|
||||
if let node = nodeForRow(row), outlineView.isGroupItem(node) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func nextSelectableRowWithUnreadArticle() -> Int? {
|
||||
|
||||
// Skip group items, because they should never be selected.
|
||||
|
||||
let selectedRow = outlineView.selectedRow
|
||||
let numberOfRows = outlineView.numberOfRows
|
||||
var row = selectedRow + 1
|
||||
|
||||
while (row < numberOfRows) {
|
||||
if rowHasAtLeastOneUnreadArticle(row) {
|
||||
if rowHasAtLeastOneUnreadArticle(row) && !rowIsGroupItem(row) {
|
||||
return row
|
||||
}
|
||||
row += 1
|
||||
|
@ -409,7 +405,7 @@ private extension SidebarViewController {
|
|||
|
||||
row = 0
|
||||
while (row <= selectedRow) {
|
||||
if rowHasAtLeastOneUnreadArticle(row) {
|
||||
if rowHasAtLeastOneUnreadArticle(row) && !rowIsGroupItem(row) {
|
||||
return row
|
||||
}
|
||||
row += 1
|
||||
|
@ -574,20 +570,4 @@ private extension SidebarViewController {
|
|||
}
|
||||
}
|
||||
|
||||
extension Feed: SmallIconProvider {
|
||||
|
||||
var smallIcon: NSImage? {
|
||||
if let image = appDelegate.faviconDownloader.favicon(for: self) {
|
||||
return image
|
||||
}
|
||||
return appDelegate.genericFeedImage
|
||||
}
|
||||
}
|
||||
|
||||
extension Folder: SmallIconProvider {
|
||||
|
||||
var smallIcon: NSImage? {
|
||||
return NSImage(named: NSImage.Name.folder)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,36 +27,30 @@ class UnreadCountView : NSView {
|
|||
}
|
||||
}
|
||||
var unreadCountString: String {
|
||||
get {
|
||||
return unreadCount < 1 ? "" : "\(unreadCount)"
|
||||
}
|
||||
return unreadCount < 1 ? "" : "\(unreadCount)"
|
||||
}
|
||||
|
||||
private var intrinsicContentSizeIsValid = false
|
||||
private var _intrinsicContentSize = NSZeroSize
|
||||
|
||||
override var intrinsicContentSize: NSSize {
|
||||
get {
|
||||
if !intrinsicContentSizeIsValid {
|
||||
var size = NSZeroSize
|
||||
if unreadCount > 0 {
|
||||
size = textSize()
|
||||
size.width += (padding.left + padding.right)
|
||||
size.height += (padding.top + padding.bottom)
|
||||
}
|
||||
_intrinsicContentSize = size
|
||||
intrinsicContentSizeIsValid = true
|
||||
if !intrinsicContentSizeIsValid {
|
||||
var size = NSZeroSize
|
||||
if unreadCount > 0 {
|
||||
size = textSize()
|
||||
size.width += (padding.left + padding.right)
|
||||
size.height += (padding.top + padding.bottom)
|
||||
}
|
||||
return _intrinsicContentSize
|
||||
_intrinsicContentSize = size
|
||||
intrinsicContentSizeIsValid = true
|
||||
}
|
||||
return _intrinsicContentSize
|
||||
}
|
||||
|
||||
override var isFlipped: Bool {
|
||||
get {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
override func invalidateIntrinsicContentSize() {
|
||||
|
||||
intrinsicContentSizeIsValid = false
|
||||
|
|
|
@ -110,6 +110,12 @@ extension Array where Element == Article {
|
|||
|
||||
return anyArticlePassesTest { !$0.status.starred }
|
||||
}
|
||||
|
||||
func unreadArticles() -> [Article]? {
|
||||
|
||||
let articles = self.filter{ !$0.status.read }
|
||||
return articles.isEmpty ? nil : articles
|
||||
}
|
||||
}
|
||||
|
||||
private extension Array where Element == Article {
|
||||
|
|
|
@ -8,6 +8,14 @@
|
|||
|
||||
import AppKit
|
||||
import Data
|
||||
import RSCore
|
||||
|
||||
extension Article: PasteboardWriterOwner {
|
||||
|
||||
public var pasteboardWriter: NSPasteboardWriting {
|
||||
return ArticlePasteboardWriter(article: self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc final class ArticlePasteboardWriter: NSObject, NSPasteboardWriting {
|
||||
|
||||
|
|
|
@ -26,15 +26,22 @@ struct TimelineCellAppearance: Equatable {
|
|||
|
||||
let textColor: NSColor
|
||||
let textFont: NSFont
|
||||
|
||||
|
||||
let textOnlyColor: NSColor
|
||||
let textOnlyFont: NSFont
|
||||
|
||||
let unreadCircleColor: NSColor
|
||||
let unreadCircleDimension: CGFloat
|
||||
let unreadCircleMarginRight: CGFloat
|
||||
|
||||
|
||||
let starDimension: CGFloat
|
||||
|
||||
let gridColor: NSColor
|
||||
let drawsGrid: Bool
|
||||
|
||||
let avatarSize: NSSize
|
||||
let avatarMarginRight: CGFloat
|
||||
let avatarMarginLeft: CGFloat
|
||||
let avatarAdjustmentTop: CGFloat
|
||||
let avatarCornerRadius: CGFloat
|
||||
let showAvatar: Bool
|
||||
|
@ -44,40 +51,48 @@ struct TimelineCellAppearance: Equatable {
|
|||
init(theme: VSTheme, showAvatar: Bool, fontSize: FontSize) {
|
||||
|
||||
let actualFontSize = AppDefaults.actualFontSize(for: fontSize)
|
||||
|
||||
let smallItemFontSize = floor(actualFontSize * 0.95)
|
||||
let largeItemFontSize = floor(actualFontSize * 1.1)
|
||||
|
||||
self.cellPadding = theme.edgeInsets(forKey: "MainWindow.Timeline.cell.padding")
|
||||
|
||||
self.feedNameColor = theme.color(forKey: "MainWindow.Timeline.cell.feedNameColor")
|
||||
self.feedNameFont = NSFont.systemFont(ofSize: actualFontSize)
|
||||
self.feedNameFont = NSFont.systemFont(ofSize: smallItemFontSize)
|
||||
|
||||
self.dateColor = theme.color(forKey: "MainWindow.Timeline.cell.dateColor")
|
||||
let actualDateFontSize = AppDefaults.actualFontSize(for: fontSize)
|
||||
self.dateFont = NSFont.systemFont(ofSize: actualDateFontSize)
|
||||
self.dateFont = NSFont.systemFont(ofSize: smallItemFontSize)
|
||||
self.dateMarginLeft = theme.float(forKey: "MainWindow.Timeline.cell.dateMarginLeft")
|
||||
|
||||
self.titleColor = theme.color(forKey: "MainWindow.Timeline.cell.titleColor")
|
||||
self.titleFont = NSFont.systemFont(ofSize: actualFontSize, weight: NSFont.Weight.bold)
|
||||
self.titleFont = NSFont.systemFont(ofSize: largeItemFontSize, weight: NSFont.Weight.semibold)
|
||||
self.titleBottomMargin = theme.float(forKey: "MainWindow.Timeline.cell.titleMarginBottom")
|
||||
|
||||
self.textColor = theme.color(forKey: "MainWindow.Timeline.cell.textColor")
|
||||
self.textFont = NSFont.systemFont(ofSize: actualFontSize)
|
||||
|
||||
self.textFont = NSFont.systemFont(ofSize: largeItemFontSize)
|
||||
|
||||
self.textOnlyColor = theme.color(forKey: "MainWindow.Timeline.cell.textOnlyColor")
|
||||
self.textOnlyFont = NSFont.systemFont(ofSize: largeItemFontSize)
|
||||
|
||||
self.unreadCircleColor = theme.color(forKey: "MainWindow.Timeline.cell.unreadCircleColor")
|
||||
self.unreadCircleDimension = theme.float(forKey: "MainWindow.Timeline.cell.unreadCircleDimension")
|
||||
self.unreadCircleMarginRight = theme.float(forKey: "MainWindow.Timeline.cell.unreadCircleMarginRight")
|
||||
|
||||
self.starDimension = theme.float(forKey: "MainWindow.Timeline.cell.starDimension")
|
||||
|
||||
self.gridColor = theme.colorWithAlpha(forKey: "MainWindow.Timeline.gridColor")
|
||||
|
||||
self.drawsGrid = theme.bool(forKey: "MainWindow.Timeline.drawsGrid")
|
||||
|
||||
self.avatarSize = theme.size(forKey: "MainWindow.Timeline.cell.avatar")
|
||||
self.avatarMarginRight = theme.float(forKey: "MainWindow.Timeline.cell.avatarMarginRight")
|
||||
self.avatarMarginLeft = theme.float(forKey: "MainWindow.Timeline.cell.avatarMarginLeft")
|
||||
self.avatarAdjustmentTop = theme.float(forKey: "MainWindow.Timeline.cell.avatarAdjustmentTop")
|
||||
self.avatarCornerRadius = theme.float(forKey: "MainWindow.Timeline.cell.avatarCornerRadius")
|
||||
self.showAvatar = showAvatar
|
||||
|
||||
var margin = self.cellPadding.left + self.unreadCircleDimension + self.unreadCircleMarginRight
|
||||
if showAvatar {
|
||||
margin += (self.avatarSize.width + self.avatarMarginRight)
|
||||
}
|
||||
let margin = self.cellPadding.left + self.unreadCircleDimension + self.unreadCircleMarginRight
|
||||
// if showAvatar {
|
||||
// margin += (self.avatarSize.width + self.avatarMarginRight)
|
||||
// }
|
||||
self.boxLeftMargin = margin
|
||||
}
|
||||
|
||||
|
|
|
@ -27,9 +27,10 @@ struct TimelineCellData {
|
|||
let showAvatar: Bool // Make space even when avatar is nil
|
||||
let featuredImage: NSImage? // image from within the article
|
||||
let read: Bool
|
||||
let starred: Bool
|
||||
|
||||
init(article: Article, appearance: TimelineCellAppearance, showFeedName: Bool, feedName: String?, avatar: NSImage?, showAvatar: Bool, featuredImage: NSImage?) {
|
||||
|
||||
|
||||
self.title = timelineTruncatedTitle(article)
|
||||
self.text = timelineTruncatedSummary(article)
|
||||
|
||||
|
@ -72,6 +73,7 @@ struct TimelineCellData {
|
|||
self.featuredImage = featuredImage
|
||||
|
||||
self.read = article.status.read
|
||||
self.starred = article.status.starred
|
||||
}
|
||||
|
||||
init() { //Empty
|
||||
|
@ -88,6 +90,7 @@ struct TimelineCellData {
|
|||
self.avatar = nil
|
||||
self.featuredImage = nil
|
||||
self.read = true
|
||||
self.starred = false
|
||||
}
|
||||
|
||||
static func emptyCache() {
|
||||
|
@ -114,6 +117,6 @@ private func attributedTitleString(_ title: String, _ text: String, _ appearance
|
|||
return NSAttributedString(string: title, attributes: [NSAttributedStringKey.foregroundColor: appearance.titleColor, NSAttributedStringKey.font: appearance.titleFont])
|
||||
}
|
||||
|
||||
return NSAttributedString(string: text, attributes: [NSAttributedStringKey.foregroundColor: appearance.textColor, NSAttributedStringKey.font: appearance.textFont])
|
||||
return NSAttributedString(string: text, attributes: [NSAttributedStringKey.foregroundColor: appearance.textOnlyColor, NSAttributedStringKey.font: appearance.textOnlyFont])
|
||||
}
|
||||
|
||||
|
|
|
@ -18,132 +18,151 @@ struct TimelineCellLayout {
|
|||
let dateRect: NSRect
|
||||
let titleRect: NSRect
|
||||
let unreadIndicatorRect: NSRect
|
||||
let starRect: NSRect
|
||||
let avatarImageRect: NSRect
|
||||
let paddingBottom: CGFloat
|
||||
|
||||
init(width: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, unreadIndicatorRect: NSRect, avatarImageRect: NSRect, paddingBottom: CGFloat) {
|
||||
init(width: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, avatarImageRect: NSRect, paddingBottom: CGFloat) {
|
||||
|
||||
self.width = width
|
||||
self.feedNameRect = feedNameRect
|
||||
self.dateRect = dateRect
|
||||
self.titleRect = titleRect
|
||||
self.unreadIndicatorRect = unreadIndicatorRect
|
||||
self.starRect = starRect
|
||||
self.avatarImageRect = avatarImageRect
|
||||
self.paddingBottom = paddingBottom
|
||||
|
||||
var height = max(0, feedNameRect.maxY)
|
||||
height = max(height, dateRect.maxY)
|
||||
height = max(height, titleRect.maxY)
|
||||
height = max(height, unreadIndicatorRect.maxY)
|
||||
height = max(height, avatarImageRect.maxY)
|
||||
height += paddingBottom
|
||||
self.height = height
|
||||
|
||||
self.height = [feedNameRect, dateRect, titleRect, unreadIndicatorRect, avatarImageRect].maxY() + paddingBottom
|
||||
}
|
||||
|
||||
init(width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) {
|
||||
|
||||
var textBoxRect = TimelineCellLayout.rectForTextBox(appearance, cellData, width)
|
||||
|
||||
let (titleRect, titleLine1Rect) = TimelineCellLayout.rectsForTitle(textBoxRect, cellData)
|
||||
let dateRect = TimelineCellLayout.rectForDate(textBoxRect, titleRect, appearance, cellData)
|
||||
let feedNameRect = TimelineCellLayout.rectForFeedName(textBoxRect, dateRect, appearance, cellData)
|
||||
let unreadIndicatorRect = TimelineCellLayout.rectForUnreadIndicator(appearance, titleLine1Rect)
|
||||
let starRect = TimelineCellLayout.rectForStar(appearance, unreadIndicatorRect)
|
||||
|
||||
textBoxRect.size.height = ceil([titleRect, dateRect, feedNameRect].maxY() - textBoxRect.origin.y)
|
||||
let avatarImageRect = TimelineCellLayout.rectForAvatar(cellData, appearance, textBoxRect, width)
|
||||
|
||||
let paddingBottom = appearance.cellPadding.bottom
|
||||
|
||||
self.init(width: width, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, avatarImageRect: avatarImageRect, paddingBottom: paddingBottom)
|
||||
}
|
||||
|
||||
static func height(for width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> CGFloat {
|
||||
|
||||
let layout = TimelineCellLayout(width: width, cellData: cellData, appearance: appearance)
|
||||
return layout.height
|
||||
}
|
||||
}
|
||||
|
||||
private func rectForDate(_ cellData: TimelineCellData, _ width: CGFloat, _ appearance: TimelineCellAppearance, _ titleRect: NSRect) -> NSRect {
|
||||
|
||||
let renderer = RSSingleLineRenderer(attributedTitle: cellData.attributedDateString)
|
||||
var r = NSZeroRect
|
||||
r.size = renderer.size
|
||||
// MARK: - Calculate Rects
|
||||
|
||||
r.origin.y = NSMaxY(titleRect) + appearance.titleBottomMargin
|
||||
r.origin.x = appearance.boxLeftMargin
|
||||
|
||||
r.size.width = min(width - (r.origin.x + appearance.cellPadding.right), r.size.width)
|
||||
r.size.width = max(r.size.width, 0.0)
|
||||
private extension TimelineCellLayout {
|
||||
|
||||
return r
|
||||
}
|
||||
static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: TimelineCellData, _ width: CGFloat) -> NSRect {
|
||||
|
||||
private func rectForFeedName(_ cellData: TimelineCellData, _ width: CGFloat, _ appearance: TimelineCellAppearance, _ dateRect: NSRect) -> NSRect {
|
||||
|
||||
if !cellData.showFeedName {
|
||||
return NSZeroRect
|
||||
// Returned height is a placeholder. Not needed when this is calculated.
|
||||
|
||||
let textBoxOriginX = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight
|
||||
let textBoxMaxX = floor((width - appearance.cellPadding.right) - (cellData.showAvatar ? appearance.avatarSize.width + appearance.avatarMarginLeft : 0.0))
|
||||
let textBoxWidth = floor(textBoxMaxX - textBoxOriginX)
|
||||
let textBoxRect = NSRect(x: textBoxOriginX, y: appearance.cellPadding.top, width: textBoxWidth, height: 1000000)
|
||||
|
||||
return textBoxRect
|
||||
}
|
||||
|
||||
let renderer = RSSingleLineRenderer(attributedTitle: cellData.attributedFeedName)
|
||||
var r = NSZeroRect
|
||||
r.size = renderer.size
|
||||
r.origin.y = NSMaxY(dateRect) + appearance.titleBottomMargin
|
||||
r.origin.x = appearance.boxLeftMargin
|
||||
|
||||
r.size.width = max(0, width - (r.origin.x + appearance.cellPadding.right))
|
||||
|
||||
return r
|
||||
}
|
||||
static func rectsForTitle(_ textBoxRect: NSRect, _ cellData: TimelineCellData) -> (NSRect, NSRect) {
|
||||
|
||||
//private func rectForFavicon(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ feedNameRect: NSRect, _ unreadIndicatorRect: NSRect) -> NSRect {
|
||||
//
|
||||
// guard let _ = cellData.favicon, cellData.showFeedName else {
|
||||
// return NSZeroRect
|
||||
// }
|
||||
//
|
||||
// var r = NSZeroRect
|
||||
// r.size = appearance.faviconSize
|
||||
// r.origin.y = feedNameRect.origin.y
|
||||
//
|
||||
// r = RSRectCenteredHorizontallyInRect(r, unreadIndicatorRect)
|
||||
//
|
||||
// return r
|
||||
//}
|
||||
var r = textBoxRect
|
||||
let renderer = RSMultiLineRenderer(attributedTitle: cellData.attributedTitle)
|
||||
|
||||
private func rectsForTitle(_ cellData: TimelineCellData, _ width: CGFloat, _ appearance: TimelineCellAppearance) -> (NSRect, NSRect) {
|
||||
|
||||
var r = NSZeroRect
|
||||
r.origin.x = appearance.boxLeftMargin
|
||||
r.origin.y = appearance.cellPadding.top
|
||||
let measurements = renderer.measurements(forWidth: textBoxRect.width)
|
||||
r.size.height = CGFloat(measurements.height)
|
||||
|
||||
let textWidth = width - (r.origin.x + appearance.cellPadding.right)
|
||||
let renderer = RSMultiLineRenderer(attributedTitle: cellData.attributedTitle)
|
||||
var rline1 = r
|
||||
rline1.size.height = CGFloat(measurements.heightOfFirstLine)
|
||||
|
||||
let measurements = renderer.measurements(forWidth: textWidth)
|
||||
r.size = NSSize(width: textWidth, height: CGFloat(measurements.height))
|
||||
r.size.width = max(r.size.width, 0.0)
|
||||
return (r, rline1)
|
||||
}
|
||||
|
||||
var rline1 = r
|
||||
rline1.size.height = CGFloat(measurements.heightOfFirstLine)
|
||||
|
||||
return (r, rline1)
|
||||
}
|
||||
static func rectForDate(_ textBoxRect: NSRect, _ titleRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
|
||||
|
||||
private func rectForUnreadIndicator(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ titleLine1Rect: NSRect) -> NSRect {
|
||||
|
||||
var r = NSZeroRect
|
||||
r.size = NSSize(width: appearance.unreadCircleDimension, height: appearance.unreadCircleDimension)
|
||||
r.origin.x = appearance.cellPadding.left
|
||||
r = RSRectCenteredVerticallyInRect(r, titleLine1Rect)
|
||||
|
||||
return r
|
||||
}
|
||||
return rectOfLineBelow(textBoxRect, titleRect, appearance.titleBottomMargin, cellData.attributedDateString)
|
||||
}
|
||||
|
||||
private func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ titleLine1Rect: NSRect) -> NSRect {
|
||||
static func rectForFeedName(_ textBoxRect: NSRect, _ dateRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect {
|
||||
|
||||
if !cellData.showFeedName {
|
||||
return NSZeroRect
|
||||
}
|
||||
|
||||
return rectOfLineBelow(textBoxRect, dateRect, appearance.titleBottomMargin, cellData.attributedFeedName)
|
||||
}
|
||||
|
||||
static func rectOfLineBelow(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ topMargin: CGFloat, _ attributedString: NSAttributedString) -> NSRect {
|
||||
|
||||
let renderer = RSSingleLineRenderer(attributedTitle: attributedString)
|
||||
var r = NSZeroRect
|
||||
r.size = renderer.size
|
||||
r.origin.y = NSMaxY(rectAbove) + topMargin
|
||||
r.origin.x = textBoxRect.origin.x
|
||||
|
||||
var width = renderer.size.width
|
||||
width = min(width, textBoxRect.size.width)
|
||||
width = max(width, 0.0)
|
||||
r.size.width = width
|
||||
|
||||
var r = NSRect.zero
|
||||
if !cellData.showAvatar {
|
||||
return r
|
||||
}
|
||||
r.size = appearance.avatarSize
|
||||
r.origin.x = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight
|
||||
r.origin.y = titleLine1Rect.minY + appearance.avatarAdjustmentTop
|
||||
|
||||
return r
|
||||
static func rectForUnreadIndicator(_ appearance: TimelineCellAppearance, _ titleLine1Rect: NSRect) -> NSRect {
|
||||
|
||||
var r = NSZeroRect
|
||||
r.size = NSSize(width: appearance.unreadCircleDimension, height: appearance.unreadCircleDimension)
|
||||
r.origin.x = appearance.cellPadding.left
|
||||
r = RSRectCenteredVerticallyInRect(r, titleLine1Rect)
|
||||
r.origin.y += 1
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
static func rectForStar(_ appearance: TimelineCellAppearance, _ unreadIndicatorRect: NSRect) -> NSRect {
|
||||
|
||||
var r = NSRect.zero
|
||||
r.size.width = appearance.starDimension
|
||||
r.size.height = appearance.starDimension
|
||||
r.origin.x = floor(unreadIndicatorRect.origin.x - ((appearance.starDimension - appearance.unreadCircleDimension) / 2.0))
|
||||
r.origin.y = unreadIndicatorRect.origin.y - 3.0
|
||||
return r
|
||||
}
|
||||
|
||||
static func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ textBoxRect: NSRect, _ width: CGFloat) -> NSRect {
|
||||
|
||||
var r = NSRect.zero
|
||||
if !cellData.showAvatar {
|
||||
return r
|
||||
}
|
||||
r.size = appearance.avatarSize
|
||||
r.origin.x = (width - appearance.cellPadding.right) - r.size.width
|
||||
r = RSRectCenteredVerticallyInRect(r, textBoxRect)
|
||||
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
func timelineCellLayout(_ width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> TimelineCellLayout {
|
||||
private extension Array where Element == NSRect {
|
||||
|
||||
let (titleRect, titleLine1Rect) = rectsForTitle(cellData, width, appearance)
|
||||
let dateRect = rectForDate(cellData, width, appearance, titleRect)
|
||||
let feedNameRect = rectForFeedName(cellData, width, appearance, dateRect)
|
||||
let unreadIndicatorRect = rectForUnreadIndicator(cellData, appearance, titleLine1Rect)
|
||||
// let faviconRect = rectForFavicon(cellData, appearance, feedNameRect, unreadIndicatorRect)
|
||||
let avatarImageRect = rectForAvatar(cellData, appearance, titleLine1Rect)
|
||||
func maxY() -> CGFloat {
|
||||
|
||||
return TimelineCellLayout(width: width, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, unreadIndicatorRect: unreadIndicatorRect, avatarImageRect: avatarImageRect, paddingBottom: appearance.cellPadding.bottom)
|
||||
var y: CGFloat = 0.0
|
||||
self.forEach { y = Swift.max(y, $0.maxY) }
|
||||
return y
|
||||
}
|
||||
}
|
||||
|
||||
func timelineCellHeight(_ width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> CGFloat {
|
||||
|
||||
let layout = timelineCellLayout(width, cellData: cellData, appearance: appearance)
|
||||
return layout.height
|
||||
}
|
||||
|
|
|
@ -11,27 +11,28 @@ import RSTextDrawing
|
|||
|
||||
class TimelineTableCellView: NSTableCellView {
|
||||
|
||||
let titleView = RSMultiLineView(frame: NSZeroRect)
|
||||
let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect)
|
||||
let dateView = RSSingleLineView(frame: NSZeroRect)
|
||||
let feedNameView = RSSingleLineView(frame: NSZeroRect)
|
||||
private let titleView = RSMultiLineView(frame: NSZeroRect)
|
||||
private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect)
|
||||
private let dateView = RSSingleLineView(frame: NSZeroRect)
|
||||
private let feedNameView = RSSingleLineView(frame: NSZeroRect)
|
||||
|
||||
let avatarImageView: NSImageView = {
|
||||
private let avatarImageView: NSImageView = {
|
||||
let imageView = NSImageView(frame: NSRect.zero)
|
||||
imageView.imageScaling = .scaleProportionallyDown
|
||||
imageView.animates = false
|
||||
imageView.imageAlignment = .alignCenter
|
||||
imageView.image = appDelegate.genericFeedImage
|
||||
imageView.image = AppImages.genericFeedImage
|
||||
return imageView
|
||||
}()
|
||||
|
||||
// let faviconImageView: NSImageView = {
|
||||
// let imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: 16, height: 16))
|
||||
// imageView.imageScaling = .scaleProportionallyDown
|
||||
// imageView.animates = false
|
||||
// imageView.imageAlignment = .alignCenter
|
||||
// return imageView
|
||||
// }()
|
||||
private let starView: NSImageView = {
|
||||
let imageView = NSImageView(frame: NSRect.zero)
|
||||
imageView.imageScaling = .scaleNone
|
||||
imageView.animates = false
|
||||
imageView.imageAlignment = .alignCenter
|
||||
imageView.image = AppImages.timelineStar
|
||||
return imageView
|
||||
}()
|
||||
|
||||
var cellAppearance: TimelineCellAppearance! {
|
||||
didSet {
|
||||
|
@ -77,23 +78,6 @@ class TimelineTableCellView: NSTableCellView {
|
|||
}
|
||||
}
|
||||
|
||||
private func addSubviewAtInit(_ view: NSView, hidden: Bool) {
|
||||
|
||||
addSubview(view)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.isHidden = hidden
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
|
||||
addSubviewAtInit(titleView, hidden: false)
|
||||
addSubviewAtInit(unreadIndicatorView, hidden: true)
|
||||
addSubviewAtInit(dateView, hidden: false)
|
||||
addSubviewAtInit(feedNameView, hidden: true)
|
||||
addSubviewAtInit(avatarImageView, hidden: false)
|
||||
// addSubviewAtInit(faviconImageView, hidden: true)
|
||||
}
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
|
||||
super.init(frame: frameRect)
|
||||
|
@ -122,11 +106,6 @@ class TimelineTableCellView: NSTableCellView {
|
|||
updateAppearance()
|
||||
}
|
||||
|
||||
private func updatedLayoutRects() -> TimelineCellLayout {
|
||||
|
||||
return timelineCellLayout(NSWidth(bounds), cellData: cellData, appearance: cellAppearance)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
|
||||
resizeSubviews(withOldSize: NSZeroSize)
|
||||
|
@ -140,7 +119,7 @@ class TimelineTableCellView: NSTableCellView {
|
|||
dateView.rs_setFrameIfNotEqual(layoutRects.dateRect)
|
||||
feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect)
|
||||
avatarImageView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect)
|
||||
// faviconImageView.rs_setFrameIfNotEqual(layoutRects.faviconRect)
|
||||
starView.rs_setFrameIfNotEqual(layoutRects.starRect)
|
||||
}
|
||||
|
||||
override func updateLayer() {
|
||||
|
@ -157,20 +136,59 @@ class TimelineTableCellView: NSTableCellView {
|
|||
layer?.backgroundColor = color.cgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTitleView() {
|
||||
// MARK: - Private
|
||||
|
||||
private extension TimelineTableCellView {
|
||||
|
||||
func addSubviewAtInit(_ view: NSView, hidden: Bool) {
|
||||
|
||||
addSubview(view)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.isHidden = hidden
|
||||
}
|
||||
|
||||
func commonInit() {
|
||||
|
||||
addSubviewAtInit(titleView, hidden: false)
|
||||
addSubviewAtInit(unreadIndicatorView, hidden: true)
|
||||
addSubviewAtInit(dateView, hidden: false)
|
||||
addSubviewAtInit(feedNameView, hidden: true)
|
||||
addSubviewAtInit(avatarImageView, hidden: false)
|
||||
addSubviewAtInit(starView, hidden: false)
|
||||
}
|
||||
|
||||
func updatedLayoutRects() -> TimelineCellLayout {
|
||||
|
||||
return TimelineCellLayout(width: bounds.width, cellData: cellData, appearance: cellAppearance)
|
||||
}
|
||||
|
||||
func updateAppearance() {
|
||||
|
||||
if let rowView = superview as? NSTableRowView {
|
||||
isEmphasized = rowView.isEmphasized
|
||||
isSelected = rowView.isSelected
|
||||
}
|
||||
else {
|
||||
isEmphasized = false
|
||||
isSelected = false
|
||||
}
|
||||
}
|
||||
|
||||
func updateTitleView() {
|
||||
|
||||
titleView.attributedStringValue = cellData.attributedTitle
|
||||
needsLayout = true
|
||||
}
|
||||
|
||||
private func updateDateView() {
|
||||
|
||||
func updateDateView() {
|
||||
|
||||
dateView.attributedStringValue = cellData.attributedDateString
|
||||
needsLayout = true
|
||||
}
|
||||
|
||||
private func updateFeedNameView() {
|
||||
func updateFeedNameView() {
|
||||
|
||||
if cellData.showFeedName {
|
||||
if feedNameView.isHidden {
|
||||
|
@ -185,14 +203,20 @@ class TimelineTableCellView: NSTableCellView {
|
|||
}
|
||||
}
|
||||
|
||||
private func updateUnreadIndicator() {
|
||||
|
||||
if unreadIndicatorView.isHidden != cellData.read {
|
||||
unreadIndicatorView.isHidden = cellData.read
|
||||
func updateUnreadIndicator() {
|
||||
|
||||
let shouldHide = cellData.read || cellData.starred
|
||||
if unreadIndicatorView.isHidden != shouldHide {
|
||||
unreadIndicatorView.isHidden = shouldHide
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAvatar() {
|
||||
func updateStarView() {
|
||||
|
||||
starView.isHidden = !cellData.starred
|
||||
}
|
||||
|
||||
func updateAvatar() {
|
||||
|
||||
if !cellData.showAvatar {
|
||||
avatarImageView.image = nil
|
||||
|
@ -213,46 +237,15 @@ class TimelineTableCellView: NSTableCellView {
|
|||
|
||||
avatarImageView.wantsLayer = true
|
||||
avatarImageView.layer?.cornerRadius = cellAppearance.avatarCornerRadius
|
||||
if avatarImageView.image == nil {
|
||||
avatarImageView.layer?.backgroundColor = NSColor(calibratedWhite: 0.0, alpha: 0.05).cgColor
|
||||
}
|
||||
else {
|
||||
avatarImageView.layer?.backgroundColor = NSColor.clear.cgColor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func updateFavicon() {
|
||||
|
||||
// if let favicon = cellData.showFeedName ? cellData.favicon : nil {
|
||||
// faviconImageView.image = favicon
|
||||
// faviconImageView.isHidden = false
|
||||
// }
|
||||
// else {
|
||||
// faviconImageView.image = nil
|
||||
// faviconImageView.isHidden = true
|
||||
// }
|
||||
}
|
||||
|
||||
private func updateSubviews() {
|
||||
func updateSubviews() {
|
||||
|
||||
updateTitleView()
|
||||
updateDateView()
|
||||
updateFeedNameView()
|
||||
updateUnreadIndicator()
|
||||
updateStarView()
|
||||
updateAvatar()
|
||||
// updateFavicon()
|
||||
}
|
||||
|
||||
private func updateAppearance() {
|
||||
|
||||
if let rowView = superview as? NSTableRowView {
|
||||
isEmphasized = rowView.isEmphasized
|
||||
isSelected = rowView.isSelected
|
||||
}
|
||||
else {
|
||||
isEmphasized = false
|
||||
isSelected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// TimelineDataSource.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/17/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
@objc final class TimelineDataSource: NSObject, NSTableViewDataSource {
|
||||
|
||||
var articles = ArticleArray()
|
||||
|
||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||
|
||||
return articles.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
|
||||
|
||||
return articles.articleAtRow(row) ?? nil
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
|
||||
|
||||
guard let article = articles.articleAtRow(row) else {
|
||||
return nil
|
||||
}
|
||||
return ArticlePasteboardWriter(article: article)
|
||||
}
|
||||
}
|
|
@ -17,20 +17,18 @@ class TimelineTableRowView : NSTableRowView {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// override var interiorBackgroundStyle: NSBackgroundStyle {
|
||||
// return .Light
|
||||
// }
|
||||
|
||||
|
||||
private var cellView: TimelineTableCellView? {
|
||||
get {
|
||||
for oneSubview in subviews {
|
||||
if let foundView = oneSubview as? TimelineTableCellView {
|
||||
return foundView
|
||||
}
|
||||
for oneSubview in subviews {
|
||||
if let foundView = oneSubview as? TimelineTableCellView {
|
||||
return foundView
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override var isEmphasized: Bool {
|
||||
|
@ -50,12 +48,9 @@ class TimelineTableRowView : NSTableRowView {
|
|||
}
|
||||
|
||||
var gridRect: NSRect {
|
||||
get {
|
||||
// return NSMakeRect(floor(cellAppearance.boxLeftMargin), NSMaxY(bounds) - 1.0, NSWidth(bounds), 1)
|
||||
return NSMakeRect(0.0, NSMaxY(bounds) - 1.0, NSWidth(bounds), 1)
|
||||
}
|
||||
return NSMakeRect(0.0, NSMaxY(bounds) - 1.0, NSWidth(bounds), 1)
|
||||
}
|
||||
|
||||
|
||||
override func drawSeparator(in dirtyRect: NSRect) {
|
||||
|
||||
let path = NSBezierPath()
|
||||
|
@ -73,7 +68,7 @@ class TimelineTableRowView : NSTableRowView {
|
|||
|
||||
super.draw(dirtyRect)
|
||||
|
||||
if !isSelected && !isNextRowSelected {
|
||||
if cellAppearance.drawsGrid && !isSelected && !isNextRowSelected {
|
||||
drawSeparator(in: dirtyRect)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,10 +49,18 @@ extension TimelineViewController {
|
|||
|
||||
@objc func markArticlesStarredFromContextualMenu(_ sender: Any?) {
|
||||
|
||||
guard let articles = articles(from: sender) else {
|
||||
return
|
||||
}
|
||||
markArticles(articles, starred: true)
|
||||
}
|
||||
|
||||
@objc func markArticlesUnstarredFromContextualMenu(_ sender: Any?) {
|
||||
|
||||
guard let articles = articles(from: sender) else {
|
||||
return
|
||||
}
|
||||
markArticles(articles, starred: false)
|
||||
}
|
||||
|
||||
@objc func openInBrowserFromContextualMenu(_ sender: Any?) {
|
||||
|
@ -69,14 +77,21 @@ private extension TimelineViewController {
|
|||
|
||||
func markArticles(_ articles: [Article], read: Bool) {
|
||||
|
||||
guard let articlesToMark = read ? unreadArticles(from: articles) : readArticles(from: articles) else {
|
||||
return
|
||||
}
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: Array(articlesToMark), markingRead: read, undoManager: undoManager) else {
|
||||
markArticles(articles, statusKey: .read, flag: read)
|
||||
}
|
||||
|
||||
func markArticles(_ articles: [Article], starred: Bool) {
|
||||
|
||||
markArticles(articles, statusKey: .starred, flag: starred)
|
||||
}
|
||||
|
||||
func markArticles(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool) {
|
||||
|
||||
guard let undoManager = undoManager, let markStatusCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
|
||||
runCommand(markReadCommand)
|
||||
runCommand(markStatusCommand)
|
||||
}
|
||||
|
||||
func unreadArticles(from articles: [Article]) -> [Article]? {
|
||||
|
@ -110,12 +125,12 @@ private extension TimelineViewController {
|
|||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
|
||||
// if articles.anyArticleIsUnstarred() {
|
||||
// menu.addItem(markStarredMenuItem(articles))
|
||||
// }
|
||||
// if articles.anyArticleIsStarred() {
|
||||
// menu.addItem(markUnstarredMenuItem(articles))
|
||||
// }
|
||||
if articles.anyArticleIsUnstarred() {
|
||||
menu.addItem(markStarredMenuItem(articles))
|
||||
}
|
||||
if articles.anyArticleIsStarred() {
|
||||
menu.addItem(markUnstarredMenuItem(articles))
|
||||
}
|
||||
if menu.items.count > 0 && !menu.items.last!.isSeparatorItem {
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
|
|
|
@ -16,23 +16,20 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
|
||||
@IBOutlet var tableView: TimelineTableView!
|
||||
@IBOutlet var contextualMenuDelegate: TimelineContextualMenuDelegate?
|
||||
|
||||
@IBOutlet var dataSource: TimelineDataSource!
|
||||
|
||||
var selectedArticles: [Article] {
|
||||
get {
|
||||
return Array(articles.articlesForIndexes(tableView.selectedRowIndexes))
|
||||
}
|
||||
return Array(articles.articlesForIndexes(tableView.selectedRowIndexes))
|
||||
}
|
||||
|
||||
var hasAtLeastOneSelectedArticle: Bool {
|
||||
get {
|
||||
return tableView.selectedRow != -1
|
||||
}
|
||||
return tableView.selectedRow != -1
|
||||
}
|
||||
|
||||
var articles = ArticleArray() {
|
||||
didSet {
|
||||
if articles != oldValue {
|
||||
clearUndoableCommands()
|
||||
dataSource.articles = articles
|
||||
updateShowAvatars()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
@ -42,7 +39,6 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
var undoableCommands = [UndoableCommand]()
|
||||
private var cellAppearance: TimelineCellAppearance!
|
||||
private var cellAppearanceWithAvatar: TimelineCellAppearance!
|
||||
|
||||
private var showFeedNames = false {
|
||||
didSet {
|
||||
if showFeedNames != oldValue {
|
||||
|
@ -61,8 +57,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
}
|
||||
|
||||
private var didRegisterForNotifications = false
|
||||
private var reloadAvailableCellsTimer: Timer?
|
||||
private var fetchAndMergeArticlesTimer: Timer?
|
||||
static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5)
|
||||
|
||||
private var sortDirection = AppDefaults.shared.timelineSortDirection {
|
||||
didSet {
|
||||
|
@ -106,9 +101,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
}
|
||||
|
||||
private var oneSelectedArticle: Article? {
|
||||
get {
|
||||
return selectedArticles.count == 1 ? selectedArticles.first : nil
|
||||
}
|
||||
return selectedArticles.count == 1 ? selectedArticles.first : nil
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
@ -157,7 +150,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
|
||||
func markAllAsRead() {
|
||||
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markReadCommand)
|
||||
|
@ -175,14 +168,14 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func openArticleInBrowser(_ sender: AnyObject) {
|
||||
@objc func openArticleInBrowser(_ sender: Any?) {
|
||||
|
||||
if let link = oneSelectedArticle?.preferredLink {
|
||||
Browser.open(link)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func toggleStatusOfSelectedArticles(_ sender: AnyObject) {
|
||||
@IBAction func toggleStatusOfSelectedArticles(_ sender: Any?) {
|
||||
|
||||
guard !selectedArticles.isEmpty else {
|
||||
return
|
||||
|
@ -198,10 +191,10 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
markSelectedArticlesAsUnread(sender)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func markSelectedArticlesAsRead(_ sender: Any?) {
|
||||
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markReadCommand)
|
||||
|
@ -209,12 +202,49 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
|
||||
@IBAction func markSelectedArticlesAsUnread(_ sender: Any?) {
|
||||
|
||||
guard let undoManager = undoManager, let markUnreadCommand = MarkReadOrUnreadCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markUnreadCommand)
|
||||
}
|
||||
|
||||
@IBAction func copy(_ sender: Any?) {
|
||||
|
||||
NSPasteboard.general.copyObjects(selectedArticles)
|
||||
}
|
||||
|
||||
func toggleStarredStatusForSelectedArticles() {
|
||||
|
||||
// If any one of the selected articles is not starred, then star them.
|
||||
// If all articles are starred, then unstar them.
|
||||
|
||||
let commandStatus = markStarredCommandStatus()
|
||||
let starring: Bool
|
||||
switch commandStatus {
|
||||
case .canMark:
|
||||
starring = true
|
||||
case .canUnmark:
|
||||
starring = false
|
||||
case .canDoNothing:
|
||||
return
|
||||
}
|
||||
|
||||
guard let undoManager = undoManager, let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, markingStarred: starring, undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markStarredCommand)
|
||||
}
|
||||
|
||||
func markStarredCommandStatus() -> MarkCommandValidationStatus {
|
||||
|
||||
return MarkCommandValidationStatus.statusFor(selectedArticles) { $0.anyArticleIsUnstarred() }
|
||||
}
|
||||
|
||||
func markReadCommandStatus() -> MarkCommandValidationStatus {
|
||||
|
||||
return MarkCommandValidationStatus.statusFor(selectedArticles) { $0.anyArticleIsUnread() }
|
||||
}
|
||||
|
||||
func markOlderArticlesAsRead() {
|
||||
|
||||
// Mark articles the same age or older than the selected article(s) as read.
|
||||
|
@ -237,7 +267,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
return
|
||||
}
|
||||
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else {
|
||||
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else {
|
||||
return
|
||||
}
|
||||
runCommand(markReadCommand)
|
||||
|
@ -411,7 +441,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
|
||||
|
||||
let prototypeCellData = TimelineCellData(article: prototypeArticle, appearance: cellAppearance, showFeedName: showingFeedNames, feedName: "Prototype Feed Name", avatar: nil, showAvatar: false, featuredImage: nil)
|
||||
let height = timelineCellHeight(100, cellData: prototypeCellData, appearance: cellAppearance)
|
||||
let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance)
|
||||
return height
|
||||
}
|
||||
|
||||
|
@ -421,28 +451,40 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
rowHeightWithoutFeedName = calculateRowHeight(showingFeedNames: false)
|
||||
updateTableViewRowHeight()
|
||||
}
|
||||
|
||||
@objc func fetchAndMergeArticles() {
|
||||
|
||||
guard let representedObjects = representedObjects else {
|
||||
return
|
||||
}
|
||||
|
||||
performBlockAndRestoreSelection {
|
||||
|
||||
var unsortedArticles = fetchUnsortedArticles(for: representedObjects)
|
||||
|
||||
// Merge articles by articleID. For any unique articleID in current articles, add to unsortedArticles.
|
||||
let unsortedArticleIDs = unsortedArticles.articleIDs()
|
||||
for article in articles {
|
||||
if !unsortedArticleIDs.contains(article.articleID) {
|
||||
unsortedArticles.insert(article)
|
||||
}
|
||||
}
|
||||
|
||||
updateArticles(with: unsortedArticles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSTableViewDataSource
|
||||
// MARK: NSUserInterfaceValidations
|
||||
|
||||
extension TimelineViewController: NSTableViewDataSource {
|
||||
extension TimelineViewController: NSUserInterfaceValidations {
|
||||
|
||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
|
||||
return articles.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
|
||||
|
||||
return articles.articleAtRow(row) ?? nil
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
|
||||
|
||||
guard let article = articles.articleAtRow(row) else {
|
||||
return nil
|
||||
if item.action == #selector(copy(_:)) {
|
||||
return NSPasteboard.general.canCopyAtLeastOneObject(selectedArticles)
|
||||
}
|
||||
return ArticlePasteboardWriter(article: article)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -474,30 +516,28 @@ extension TimelineViewController: NSTableViewDelegate {
|
|||
|
||||
func tableViewSelectionDidChange(_ notification: Notification) {
|
||||
|
||||
tableView.redrawGrid()
|
||||
// tableView.redrawGrid()
|
||||
|
||||
let selectedRow = tableView.selectedRow
|
||||
if selectedRow < 0 || selectedRow == NSNotFound || tableView.numberOfSelectedRows != 1 {
|
||||
if selectedArticles.isEmpty {
|
||||
postTimelineSelectionDidChangeNotification(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if let selectedArticle = articles.articleAtRow(selectedRow) {
|
||||
if (!selectedArticle.status.read) {
|
||||
markArticles(Set([selectedArticle]), statusKey: .read, flag: true)
|
||||
if selectedArticles.count == 1 {
|
||||
let article = selectedArticles.first!
|
||||
if !article.status.read {
|
||||
markArticles(Set([article]), statusKey: .read, flag: true)
|
||||
}
|
||||
postTimelineSelectionDidChangeNotification(selectedArticle)
|
||||
}
|
||||
else {
|
||||
postTimelineSelectionDidChangeNotification(nil)
|
||||
}
|
||||
|
||||
postTimelineSelectionDidChangeNotification(selectedArticles)
|
||||
}
|
||||
|
||||
private func postTimelineSelectionDidChangeNotification(_ selectedArticle: Article?) {
|
||||
private func postTimelineSelectionDidChangeNotification(_ selectedArticles: ArticleArray?) {
|
||||
|
||||
var userInfo = UserInfoDictionary()
|
||||
if let article = selectedArticle {
|
||||
userInfo[UserInfoKey.article] = article
|
||||
if let selectedArticles = selectedArticles {
|
||||
userInfo[UserInfoKey.articles] = selectedArticles
|
||||
}
|
||||
userInfo[UserInfoKey.view] = tableView
|
||||
|
||||
|
@ -535,9 +575,6 @@ extension TimelineViewController: NSTableViewDelegate {
|
|||
return nil
|
||||
}
|
||||
|
||||
// TODO: make Feed know about its authors.
|
||||
// https://github.com/brentsimmons/Evergreen/issues/212
|
||||
|
||||
return appDelegate.feedIconDownloader.icon(for: feed)
|
||||
}
|
||||
|
||||
|
@ -565,7 +602,7 @@ extension TimelineViewController: NSTableViewDelegate {
|
|||
|
||||
private extension TimelineViewController {
|
||||
|
||||
func reloadAvailableCells() {
|
||||
@objc func reloadAvailableCells() {
|
||||
|
||||
if let indexesToReload = tableView.indexesOfAvailableRows() {
|
||||
reloadCells(for: indexesToReload)
|
||||
|
@ -574,21 +611,7 @@ private extension TimelineViewController {
|
|||
|
||||
func queueReloadAvailableCells() {
|
||||
|
||||
invalidateReloadTimer()
|
||||
reloadAvailableCellsTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
|
||||
self.reloadAvailableCells()
|
||||
self.invalidateReloadTimer()
|
||||
}
|
||||
}
|
||||
|
||||
func invalidateReloadTimer() {
|
||||
|
||||
if let timer = reloadAvailableCellsTimer {
|
||||
if timer.isValid {
|
||||
timer.invalidate()
|
||||
}
|
||||
reloadAvailableCellsTimer = nil
|
||||
}
|
||||
CoalescingQueue.standard.add(self, #selector(reloadAvailableCells))
|
||||
}
|
||||
|
||||
func updateTableViewRowHeight() {
|
||||
|
@ -687,19 +710,6 @@ private extension TimelineViewController {
|
|||
return fetchedArticles
|
||||
}
|
||||
|
||||
func fetchAndMergeArticles() {
|
||||
|
||||
guard let representedObjects = representedObjects else {
|
||||
return
|
||||
}
|
||||
|
||||
performBlockAndRestoreSelection {
|
||||
var unsortedArticles = fetchUnsortedArticles(for: representedObjects)
|
||||
unsortedArticles.formUnion(Set(articles))
|
||||
updateArticles(with: unsortedArticles)
|
||||
}
|
||||
}
|
||||
|
||||
func selectArticles(_ articleIDs: [String]) {
|
||||
|
||||
let indexesToSelect = articles.indexesForArticleIDs(Set(articleIDs))
|
||||
|
@ -710,23 +720,9 @@ private extension TimelineViewController {
|
|||
tableView.selectRowIndexes(indexesToSelect, byExtendingSelection: false)
|
||||
}
|
||||
|
||||
func invalidateFetchAndMergeArticlesTimer() {
|
||||
|
||||
if let timer = fetchAndMergeArticlesTimer {
|
||||
if timer.isValid {
|
||||
timer.invalidate()
|
||||
}
|
||||
fetchAndMergeArticlesTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func queueFetchAndMergeArticles() {
|
||||
|
||||
invalidateFetchAndMergeArticlesTimer()
|
||||
fetchAndMergeArticlesTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { (timer) in
|
||||
self.fetchAndMergeArticles()
|
||||
self.invalidateFetchAndMergeArticlesTimer()
|
||||
}
|
||||
TimelineViewController.fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticles))
|
||||
}
|
||||
|
||||
func representedObjectArraysAreEqual(_ objects1: [AnyObject]?, _ objects2: [AnyObject]?) -> Bool {
|
||||
|
|
|
@ -54,7 +54,7 @@ class PreferencesWindowController : NSWindowController, NSToolbarDelegate {
|
|||
|
||||
// MARK: Actions
|
||||
|
||||
@objc func toolbarItemClicked(_ sender: AnyObject) {
|
||||
@objc func toolbarItemClicked(_ sender: Any?) {
|
||||
|
||||
|
||||
}
|
||||
|
@ -96,9 +96,7 @@ class PreferencesWindowController : NSWindowController, NSToolbarDelegate {
|
|||
private extension PreferencesWindowController {
|
||||
|
||||
var currentView: NSView? {
|
||||
get {
|
||||
return window?.contentView?.subviews.first
|
||||
}
|
||||
return window?.contentView?.subviews.first
|
||||
}
|
||||
|
||||
func toolbarItemSpec(for identifier: String) -> PreferencesToolbarItemSpec? {
|
||||
|
|
|
@ -66,6 +66,8 @@
|
|||
<string>000000</string>
|
||||
<key>gridColorAlpha</key>
|
||||
<real>0.1</real>
|
||||
<key>drawsGrid</key>
|
||||
<false/>
|
||||
<key>header</key>
|
||||
<dict>
|
||||
<key>backgroundColor</key>
|
||||
|
@ -74,15 +76,15 @@
|
|||
<key>cell</key>
|
||||
<dict>
|
||||
<key>paddingLeft</key>
|
||||
<integer>12</integer>
|
||||
<integer>20</integer>
|
||||
<key>paddingRight</key>
|
||||
<integer>12</integer>
|
||||
<integer>20</integer>
|
||||
<key>paddingTop</key>
|
||||
<integer>12</integer>
|
||||
<integer>16</integer>
|
||||
<key>paddingBottom</key>
|
||||
<integer>14</integer>
|
||||
<integer>16</integer>
|
||||
<key>feedNameColor</key>
|
||||
<string>999999</string>
|
||||
<string>aaaaaa</string>
|
||||
<key>faviconFeedNameSpacing</key>
|
||||
<integer>2</integer>
|
||||
<key>dateColor</key>
|
||||
|
@ -92,11 +94,13 @@
|
|||
<key>dateMarginBottom</key>
|
||||
<integer>2</integer>
|
||||
<key>textColor</key>
|
||||
<string>666666</string>
|
||||
<string>aaaaaa</string>
|
||||
<key>textOnlyColor</key>
|
||||
<string>222222</string>
|
||||
<key>titleColor</key>
|
||||
<string>222222</string>
|
||||
<key>titleMarginBottom</key>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
<key>unreadCircleColor</key>
|
||||
<string>#2db6ff</string>
|
||||
<key>unreadCircleDimension</key>
|
||||
|
@ -108,11 +112,15 @@
|
|||
<key>avatarWidth</key>
|
||||
<integer>48</integer>
|
||||
<key>avatarMarginRight</key>
|
||||
<integer>20</integer>
|
||||
<key>avatarMarginLeft</key>
|
||||
<integer>8</integer>
|
||||
<key>avatarAdjustmentTop</key>
|
||||
<integer>4</integer>
|
||||
<key>avatarCornerRadius</key>
|
||||
<integer>7</integer>
|
||||
<key>starDimension</key>
|
||||
<integer>13</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Detail</key>
|
||||
|
|
|
@ -10,7 +10,7 @@ import Foundation
|
|||
import Data
|
||||
import RSCore
|
||||
|
||||
protocol PseudoFeed: class, DisplayNameProvider, UnreadCountProvider, SmallIconProvider {
|
||||
protocol PseudoFeed: class, DisplayNameProvider, UnreadCountProvider, SmallIconProvider, PasteboardWriterOwner {
|
||||
|
||||
}
|
||||
|
||||
|
@ -22,8 +22,6 @@ private var smartFeedIcon: NSImage = {
|
|||
extension PseudoFeed {
|
||||
|
||||
var smallIcon: NSImage? {
|
||||
get {
|
||||
return smartFeedIcon
|
||||
}
|
||||
return smartFeedIcon
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,7 @@ protocol SmartFeedDelegate: DisplayNameProvider, ArticleFetcher {
|
|||
final class SmartFeed: PseudoFeed {
|
||||
|
||||
var nameForDisplay: String {
|
||||
get {
|
||||
return delegate.nameForDisplay
|
||||
}
|
||||
return delegate.nameForDisplay
|
||||
}
|
||||
|
||||
var unreadCount = 0 {
|
||||
|
@ -32,23 +30,31 @@ final class SmartFeed: PseudoFeed {
|
|||
}
|
||||
}
|
||||
|
||||
var pasteboardWriter: NSPasteboardWriting {
|
||||
return SmartFeedPasteboardWriter(smartFeed: self)
|
||||
}
|
||||
|
||||
private let delegate: SmartFeedDelegate
|
||||
private var timer: Timer?
|
||||
private var unreadCounts = [Account: Int]()
|
||||
|
||||
init(delegate: SmartFeedDelegate) {
|
||||
|
||||
self.delegate = delegate
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
startTimer() // Fetch unread count at startup
|
||||
queueFetchUnreadCounts() // Fetch unread count at startup
|
||||
}
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
|
||||
if note.object is Account {
|
||||
startTimer()
|
||||
queueFetchUnreadCounts()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func fetchUnreadCounts() {
|
||||
|
||||
AccountManager.shared.accounts.forEach { self.fetchUnreadCount(for: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
extension SmartFeed: ArticleFetcher {
|
||||
|
@ -66,9 +72,12 @@ extension SmartFeed: ArticleFetcher {
|
|||
|
||||
private extension SmartFeed {
|
||||
|
||||
// MARK: - Unread Counts
|
||||
func queueFetchUnreadCounts() {
|
||||
|
||||
private func fetchUnreadCount(for account: Account) {
|
||||
CoalescingQueue.standard.add(self, #selector(fetchUnreadCounts))
|
||||
}
|
||||
|
||||
func fetchUnreadCount(for account: Account) {
|
||||
|
||||
delegate.fetchUnreadCount(for: account) { (accountUnreadCount) in
|
||||
self.unreadCounts[account] = accountUnreadCount
|
||||
|
@ -76,12 +85,7 @@ private extension SmartFeed {
|
|||
}
|
||||
}
|
||||
|
||||
private func fetchUnreadCounts() {
|
||||
|
||||
AccountManager.shared.accounts.forEach { self.fetchUnreadCount(for: $0) }
|
||||
}
|
||||
|
||||
private func updateUnreadCount() {
|
||||
func updateUnreadCount() {
|
||||
|
||||
unreadCount = AccountManager.shared.accounts.reduce(0) { (result, account) -> Int in
|
||||
if let oneUnreadCount = unreadCounts[account] {
|
||||
|
@ -90,26 +94,4 @@ private extension SmartFeed {
|
|||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Timer
|
||||
|
||||
func stopTimer() {
|
||||
|
||||
if let timer = timer {
|
||||
timer.rs_invalidateIfValid()
|
||||
}
|
||||
timer = nil
|
||||
}
|
||||
|
||||
private static let fetchCoalescingDelay: TimeInterval = 0.1
|
||||
|
||||
func startTimer() {
|
||||
|
||||
stopTimer()
|
||||
|
||||
timer = Timer.scheduledTimer(withTimeInterval: SmartFeed.fetchCoalescingDelay, repeats: false, block: { (timer) in
|
||||
self.fetchUnreadCounts()
|
||||
self.stopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// SmartFeedPasteboardWriter.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/11/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
@objc final class SmartFeedPasteboardWriter: NSObject, NSPasteboardWriting {
|
||||
|
||||
private let smartFeed: PseudoFeed
|
||||
|
||||
init(smartFeed: PseudoFeed) {
|
||||
|
||||
self.smartFeed = smartFeed
|
||||
}
|
||||
|
||||
// MARK: - NSPasteboardWriting
|
||||
|
||||
func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
|
||||
|
||||
return [.string]
|
||||
}
|
||||
|
||||
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
|
||||
let plist: Any?
|
||||
|
||||
switch type {
|
||||
case .string:
|
||||
plist = smartFeed.nameForDisplay
|
||||
default:
|
||||
plist = nil
|
||||
}
|
||||
|
||||
return plist
|
||||
}
|
||||
}
|
||||
|
|
@ -24,8 +24,11 @@ struct StarredFeedDelegate: SmartFeedDelegate {
|
|||
|
||||
func fetchArticles() -> Set<Article> {
|
||||
|
||||
// TODO
|
||||
return Set<Article>()
|
||||
var articles = Set<Article>()
|
||||
for account in AccountManager.shared.accounts {
|
||||
articles.formUnion(account.fetchStarredArticles())
|
||||
}
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchUnreadArticles() -> Set<Article> {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppKit
|
||||
import Account
|
||||
import Data
|
||||
|
||||
|
@ -24,6 +24,10 @@ final class UnreadFeed: PseudoFeed {
|
|||
}
|
||||
}
|
||||
|
||||
var pasteboardWriter: NSPasteboardWriting {
|
||||
return SmartFeedPasteboardWriter(smartFeed: self)
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
self.unreadCount = appDelegate.unreadCount
|
||||
|
|
|
@ -56,23 +56,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
let database: Database
|
||||
let delegate: AccountDelegate
|
||||
var username: String?
|
||||
var saveTimer: Timer?
|
||||
static let saveQueue = CoalescingQueue(name: "Account Save Queue", interval: 1.0)
|
||||
|
||||
public var dirty = false {
|
||||
didSet {
|
||||
|
||||
if refreshInProgress {
|
||||
if let _ = saveTimer {
|
||||
removeSaveTimer()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if dirty {
|
||||
resetSaveTimer()
|
||||
}
|
||||
else {
|
||||
removeSaveTimer()
|
||||
if dirty && !refreshInProgress {
|
||||
queueSaveToDiskIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,28 +82,22 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
}
|
||||
else {
|
||||
NotificationCenter.default.post(name: .AccountRefreshDidFinish, object: self)
|
||||
if dirty {
|
||||
resetSaveTimer()
|
||||
}
|
||||
queueSaveToDiskIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var refreshProgress: DownloadProgress {
|
||||
get {
|
||||
return delegate.refreshProgress
|
||||
}
|
||||
return delegate.refreshProgress
|
||||
}
|
||||
|
||||
|
||||
var supportsSubFolders: Bool {
|
||||
get {
|
||||
return delegate.supportsSubFolders
|
||||
}
|
||||
return delegate.supportsSubFolders
|
||||
}
|
||||
|
||||
|
||||
init?(dataFolder: String, settingsFile: String, type: AccountType, accountID: String) {
|
||||
|
||||
|
||||
// TODO: support various syncing systems.
|
||||
precondition(type == .onMyMac)
|
||||
self.delegate = LocalAccountDelegate()
|
||||
|
@ -364,6 +347,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
return database.fetchTodayArticles(for: flattenedFeeds())
|
||||
}
|
||||
|
||||
public func fetchStarredArticles() -> Set<Article> {
|
||||
|
||||
return database.fetchStarredArticles(for: flattenedFeeds())
|
||||
}
|
||||
|
||||
private func validateUnreadCount(_ feed: Feed, _ articles: Set<Article>) {
|
||||
|
||||
// articles must contain all the unread articles for the feed.
|
||||
|
@ -469,6 +457,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
}
|
||||
}
|
||||
|
||||
@objc func saveToDiskIfNeeded() {
|
||||
|
||||
guard dirty else {
|
||||
return
|
||||
}
|
||||
saveToDisk()
|
||||
dirty = false
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
public class func ==(lhs: Account, rhs: Account) -> Bool {
|
||||
|
@ -498,6 +495,11 @@ private extension Account {
|
|||
static let unreadCount = "unreadCount"
|
||||
}
|
||||
|
||||
func queueSaveToDiskIfNeeded() {
|
||||
|
||||
Account.saveQueue.add(self, #selector(saveToDiskIfNeeded))
|
||||
}
|
||||
|
||||
func object(with diskObject: [String: Any]) -> AnyObject? {
|
||||
|
||||
if Feed.isFeedDictionary(diskObject) {
|
||||
|
@ -552,21 +554,6 @@ private extension Account {
|
|||
return d as NSDictionary
|
||||
}
|
||||
|
||||
func saveToDiskIfNeeded() {
|
||||
|
||||
if !dirty {
|
||||
return
|
||||
}
|
||||
|
||||
if refreshInProgress {
|
||||
resetSaveTimer()
|
||||
return
|
||||
}
|
||||
|
||||
saveToDisk()
|
||||
dirty = false
|
||||
}
|
||||
|
||||
func saveToDisk() {
|
||||
|
||||
let d = diskDictionary()
|
||||
|
@ -577,21 +564,6 @@ private extension Account {
|
|||
NSApplication.shared.presentError(error)
|
||||
}
|
||||
}
|
||||
|
||||
func resetSaveTimer() {
|
||||
|
||||
saveTimer?.rs_invalidateIfValid()
|
||||
|
||||
saveTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { (timer) in
|
||||
self.saveToDiskIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
func removeSaveTimer() {
|
||||
|
||||
saveTimer?.rs_invalidateIfValid()
|
||||
saveTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
|
|
@ -31,37 +31,29 @@ public final class AccountManager: UnreadCountProvider {
|
|||
}
|
||||
|
||||
public var accounts: [Account] {
|
||||
get {
|
||||
return Array(accountsDictionary.values)
|
||||
}
|
||||
return Array(accountsDictionary.values)
|
||||
}
|
||||
|
||||
public var sortedAccounts: [Account] {
|
||||
get {
|
||||
return accountsSortedByName()
|
||||
}
|
||||
return accountsSortedByName()
|
||||
}
|
||||
|
||||
public var refreshInProgress: Bool {
|
||||
get {
|
||||
for account in accounts {
|
||||
if account.refreshInProgress {
|
||||
return true
|
||||
}
|
||||
for account in accounts {
|
||||
if account.refreshInProgress {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
public var combinedRefreshProgress: CombinedRefreshProgress {
|
||||
get {
|
||||
let downloadProgressArray = accounts.map { $0.refreshProgress }
|
||||
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
|
||||
}
|
||||
let downloadProgressArray = accounts.map { $0.refreshProgress }
|
||||
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
|
||||
}
|
||||
|
||||
|
||||
public init() {
|
||||
|
||||
|
||||
// The local "On My Mac" account must always exist, even if it's empty.
|
||||
|
||||
let localAccountFolder = (accountsFolder as NSString).appendingPathComponent("OnMyMac")
|
||||
|
|
|
@ -18,9 +18,7 @@ public extension Notification.Name {
|
|||
public extension Feed {
|
||||
|
||||
public var account: Account? {
|
||||
get {
|
||||
return AccountManager.shared.existingAccount(with: accountID)
|
||||
}
|
||||
return AccountManager.shared.existingAccount(with: accountID)
|
||||
}
|
||||
|
||||
public func takeSettings(from parsedFeed: ParsedFeed) {
|
||||
|
@ -59,15 +57,11 @@ public extension Feed {
|
|||
public extension Article {
|
||||
|
||||
public var account: Account? {
|
||||
get {
|
||||
return AccountManager.shared.existingAccount(with: accountID)
|
||||
}
|
||||
return AccountManager.shared.existingAccount(with: accountID)
|
||||
}
|
||||
|
||||
public var feed: Feed? {
|
||||
get {
|
||||
return account?.existingFeed(with: feedID)
|
||||
}
|
||||
return account?.existingFeed(with: feedID)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,9 +14,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
|||
let supportsSubFolders = false
|
||||
|
||||
var refreshProgress: DownloadProgress {
|
||||
get {
|
||||
return DownloadProgress(numberOfTasks: 0) // TODO
|
||||
}
|
||||
return DownloadProgress(numberOfTasks: 0) // TODO
|
||||
}
|
||||
|
||||
func refreshAll(for: Account) {
|
||||
|
|
|
@ -29,10 +29,7 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
|
|||
// MARK: - DisplayNameProvider
|
||||
|
||||
public var nameForDisplay: String {
|
||||
get {
|
||||
return name ?? Folder.untitledName
|
||||
|
||||
}
|
||||
return name ?? Folder.untitledName
|
||||
}
|
||||
|
||||
// MARK: - UnreadCountProvider
|
||||
|
@ -58,6 +55,7 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
|
|||
self.hashValue = folderID
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: self)
|
||||
}
|
||||
|
||||
// MARK: - Disk Dictionary
|
||||
|
@ -83,38 +81,36 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
|
|||
}
|
||||
|
||||
var dictionary: [String: Any] {
|
||||
get {
|
||||
|
||||
var d = [String: Any]()
|
||||
guard let account = account else {
|
||||
return d
|
||||
}
|
||||
|
||||
if let name = name {
|
||||
d[Key.name] = name
|
||||
}
|
||||
if unreadCount > 0 {
|
||||
d[Key.unreadCount] = unreadCount
|
||||
}
|
||||
|
||||
let childObjects = children.compactMap { (child) -> [String: Any]? in
|
||||
|
||||
if let feed = child as? Feed {
|
||||
return feed.dictionary
|
||||
}
|
||||
if let folder = child as? Folder, account.supportsSubFolders {
|
||||
return folder.dictionary
|
||||
}
|
||||
assertionFailure("Expected a feed or a folder.");
|
||||
return nil
|
||||
}
|
||||
|
||||
if !childObjects.isEmpty {
|
||||
d[Key.children] = childObjects
|
||||
}
|
||||
|
||||
|
||||
var d = [String: Any]()
|
||||
guard let account = account else {
|
||||
return d
|
||||
}
|
||||
|
||||
if let name = name {
|
||||
d[Key.name] = name
|
||||
}
|
||||
if unreadCount > 0 {
|
||||
d[Key.unreadCount] = unreadCount
|
||||
}
|
||||
|
||||
let childObjects = children.compactMap { (child) -> [String: Any]? in
|
||||
|
||||
if let feed = child as? Feed {
|
||||
return feed.dictionary
|
||||
}
|
||||
if let folder = child as? Folder, account.supportsSubFolders {
|
||||
return folder.dictionary
|
||||
}
|
||||
assertionFailure("Expected a feed or a folder.");
|
||||
return nil
|
||||
}
|
||||
|
||||
if !childObjects.isEmpty {
|
||||
d[Key.children] = childObjects
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// MARK: Feeds
|
||||
|
@ -130,7 +126,7 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
|
|||
return true
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
|
||||
|
@ -141,6 +137,11 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
|
|||
}
|
||||
}
|
||||
|
||||
@objc func childrenDidChange(_ note: Notification) {
|
||||
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
static public func ==(lhs: Folder, rhs: Folder) -> Bool {
|
||||
|
|
|
@ -15,9 +15,7 @@ final class LocalAccountDelegate: AccountDelegate {
|
|||
private let refresher = LocalAccountRefresher()
|
||||
|
||||
var refreshProgress: DownloadProgress {
|
||||
get {
|
||||
return refresher.progress
|
||||
}
|
||||
return refresher.progress
|
||||
}
|
||||
|
||||
func refreshAll(for account: Account) {
|
||||
|
|
|
@ -19,9 +19,7 @@ final class LocalAccountRefresher {
|
|||
}()
|
||||
|
||||
var progress: DownloadProgress {
|
||||
get {
|
||||
return downloadSession.progress
|
||||
}
|
||||
return downloadSession.progress
|
||||
}
|
||||
|
||||
public func refreshFeeds(_ feeds: Set<Feed>) {
|
||||
|
|
|
@ -34,15 +34,13 @@ public final class Feed: DisplayNameProvider, UnreadCountProvider, Hashable {
|
|||
// MARK: - DisplayNameProvider
|
||||
|
||||
public var nameForDisplay: String {
|
||||
get {
|
||||
if let s = editedName, !s.isEmpty {
|
||||
return s
|
||||
}
|
||||
if let s = name, !s.isEmpty {
|
||||
return s
|
||||
}
|
||||
return NSLocalizedString("Untitled", comment: "Feed name")
|
||||
if let s = editedName, !s.isEmpty {
|
||||
return s
|
||||
}
|
||||
if let s = name, !s.isEmpty {
|
||||
return s
|
||||
}
|
||||
return NSLocalizedString("Untitled", comment: "Feed name")
|
||||
}
|
||||
|
||||
// MARK: - UnreadCountProvider
|
||||
|
@ -115,46 +113,44 @@ public final class Feed: DisplayNameProvider, UnreadCountProvider, Hashable {
|
|||
}
|
||||
|
||||
public var dictionary: [String: Any] {
|
||||
get {
|
||||
var d = [String: Any]()
|
||||
|
||||
d[Key.url] = url
|
||||
|
||||
// feedID is not repeated when it’s the same as url
|
||||
if (feedID != url) {
|
||||
d[Key.feedID] = feedID
|
||||
}
|
||||
|
||||
if let homePageURL = homePageURL {
|
||||
d[Key.homePageURL] = homePageURL
|
||||
}
|
||||
if let iconURL = iconURL {
|
||||
d[Key.iconURL] = iconURL
|
||||
}
|
||||
if let faviconURL = faviconURL {
|
||||
d[Key.faviconURL] = faviconURL
|
||||
}
|
||||
if let name = name {
|
||||
d[Key.name] = name
|
||||
}
|
||||
if let editedName = editedName {
|
||||
d[Key.editedName] = editedName
|
||||
}
|
||||
if let authorsArray = authors?.diskArray() {
|
||||
d[Key.authors] = authorsArray
|
||||
}
|
||||
if let contentHash = contentHash {
|
||||
d[Key.contentHash] = contentHash
|
||||
}
|
||||
if unreadCount > 0 {
|
||||
d[Key.unreadCount] = unreadCount
|
||||
}
|
||||
if let conditionalGetInfo = conditionalGetInfo {
|
||||
d[Key.conditionalGetInfo] = conditionalGetInfo.dictionary
|
||||
}
|
||||
|
||||
return d
|
||||
var d = [String: Any]()
|
||||
|
||||
d[Key.url] = url
|
||||
|
||||
// feedID is not repeated when it’s the same as url
|
||||
if (feedID != url) {
|
||||
d[Key.feedID] = feedID
|
||||
}
|
||||
|
||||
if let homePageURL = homePageURL {
|
||||
d[Key.homePageURL] = homePageURL
|
||||
}
|
||||
if let iconURL = iconURL {
|
||||
d[Key.iconURL] = iconURL
|
||||
}
|
||||
if let faviconURL = faviconURL {
|
||||
d[Key.faviconURL] = faviconURL
|
||||
}
|
||||
if let name = name {
|
||||
d[Key.name] = name
|
||||
}
|
||||
if let editedName = editedName {
|
||||
d[Key.editedName] = editedName
|
||||
}
|
||||
if let authorsArray = authors?.diskArray() {
|
||||
d[Key.authors] = authorsArray
|
||||
}
|
||||
if let contentHash = contentHash {
|
||||
d[Key.contentHash] = contentHash
|
||||
}
|
||||
if unreadCount > 0 {
|
||||
d[Key.unreadCount] = unreadCount
|
||||
}
|
||||
if let conditionalGetInfo = conditionalGetInfo {
|
||||
d[Key.conditionalGetInfo] = conditionalGetInfo.dictionary
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// MARK: - Debug
|
||||
|
|
|
@ -77,6 +77,11 @@ final class ArticlesTable: DatabaseTable {
|
|||
return fetchTodayArticles(feeds.feedIDs())
|
||||
}
|
||||
|
||||
public func fetchStarredArticles(for feeds: Set<Feed>) -> Set<Article> {
|
||||
|
||||
return fetchStarredArticles(feeds.feedIDs())
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
|
||||
func update(_ feed: Feed, _ parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
|
||||
|
@ -405,6 +410,28 @@ private extension ArticlesTable {
|
|||
return articles
|
||||
}
|
||||
|
||||
func fetchStarredArticles(_ feedIDs: Set<String>) -> Set<Article> {
|
||||
|
||||
if feedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
|
||||
var articles = Set<Article>()
|
||||
|
||||
queue.fetchSync { (database) in
|
||||
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred = 1 and userDeleted = 0;
|
||||
|
||||
let parameters = feedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and starred = 1 and userDeleted = 0"
|
||||
articles = self.fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
|
||||
}
|
||||
|
||||
return articles
|
||||
}
|
||||
|
||||
|
||||
func articlesWithSQL(_ sql: String, _ parameters: [AnyObject], _ database: FMDatabase) -> Set<Article> {
|
||||
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
|
||||
|
|
|
@ -62,6 +62,11 @@ public final class Database {
|
|||
return articlesTable.fetchTodayArticles(for: feeds)
|
||||
}
|
||||
|
||||
public func fetchStarredArticles(for feeds: Set<Feed>) -> Set<Article> {
|
||||
|
||||
return articlesTable.fetchStarredArticles(for: feeds)
|
||||
}
|
||||
|
||||
// MARK: - Unread Counts
|
||||
|
||||
public func fetchUnreadCounts(for feeds: Set<Feed>, _ completion: @escaping UnreadCountCompletionBlock) {
|
||||
|
|
|
@ -110,9 +110,7 @@ extension Article: DatabaseObject {
|
|||
}
|
||||
|
||||
public var databaseID: String {
|
||||
get {
|
||||
return articleID
|
||||
}
|
||||
return articleID
|
||||
}
|
||||
|
||||
public func relatedObjectsWithName(_ name: String) -> [DatabaseObject]? {
|
||||
|
|
|
@ -26,9 +26,7 @@ extension ArticleStatus {
|
|||
extension ArticleStatus: DatabaseObject {
|
||||
|
||||
public var databaseID: String {
|
||||
get {
|
||||
return articleID
|
||||
}
|
||||
return articleID
|
||||
}
|
||||
|
||||
public func databaseDictionary() -> NSDictionary? {
|
||||
|
|
|
@ -57,9 +57,7 @@ private func optionalIntForColumn(_ row: FMResultSet, _ columnName: String) -> I
|
|||
extension Attachment: DatabaseObject {
|
||||
|
||||
public var databaseID: String {
|
||||
get {
|
||||
return attachmentID
|
||||
}
|
||||
return attachmentID
|
||||
}
|
||||
|
||||
public func databaseDictionary() -> NSDictionary? {
|
||||
|
|
|
@ -45,9 +45,7 @@ extension Author {
|
|||
extension Author: DatabaseObject {
|
||||
|
||||
public var databaseID: String {
|
||||
get {
|
||||
return authorID
|
||||
}
|
||||
return authorID
|
||||
}
|
||||
|
||||
public func databaseDictionary() -> NSDictionary? {
|
||||
|
|
|
@ -13,12 +13,10 @@ import Data
|
|||
extension ParsedItem {
|
||||
|
||||
var articleID: String {
|
||||
get {
|
||||
if let s = syncServiceID {
|
||||
return s
|
||||
}
|
||||
// Must be same calculation as for Article.
|
||||
return Article.calculatedArticleID(feedID: feedURL, uniqueID: uniqueID)
|
||||
if let s = syncServiceID {
|
||||
return s
|
||||
}
|
||||
// Must be same calculation as for Article.
|
||||
return Article.calculatedArticleID(feedID: feedURL, uniqueID: uniqueID)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,9 +179,7 @@ private final class StatusCache {
|
|||
|
||||
var dictionary = [String: ArticleStatus]()
|
||||
var cachedStatuses: Set<ArticleStatus> {
|
||||
get {
|
||||
return Set(dictionary.values)
|
||||
}
|
||||
return Set(dictionary.values)
|
||||
}
|
||||
|
||||
func add(_ statuses: Set<ArticleStatus>) {
|
||||
|
|
|
@ -27,8 +27,6 @@ public struct UnreadCountDictionary {
|
|||
}
|
||||
|
||||
public subscript(_ feed: Feed) -> Int? {
|
||||
get {
|
||||
return dictionary[feed.feedID]
|
||||
}
|
||||
return dictionary[feed.feedID]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
842DD7CE1E14995C00E061EB /* RSPlist.m in Sources */ = {isa = PBXBuildFile; fileRef = 844C915A1B65753E0051FC1B /* RSPlist.m */; };
|
||||
842DD7CF1E14995C00E061EB /* RSMacroProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 8453F7DC1BDF337800B1C8ED /* RSMacroProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
842DD7D01E14995C00E061EB /* RSMacroProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453F7DD1BDF337800B1C8ED /* RSMacroProcessor.m */; };
|
||||
842DD7D41E14995C00E061EB /* DiskSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849BF8B91C9130150071D1DA /* DiskSaver.swift */; };
|
||||
842DD7D51E14995C00E061EB /* PlistProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A835891D4EC7B80004C598 /* PlistProviderProtocol.swift */; };
|
||||
842DD7D61E14996300E061EB /* NSArray+RSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 84CFF5251AC3C9A200CEA6C8 /* NSArray+RSCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
842DD7D71E14996300E061EB /* NSArray+RSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 84CFF5261AC3C9A200CEA6C8 /* NSArray+RSCore.m */; };
|
||||
|
@ -95,14 +94,19 @@
|
|||
849A339E1AC90A0A0015BA09 /* NSTableView+RSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 849A339C1AC90A0A0015BA09 /* NSTableView+RSCore.m */; };
|
||||
849B08971BF7BCE30090CEE4 /* NSPasteboard+RSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 849B08951BF7BCE30090CEE4 /* NSPasteboard+RSCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
849B08981BF7BCE30090CEE4 /* NSPasteboard+RSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 849B08961BF7BCE30090CEE4 /* NSPasteboard+RSCore.m */; };
|
||||
849BF8BA1C9130150071D1DA /* DiskSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849BF8B91C9130150071D1DA /* DiskSaver.swift */; };
|
||||
849EE70D2039187D0082A1EA /* NSWindowController+RSCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE70C2039187D0082A1EA /* NSWindowController+RSCore.swift */; };
|
||||
849EE72320393A750082A1EA /* NSToolbar+RSCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE72220393A750082A1EA /* NSToolbar+RSCore.swift */; };
|
||||
849EE72520393AEA0082A1EA /* Array+RSCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE72420393AEA0082A1EA /* Array+RSCore.swift */; };
|
||||
84A8358A1D4EC7B80004C598 /* PlistProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A835891D4EC7B80004C598 /* PlistProviderProtocol.swift */; };
|
||||
84AD1EA520315A8800BC20B7 /* PasteboardWriterOwner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EA420315A8700BC20B7 /* PasteboardWriterOwner.swift */; };
|
||||
84AD1EA820315BA900BC20B7 /* NSPasteboard+RSCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EA720315BA900BC20B7 /* NSPasteboard+RSCore.swift */; };
|
||||
84B890561C59CF1600D8BF23 /* NSString+ExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B890551C59CF1600D8BF23 /* NSString+ExtrasTests.m */; };
|
||||
84B99C941FAE64D500ECDEDB /* DisplayNameProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C931FAE64D400ECDEDB /* DisplayNameProvider.swift */; };
|
||||
84B99C951FAE64D500ECDEDB /* DisplayNameProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C931FAE64D400ECDEDB /* DisplayNameProvider.swift */; };
|
||||
84B99C9A1FAE650100ECDEDB /* OPMLRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C991FAE650100ECDEDB /* OPMLRepresentable.swift */; };
|
||||
84B99C9B1FAE650100ECDEDB /* OPMLRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C991FAE650100ECDEDB /* OPMLRepresentable.swift */; };
|
||||
84BB45431D6909C700B48537 /* NSMutableDictionary-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BB45421D6909C700B48537 /* NSMutableDictionary-Extensions.swift */; };
|
||||
84C326872038C9F6006A025C /* CoalescingQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C326862038C9F6006A025C /* CoalescingQueue.swift */; };
|
||||
84C632A0200D30F1007BEEAA /* NSAppleEventDescriptor+RSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 84C6329E200D30F1007BEEAA /* NSAppleEventDescriptor+RSCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
84C632A1200D30F1007BEEAA /* NSAppleEventDescriptor+RSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 84C6329F200D30F1007BEEAA /* NSAppleEventDescriptor+RSCore.m */; };
|
||||
84C632A4200D356E007BEEAA /* SendToBlogEditorApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 84C632A2200D356E007BEEAA /* SendToBlogEditorApp.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -215,12 +219,17 @@
|
|||
849A339C1AC90A0A0015BA09 /* NSTableView+RSCore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTableView+RSCore.m"; sourceTree = "<group>"; };
|
||||
849B08951BF7BCE30090CEE4 /* NSPasteboard+RSCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPasteboard+RSCore.h"; sourceTree = "<group>"; };
|
||||
849B08961BF7BCE30090CEE4 /* NSPasteboard+RSCore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSPasteboard+RSCore.m"; sourceTree = "<group>"; };
|
||||
849BF8B91C9130150071D1DA /* DiskSaver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DiskSaver.swift; path = RSCore/DiskSaver.swift; sourceTree = "<group>"; };
|
||||
849EE70C2039187D0082A1EA /* NSWindowController+RSCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "NSWindowController+RSCore.swift"; path = "AppKit/NSWindowController+RSCore.swift"; sourceTree = "<group>"; };
|
||||
849EE72220393A750082A1EA /* NSToolbar+RSCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "NSToolbar+RSCore.swift"; path = "AppKit/NSToolbar+RSCore.swift"; sourceTree = "<group>"; };
|
||||
849EE72420393AEA0082A1EA /* Array+RSCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+RSCore.swift"; sourceTree = "<group>"; };
|
||||
84A835891D4EC7B80004C598 /* PlistProviderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlistProviderProtocol.swift; path = RSCore/PlistProviderProtocol.swift; sourceTree = "<group>"; };
|
||||
84AD1EA420315A8700BC20B7 /* PasteboardWriterOwner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PasteboardWriterOwner.swift; path = RSCore/PasteboardWriterOwner.swift; sourceTree = "<group>"; };
|
||||
84AD1EA720315BA900BC20B7 /* NSPasteboard+RSCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "NSPasteboard+RSCore.swift"; path = "AppKit/NSPasteboard+RSCore.swift"; sourceTree = "<group>"; };
|
||||
84B890551C59CF1600D8BF23 /* NSString+ExtrasTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+ExtrasTests.m"; sourceTree = "<group>"; };
|
||||
84B99C931FAE64D400ECDEDB /* DisplayNameProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DisplayNameProvider.swift; path = RSCore/DisplayNameProvider.swift; sourceTree = "<group>"; };
|
||||
84B99C991FAE650100ECDEDB /* OPMLRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OPMLRepresentable.swift; path = RSCore/OPMLRepresentable.swift; sourceTree = "<group>"; };
|
||||
84BB45421D6909C700B48537 /* NSMutableDictionary-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMutableDictionary-Extensions.swift"; sourceTree = "<group>"; };
|
||||
84C326862038C9F6006A025C /* CoalescingQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CoalescingQueue.swift; path = RSCore/CoalescingQueue.swift; sourceTree = "<group>"; };
|
||||
84C6329E200D30F1007BEEAA /* NSAppleEventDescriptor+RSCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "NSAppleEventDescriptor+RSCore.h"; path = "AppKit/NSAppleEventDescriptor+RSCore.h"; sourceTree = "<group>"; };
|
||||
84C6329F200D30F1007BEEAA /* NSAppleEventDescriptor+RSCore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "NSAppleEventDescriptor+RSCore.m"; path = "AppKit/NSAppleEventDescriptor+RSCore.m"; sourceTree = "<group>"; };
|
||||
84C632A2200D356E007BEEAA /* SendToBlogEditorApp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SendToBlogEditorApp.h; path = AppKit/SendToBlogEditorApp.h; sourceTree = "<group>"; };
|
||||
|
@ -357,13 +366,14 @@
|
|||
8453F7DD1BDF337800B1C8ED /* RSMacroProcessor.m */,
|
||||
84B99C931FAE64D400ECDEDB /* DisplayNameProvider.swift */,
|
||||
84B99C991FAE650100ECDEDB /* OPMLRepresentable.swift */,
|
||||
849BF8B91C9130150071D1DA /* DiskSaver.swift */,
|
||||
84A835891D4EC7B80004C598 /* PlistProviderProtocol.swift */,
|
||||
842E45CB1ED623C7000A8B52 /* UniqueIdentifier.swift */,
|
||||
84E34DA51F9FA1070077082F /* UndoableCommand.swift */,
|
||||
8402047D1FBCE77900D94C1A /* BatchUpdate.swift */,
|
||||
848F6AE81FC2BC50002D422E /* ThreadSafeCache.swift */,
|
||||
844B5B561FE9D36000C7C76A /* Keyboard.swift */,
|
||||
84AD1EA420315A8700BC20B7 /* PasteboardWriterOwner.swift */,
|
||||
84C326862038C9F6006A025C /* CoalescingQueue.swift */,
|
||||
84CFF5241AC3C8A200CEA6C8 /* Foundation */,
|
||||
84CFF5551AC3CF4A00CEA6C8 /* AppKit */,
|
||||
84CFF5661AC3D13F00CEA6C8 /* Images */,
|
||||
|
@ -434,6 +444,7 @@
|
|||
84CFF54A1AC3CDAC00CEA6C8 /* NSString+RSCore.m */,
|
||||
84CFF5451AC3CD8000CEA6C8 /* NSTimer+RSCore.h */,
|
||||
84CFF5461AC3CD8000CEA6C8 /* NSTimer+RSCore.m */,
|
||||
849EE72420393AEA0082A1EA /* Array+RSCore.swift */,
|
||||
84FEB4AB1D19D7F4004727E5 /* Date+Extensions.swift */,
|
||||
84BB45421D6909C700B48537 /* NSMutableDictionary-Extensions.swift */,
|
||||
8414CBA61C95F2EA00333C12 /* Set+Extensions.swift */,
|
||||
|
@ -466,9 +477,11 @@
|
|||
842635561D7FA1C800196285 /* NSTableView+Extensions.swift */,
|
||||
849A339B1AC90A0A0015BA09 /* NSTableView+RSCore.h */,
|
||||
849A339C1AC90A0A0015BA09 /* NSTableView+RSCore.m */,
|
||||
849EE72220393A750082A1EA /* NSToolbar+RSCore.swift */,
|
||||
84CFF5561AC3CF9100CEA6C8 /* NSView+RSCore.h */,
|
||||
84CFF5571AC3CF9100CEA6C8 /* NSView+RSCore.m */,
|
||||
8432B1871DACA2060057D6DF /* NSWindow-Extensions.swift */,
|
||||
849EE70C2039187D0082A1EA /* NSWindowController+RSCore.swift */,
|
||||
8414CBA91C95F8F700333C12 /* RSGeometry.h */,
|
||||
8414CBAA1C95F8F700333C12 /* RSGeometry.m */,
|
||||
8461387E1DB3F5BE00048B83 /* RSToolbarItem.swift */,
|
||||
|
@ -480,6 +493,7 @@
|
|||
84C687371FBC028900345C9E /* LogItem.swift */,
|
||||
8434D15B200BD6F400D6281E /* UserApp.swift */,
|
||||
84D5BA1D201E87E2009092BD /* URLPasteboardWriter.swift */,
|
||||
84AD1EA720315BA900BC20B7 /* NSPasteboard+RSCore.swift */,
|
||||
842DD7F91E1499FA00E061EB /* Views */,
|
||||
);
|
||||
name = AppKit;
|
||||
|
@ -735,7 +749,6 @@
|
|||
842DD7C81E14995C00E061EB /* RSConstants.m in Sources */,
|
||||
845A29201FC8BC49007B49E3 /* BinaryDiskCache.swift in Sources */,
|
||||
84C687391FBC028900345C9E /* LogItem.swift in Sources */,
|
||||
842DD7D41E14995C00E061EB /* DiskSaver.swift in Sources */,
|
||||
842DD7E11E14996300E061EB /* NSFileManager+RSCore.m in Sources */,
|
||||
842DD7C61E14995C00E061EB /* RSBlocks.m in Sources */,
|
||||
842DD7DD1E14996300E061EB /* NSDate+RSCore.m in Sources */,
|
||||
|
@ -765,7 +778,6 @@
|
|||
files = (
|
||||
8417FE031AC67D430048E9B7 /* RSOpaqueContainerView.m in Sources */,
|
||||
84CFF5481AC3CD8000CEA6C8 /* NSTimer+RSCore.m in Sources */,
|
||||
849BF8BA1C9130150071D1DA /* DiskSaver.swift in Sources */,
|
||||
84FE9FC41C00453900081CE9 /* NSStoryboard+RSCore.m in Sources */,
|
||||
84CFF5341AC3CB6800CEA6C8 /* NSDictionary+RSCore.m in Sources */,
|
||||
84CFF54C1AC3CDAC00CEA6C8 /* NSString+RSCore.m in Sources */,
|
||||
|
@ -780,6 +792,7 @@
|
|||
8414CBAC1C95F8F700333C12 /* RSGeometry.m in Sources */,
|
||||
84134D201C59D5450063FD24 /* NSCalendar+RSCore.m in Sources */,
|
||||
84CFF5651AC3D13C00CEA6C8 /* RSImageRenderer.m in Sources */,
|
||||
849EE70D2039187D0082A1EA /* NSWindowController+RSCore.swift in Sources */,
|
||||
84CFF5381AC3CBB200CEA6C8 /* NSMutableArray+RSCore.m in Sources */,
|
||||
84CFF5401AC3CD0100CEA6C8 /* NSMutableSet+RSCore.m in Sources */,
|
||||
84CFF5441AC3CD3500CEA6C8 /* NSNotificationCenter+RSCore.m in Sources */,
|
||||
|
@ -788,7 +801,9 @@
|
|||
84C687321FBAA3DF00345C9E /* LogWindowController.swift in Sources */,
|
||||
84C687381FBC028900345C9E /* LogItem.swift in Sources */,
|
||||
8432B1861DACA0E90057D6DF /* NSResponder-Extensions.swift in Sources */,
|
||||
849EE72520393AEA0082A1EA /* Array+RSCore.swift in Sources */,
|
||||
84E8E0D9202EC39800562D8F /* NSMenu+Extensions.swift in Sources */,
|
||||
84AD1EA820315BA900BC20B7 /* NSPasteboard+RSCore.swift in Sources */,
|
||||
84D5BA1E201E87E2009092BD /* URLPasteboardWriter.swift in Sources */,
|
||||
849B08981BF7BCE30090CEE4 /* NSPasteboard+RSCore.m in Sources */,
|
||||
842635571D7FA1C800196285 /* NSTableView+Extensions.swift in Sources */,
|
||||
|
@ -810,13 +825,16 @@
|
|||
842635591D7FA24800196285 /* NSOutlineView+Extensions.swift in Sources */,
|
||||
844C915C1B65753E0051FC1B /* RSPlist.m in Sources */,
|
||||
84CFF5231AC3C89D00CEA6C8 /* NSObject+RSCore.m in Sources */,
|
||||
84AD1EA520315A8800BC20B7 /* PasteboardWriterOwner.swift in Sources */,
|
||||
8414CBA71C95F2EA00333C12 /* Set+Extensions.swift in Sources */,
|
||||
84B99C9A1FAE650100ECDEDB /* OPMLRepresentable.swift in Sources */,
|
||||
84E34DA61F9FA1070077082F /* UndoableCommand.swift in Sources */,
|
||||
844F91D61D90D86100820C48 /* RSTransparentContainerView.m in Sources */,
|
||||
84CFF56E1AC3D20A00CEA6C8 /* NSImage+RSCore.m in Sources */,
|
||||
8453F7DF1BDF337800B1C8ED /* RSMacroProcessor.m in Sources */,
|
||||
84C326872038C9F6006A025C /* CoalescingQueue.swift in Sources */,
|
||||
842E45CC1ED623C7000A8B52 /* UniqueIdentifier.swift in Sources */,
|
||||
849EE72320393A750082A1EA /* NSToolbar+RSCore.swift in Sources */,
|
||||
84A8358A1D4EC7B80004C598 /* PlistProviderProtocol.swift in Sources */,
|
||||
849A339E1AC90A0A0015BA09 /* NSTableView+RSCore.m in Sources */,
|
||||
8434D15C200BD6F400D6281E /* UserApp.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// NSPasteboard+RSCore.swift
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 2/11/18.
|
||||
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
public extension NSPasteboard {
|
||||
|
||||
func copyObjects(_ objects: [Any]) {
|
||||
|
||||
guard let writers = writersFor(objects) else {
|
||||
return
|
||||
}
|
||||
|
||||
clearContents()
|
||||
writeObjects(writers)
|
||||
}
|
||||
|
||||
func canCopyAtLeastOneObject(_ objects: [Any]) -> Bool {
|
||||
|
||||
for object in objects {
|
||||
if object is PasteboardWriterOwner {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private extension NSPasteboard {
|
||||
|
||||
func writersFor(_ objects: [Any]) -> [NSPasteboardWriting]? {
|
||||
|
||||
let writers = objects.compactMap { ($0 as? PasteboardWriterOwner)?.pasteboardWriter }
|
||||
return writers.isEmpty ? nil : writers
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// NSToolbar+RSCore.swift
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 2/17/18.
|
||||
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
public extension NSToolbar {
|
||||
|
||||
public func existingItem(withIdentifier identifier: NSToolbarItem.Identifier) -> NSToolbarItem? {
|
||||
|
||||
return items.firstElementPassingTest{ $0.itemIdentifier == identifier }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// NSWindowController+RSCore.swift
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 2/17/18.
|
||||
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
public extension NSWindowController {
|
||||
|
||||
public var isDisplayingSheet: Bool {
|
||||
|
||||
return window?.isDisplayingSheet ?? false
|
||||
}
|
||||
|
||||
public var isOpen: Bool {
|
||||
|
||||
return isWindowLoaded && window!.isVisible
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Array+RSCore.swift
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 2/17/18.
|
||||
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension Array {
|
||||
|
||||
public func firstElementPassingTest( _ test: (Element) -> Bool) -> Element? {
|
||||
|
||||
guard let index = self.index(where: test) else {
|
||||
return nil
|
||||
}
|
||||
return self[index]
|
||||
}
|
||||
}
|
|
@ -22,9 +22,7 @@ public final class BatchUpdate {
|
|||
private var count = 0
|
||||
|
||||
public var isPerforming: Bool {
|
||||
get {
|
||||
return count > 0
|
||||
}
|
||||
return count > 0
|
||||
}
|
||||
|
||||
public func perform(_ batchUpdateBlock: BatchUpdateBlock) {
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// CoalescingQueue.swift
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 2/17/18.
|
||||
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Use when you want to coalesce calls for something like updating visible table cells.
|
||||
// Calls are uniqued. If you add a call with the same target and selector as a previous call, you’ll just get one call.
|
||||
// Targets are weakly-held. If a target goes to nil, the call is not performed.
|
||||
// The perform date is pushed off every time a call is added.
|
||||
// Calls are FIFO.
|
||||
|
||||
struct QueueCall: Equatable {
|
||||
|
||||
weak var target: AnyObject?
|
||||
let selector: Selector
|
||||
|
||||
init(target: AnyObject, selector: Selector) {
|
||||
|
||||
self.target = target
|
||||
self.selector = selector
|
||||
}
|
||||
|
||||
func perform() {
|
||||
|
||||
let _ = target?.perform(selector)
|
||||
}
|
||||
|
||||
static func ==(lhs: QueueCall, rhs: QueueCall) -> Bool {
|
||||
|
||||
return lhs.target === rhs.target && lhs.selector == rhs.selector
|
||||
}
|
||||
}
|
||||
|
||||
@objc public final class CoalescingQueue: NSObject {
|
||||
|
||||
public static let standard = CoalescingQueue(name: "Standard")
|
||||
public let name: String
|
||||
private let interval: TimeInterval
|
||||
private var timer: Timer? = nil
|
||||
private var calls = [QueueCall]()
|
||||
|
||||
public init(name: String, interval: TimeInterval = 0.05) {
|
||||
|
||||
self.name = name
|
||||
self.interval = interval
|
||||
}
|
||||
|
||||
public func add(_ target: AnyObject, _ selector: Selector) {
|
||||
|
||||
let queueCall = QueueCall(target: target, selector: selector)
|
||||
add(queueCall)
|
||||
}
|
||||
|
||||
@objc func timerDidFire(_ sender: Any?) {
|
||||
|
||||
let callsToMake = calls // Make a copy in case calls are added to the queue while performing calls.
|
||||
resetCalls()
|
||||
callsToMake.forEach { $0.perform() }
|
||||
}
|
||||
}
|
||||
|
||||
private extension CoalescingQueue {
|
||||
|
||||
func add(_ call: QueueCall) {
|
||||
|
||||
restartTimer()
|
||||
|
||||
if !calls.contains(call) {
|
||||
calls.append(call)
|
||||
}
|
||||
}
|
||||
|
||||
func resetCalls() {
|
||||
|
||||
calls = [QueueCall]()
|
||||
}
|
||||
|
||||
func restartTimer() {
|
||||
|
||||
invalidateTimer()
|
||||
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(timerDidFire(_:)), userInfo: nil, repeats: false)
|
||||
}
|
||||
|
||||
func invalidateTimer() {
|
||||
|
||||
if let timer = timer, timer.isValid {
|
||||
timer.invalidate()
|
||||
}
|
||||
timer = nil
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
//
|
||||
// DiskSaver.swift
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 12/28/15.
|
||||
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class DiskSaver: NSObject {
|
||||
|
||||
private let path: String
|
||||
public weak var delegate: PlistProvider?
|
||||
private var coalescedSaveTimer: Timer?
|
||||
|
||||
public var dirty = false {
|
||||
didSet {
|
||||
if dirty {
|
||||
coalescedSaveToDisk()
|
||||
}
|
||||
else {
|
||||
invalidateSaveTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init(path: String) {
|
||||
|
||||
self.path = path
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
if let timer = coalescedSaveTimer, timer.isValid {
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private func invalidateSaveTimer() {
|
||||
|
||||
if let timer = coalescedSaveTimer, timer.isValid {
|
||||
timer.invalidate()
|
||||
}
|
||||
coalescedSaveTimer = nil
|
||||
}
|
||||
|
||||
private let coalescedSaveInterval = 1.0
|
||||
|
||||
private func coalescedSaveToDisk() {
|
||||
|
||||
invalidateSaveTimer()
|
||||
coalescedSaveTimer = Timer.scheduledTimer(timeInterval: coalescedSaveInterval, target: self, selector: #selector(saveToDisk), userInfo: nil, repeats: false)
|
||||
}
|
||||
|
||||
@objc public dynamic func saveToDisk() {
|
||||
|
||||
invalidateSaveTimer()
|
||||
if !dirty {
|
||||
return
|
||||
}
|
||||
if let d = delegate?.plist {
|
||||
|
||||
do {
|
||||
try RSPlist.write(d, filePath: path)
|
||||
dirty = false
|
||||
}
|
||||
catch {
|
||||
print("DiskSaver: error writing \(path) to disk.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,11 +19,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@interface NSObject (RSCore)
|
||||
|
||||
/*Cancels any previous and does a new -performSelector:withObject:afterDelay:. Experimental.*/
|
||||
|
||||
- (void)rs_performSelectorCoalesced:(SEL)selector withObject:(id _Nullable)obj afterDelay:(NSTimeInterval)delay
|
||||
NS_SWIFT_NAME(performSelectorCoalesced(_:with:delay:));
|
||||
|
||||
|
||||
- (void)rs_takeValuesFromObject:(id)object propertyNames:(NSArray *)propertyNames;
|
||||
|
||||
|
|
|
@ -50,13 +50,6 @@ BOOL RSEqualValues(id obj1, id obj2) {
|
|||
|
||||
@implementation NSObject (RSCore)
|
||||
|
||||
- (void)rs_performSelectorCoalesced:(SEL)selector withObject:(id)obj afterDelay:(NSTimeInterval)delay {
|
||||
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector object:obj];
|
||||
[self performSelector:selector withObject:obj afterDelay:delay];
|
||||
}
|
||||
|
||||
|
||||
- (void)rs_takeValuesFromObject:(id)object propertyNames:(NSArray *)propertyNames {
|
||||
|
||||
for (NSString *onePropertyName in propertyNames) {
|
||||
|
|
|
@ -11,15 +11,12 @@ import AppKit
|
|||
public extension NSOutlineView {
|
||||
|
||||
var selectedItems: [AnyObject] {
|
||||
get {
|
||||
if selectionIsEmpty {
|
||||
return [AnyObject]()
|
||||
}
|
||||
|
||||
if selectionIsEmpty {
|
||||
return [AnyObject]()
|
||||
}
|
||||
|
||||
return selectedRowIndexes.compactMap { (oneIndex) -> AnyObject? in
|
||||
return item(atRow: oneIndex) as AnyObject
|
||||
}
|
||||
return selectedRowIndexes.compactMap { (oneIndex) -> AnyObject? in
|
||||
return item(atRow: oneIndex) as AnyObject
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,13 +9,11 @@
|
|||
import AppKit
|
||||
|
||||
public extension NSTableView {
|
||||
|
||||
|
||||
var selectionIsEmpty: Bool {
|
||||
get {
|
||||
return selectedRowIndexes.startIndex == selectedRowIndexes.endIndex
|
||||
}
|
||||
return selectedRowIndexes.startIndex == selectedRowIndexes.endIndex
|
||||
}
|
||||
|
||||
|
||||
func indexesOfAvailableRowsPassingTest(_ test: (Int) -> Bool) -> IndexSet? {
|
||||
|
||||
// Checks visible and in-flight rows.
|
||||
|
|
|
@ -9,7 +9,12 @@
|
|||
import AppKit
|
||||
|
||||
public extension NSWindow {
|
||||
|
||||
|
||||
public var isDisplayingSheet: Bool {
|
||||
|
||||
return attachedSheet != nil
|
||||
}
|
||||
|
||||
public func makeFirstResponderUnlessDescendantIsFirstResponder(_ responder: NSResponder) {
|
||||
|
||||
if let fr = firstResponder, fr.hasAncestor(responder) {
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// PasteboardWriterOwner.swift
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 2/11/18.
|
||||
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
public protocol PasteboardWriterOwner {
|
||||
|
||||
var pasteboardWriter: NSPasteboardWriting { get }
|
||||
}
|
|
@ -52,9 +52,7 @@ struct RelatedObjectIDsMap {
|
|||
}
|
||||
|
||||
subscript(_ objectID: String) -> Set<String>? {
|
||||
get {
|
||||
return dictionary[objectID]
|
||||
}
|
||||
return dictionary[objectID]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,8 +39,6 @@ public struct RelatedObjectsMap {
|
|||
}
|
||||
|
||||
public subscript(_ objectID: String) -> [DatabaseObject]? {
|
||||
get {
|
||||
return dictionary[objectID]
|
||||
}
|
||||
return dictionary[objectID]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,9 +25,7 @@ public struct FeedSpecifier: Hashable {
|
|||
public let source: Source
|
||||
public let hashValue: Int
|
||||
public var score: Int {
|
||||
get {
|
||||
return calculatedScore()
|
||||
}
|
||||
return calculatedScore()
|
||||
}
|
||||
|
||||
init(title: String?, urlString: String, source: Source) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue