Do some refactoring.
This commit is contained in:
parent
af3f41fbda
commit
911e6b0879
@ -87,6 +87,7 @@
|
|||||||
84F204CE1FAACB660076E152 /* FeedListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204CD1FAACB660076E152 /* FeedListViewController.swift */; };
|
84F204CE1FAACB660076E152 /* FeedListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204CD1FAACB660076E152 /* FeedListViewController.swift */; };
|
||||||
84F204DE1FAACB8B0076E152 /* FeedListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */; };
|
84F204DE1FAACB8B0076E152 /* FeedListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */; };
|
||||||
84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; };
|
84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; };
|
||||||
|
84F204E21FAAD4750076E152 /* TimelineTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204E11FAAD4750076E152 /* TimelineTableViewDelegate.swift */; };
|
||||||
84FB3A6F1FA6612C00EFC320 /* TimelineTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FB3A6E1FA6612C00EFC320 /* TimelineTableViewDataSource.swift */; };
|
84FB3A6F1FA6612C00EFC320 /* TimelineTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FB3A6E1FA6612C00EFC320 /* TimelineTableViewDataSource.swift */; };
|
||||||
84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; };
|
84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; };
|
||||||
84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
@ -453,6 +454,7 @@
|
|||||||
84F204CD1FAACB660076E152 /* FeedListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListViewController.swift; sourceTree = "<group>"; };
|
84F204CD1FAACB660076E152 /* FeedListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListViewController.swift; sourceTree = "<group>"; };
|
||||||
84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListTimelineViewController.swift; sourceTree = "<group>"; };
|
84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListTimelineViewController.swift; sourceTree = "<group>"; };
|
||||||
84F204DF1FAACBB30076E152 /* ArticleArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleArray.swift; sourceTree = "<group>"; };
|
84F204DF1FAACBB30076E152 /* ArticleArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleArray.swift; sourceTree = "<group>"; };
|
||||||
|
84F204E11FAAD4750076E152 /* TimelineTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewDelegate.swift; sourceTree = "<group>"; };
|
||||||
84FB3A6E1FA6612C00EFC320 /* TimelineTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewDataSource.swift; sourceTree = "<group>"; };
|
84FB3A6E1FA6612C00EFC320 /* TimelineTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewDataSource.swift; sourceTree = "<group>"; };
|
||||||
84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; };
|
84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@ -589,6 +591,7 @@
|
|||||||
849A976B1ED9EBC8007D329B /* TimelineViewController.swift */,
|
849A976B1ED9EBC8007D329B /* TimelineViewController.swift */,
|
||||||
84F204DF1FAACBB30076E152 /* ArticleArray.swift */,
|
84F204DF1FAACBB30076E152 /* ArticleArray.swift */,
|
||||||
84FB3A6E1FA6612C00EFC320 /* TimelineTableViewDataSource.swift */,
|
84FB3A6E1FA6612C00EFC320 /* TimelineTableViewDataSource.swift */,
|
||||||
|
84F204E11FAAD4750076E152 /* TimelineTableViewDelegate.swift */,
|
||||||
849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */,
|
849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */,
|
||||||
849A976A1ED9EBC8007D329B /* TimelineTableView.swift */,
|
849A976A1ED9EBC8007D329B /* TimelineTableView.swift */,
|
||||||
849A976F1ED9EC04007D329B /* Cell */,
|
849A976F1ED9EC04007D329B /* Cell */,
|
||||||
@ -1220,6 +1223,7 @@
|
|||||||
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */,
|
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */,
|
||||||
849A97921ED9EF65007D329B /* IndeterminateProgressWindowController.swift in Sources */,
|
849A97921ED9EF65007D329B /* IndeterminateProgressWindowController.swift in Sources */,
|
||||||
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */,
|
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */,
|
||||||
|
84F204E21FAAD4750076E152 /* TimelineTableViewDelegate.swift in Sources */,
|
||||||
849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */,
|
849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */,
|
||||||
849A978D1ED9EE4D007D329B /* FeedListWindowController.swift in Sources */,
|
849A978D1ED9EE4D007D329B /* FeedListWindowController.swift in Sources */,
|
||||||
849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */,
|
849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */,
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Data
|
import Data
|
||||||
|
|
||||||
|
typealias ArticleArray = [Article]
|
||||||
|
|
||||||
extension Array where Element == Article {
|
extension Array where Element == Article {
|
||||||
|
|
||||||
func articleAtRow(_ row: Int) -> Article? {
|
func articleAtRow(_ row: Int) -> Article? {
|
||||||
@ -19,4 +21,86 @@ extension Array where Element == Article {
|
|||||||
return self[row]
|
return self[row]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rowOfNextUnreadArticle(_ selectedRow: Int) -> Int? {
|
||||||
|
|
||||||
|
if isEmpty {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var rowIndex = selectedRow
|
||||||
|
while(true) {
|
||||||
|
|
||||||
|
rowIndex = rowIndex + 1
|
||||||
|
if rowIndex >= count {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let article = articleAtRow(rowIndex)!
|
||||||
|
if !article.status.read {
|
||||||
|
return rowIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func articlesForIndexes(_ indexes: IndexSet) -> Set<Article> {
|
||||||
|
|
||||||
|
return Set(indexes.flatMap{ (oneIndex) -> Article? in
|
||||||
|
return articleAtRow(oneIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexesForArticleIDs(_ articleIDs: Set<String>) -> IndexSet {
|
||||||
|
|
||||||
|
var indexes = IndexSet()
|
||||||
|
|
||||||
|
articleIDs.forEach { (articleID) in
|
||||||
|
let oneIndex = rowForArticleID(articleID)
|
||||||
|
if oneIndex != NSNotFound {
|
||||||
|
indexes.insert(oneIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexes
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedByDate() -> ArticleArray {
|
||||||
|
|
||||||
|
return sorted(by: articleComparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func canMarkAllAsRead() -> Bool {
|
||||||
|
|
||||||
|
for article in self {
|
||||||
|
if !article.status.read {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Array where Element == Article {
|
||||||
|
|
||||||
|
func rowForArticleID(_ articleID: String) -> Int {
|
||||||
|
|
||||||
|
if let index = index(where: { $0.articleID == articleID }) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
return NSNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func rowForArticle(_ article: Article) -> Int {
|
||||||
|
|
||||||
|
return rowForArticleID(article.articleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Sorting
|
||||||
|
|
||||||
|
func articleComparator(_ article1: Article, article2: Article) -> Bool {
|
||||||
|
|
||||||
|
return article1.logicalDatePublished > article2.logicalDatePublished
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -41,4 +41,16 @@ class TimelineTableView: NSTableView {
|
|||||||
super.viewDidEndLiveResize()
|
super.viewDidEndLiveResize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func redrawGrid() {
|
||||||
|
|
||||||
|
guard let rowViews = visibleRowViews() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rowViews.forEach{ (rowView) in
|
||||||
|
if let rowView = rowView as? TimelineTableRowView {
|
||||||
|
rowView.invalidateGridRect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,12 @@ import Cocoa
|
|||||||
|
|
||||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||||
|
|
||||||
return timelineViewController?.numberOfArticles ?? 0
|
return timelineViewController?.articles.count ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
|
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
|
||||||
|
|
||||||
return timelineViewController?.articleAtRow(row) ?? nil
|
return timelineViewController?.articles.articleAtRow(row) ?? nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
112
Evergreen/MainWindow/Timeline/TimelineTableViewDelegate.swift
Normal file
112
Evergreen/MainWindow/Timeline/TimelineTableViewDelegate.swift
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
//
|
||||||
|
// TimelineTableViewDelegate.swift
|
||||||
|
// Evergreen
|
||||||
|
//
|
||||||
|
// Created by Brent Simmons on 11/1/17.
|
||||||
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import Data
|
||||||
|
|
||||||
|
@objc final class TimelineTableViewDelegate: NSObject, NSTableViewDelegate {
|
||||||
|
|
||||||
|
private weak var timelineViewController: TimelineViewController?
|
||||||
|
|
||||||
|
init(timelineViewController: TimelineViewController) {
|
||||||
|
|
||||||
|
self.timelineViewController = timelineViewController
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: NSTableViewDelegate
|
||||||
|
|
||||||
|
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
|
||||||
|
|
||||||
|
guard let timelineViewController = timelineViewController else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let rowView: TimelineTableRowView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "timelineRow"), owner: self) as! TimelineTableRowView
|
||||||
|
rowView.cellAppearance = timelineViewController.cellAppearance
|
||||||
|
return rowView
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||||
|
|
||||||
|
guard let timelineViewController = timelineViewController else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let cell: TimelineTableCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "timelineCell"), owner: self) as! TimelineTableCellView
|
||||||
|
cell.cellAppearance = timelineViewController.cellAppearance
|
||||||
|
|
||||||
|
if let article = timelineViewController.articles.articleAtRow(row) {
|
||||||
|
configureTimelineCell(cell, article: article)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
makeTimelineCellEmpty(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
private func postTimelineSelectionDidChangeNotification(_ selectedArticle: Article?) {
|
||||||
|
|
||||||
|
guard let timelineViewController = timelineViewController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let appInfo = AppInfo()
|
||||||
|
if let article = selectedArticle {
|
||||||
|
appInfo.article = article
|
||||||
|
}
|
||||||
|
appInfo.view = timelineViewController.tableView
|
||||||
|
|
||||||
|
NotificationCenter.default.post(name: .TimelineSelectionDidChange, object: timelineViewController, userInfo: appInfo.userInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableViewSelectionDidChange(_ notification: Notification) {
|
||||||
|
|
||||||
|
guard let timelineViewController = timelineViewController, let tableView = timelineViewController.tableView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tableView.redrawGrid()
|
||||||
|
|
||||||
|
let selectedRow = tableView.selectedRow
|
||||||
|
if selectedRow < 0 || selectedRow == NSNotFound || tableView.numberOfSelectedRows != 1 {
|
||||||
|
postTimelineSelectionDidChangeNotification(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let selectedArticle = timelineViewController.articles.articleAtRow(selectedRow) {
|
||||||
|
if (!selectedArticle.status.read) {
|
||||||
|
markArticles(Set([selectedArticle]), statusKey: .read, flag: true)
|
||||||
|
}
|
||||||
|
postTimelineSelectionDidChangeNotification(selectedArticle)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
postTimelineSelectionDidChangeNotification(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension TimelineTableViewDelegate {
|
||||||
|
|
||||||
|
func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) {
|
||||||
|
|
||||||
|
guard let timelineViewController = timelineViewController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.objectValue = article
|
||||||
|
cell.cellData = TimelineCellData(article: article, appearance: timelineViewController.cellAppearance, showFeedName: timelineViewController.showFeedNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTimelineCellEmpty(_ cell: TimelineTableCellView) {
|
||||||
|
|
||||||
|
cell.objectValue = nil
|
||||||
|
cell.cellData = emptyCellData
|
||||||
|
}
|
||||||
|
}
|
@ -9,30 +9,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
import RSTextDrawing
|
import RSTextDrawing
|
||||||
import RSTree
|
|
||||||
import Data
|
import Data
|
||||||
import Account
|
import Account
|
||||||
|
|
||||||
class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource, KeyboardDelegate {
|
class TimelineViewController: NSViewController, KeyboardDelegate {
|
||||||
|
|
||||||
@IBOutlet var tableView: TimelineTableView!
|
@IBOutlet var tableView: TimelineTableView!
|
||||||
private var undoableCommands = [UndoableCommand]()
|
|
||||||
private var dataSource: TimelineTableViewDataSource!
|
|
||||||
var didRegisterForNotifications = false
|
|
||||||
var fontSize: FontSize = AppDefaults.shared.timelineFontSize {
|
|
||||||
didSet {
|
|
||||||
fontSizeDidChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var cellAppearance: TimelineCellAppearance!
|
var cellAppearance: TimelineCellAppearance!
|
||||||
|
var showFeedNames = false
|
||||||
|
|
||||||
var numberOfArticles: Int {
|
var articles = ArticleArray() {
|
||||||
get {
|
|
||||||
return articles.count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var articles = [Article]() {
|
|
||||||
didSet {
|
didSet {
|
||||||
if articles != oldValue {
|
if articles != oldValue {
|
||||||
clearUndoableCommands()
|
clearUndoableCommands()
|
||||||
@ -41,6 +27,29 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var selectedArticles: [Article] {
|
||||||
|
get {
|
||||||
|
return Array(articles.articlesForIndexes(tableView.selectedRowIndexes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var undoableCommands = [UndoableCommand]()
|
||||||
|
|
||||||
|
private lazy var tableViewDataSource: TimelineTableViewDataSource! = {
|
||||||
|
return TimelineTableViewDataSource(timelineViewController: self)
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var tableViewDelegate: TimelineTableViewDelegate! = {
|
||||||
|
return TimelineTableViewDelegate(timelineViewController: self)
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var didRegisterForNotifications = false
|
||||||
|
private var fontSize: FontSize = AppDefaults.shared.timelineFontSize {
|
||||||
|
didSet {
|
||||||
|
fontSizeDidChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var representedObjects: [AnyObject]? {
|
private var representedObjects: [AnyObject]? {
|
||||||
didSet {
|
didSet {
|
||||||
if !representedObjectArraysAreEqual(oldValue, representedObjects) {
|
if !representedObjectArraysAreEqual(oldValue, representedObjects) {
|
||||||
@ -52,20 +61,6 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var showFeedNames: Bool {
|
|
||||||
|
|
||||||
// if let _ = node?.representedObject as? Feed {
|
|
||||||
return false
|
|
||||||
// }
|
|
||||||
// return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedArticles: [Article] {
|
|
||||||
get {
|
|
||||||
return Array(articlesForIndexes(tableView.selectedRowIndexes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var oneSelectedArticle: Article? {
|
private var oneSelectedArticle: Article? {
|
||||||
get {
|
get {
|
||||||
return selectedArticles.count == 1 ? selectedArticles.first : nil
|
return selectedArticles.count == 1 ? selectedArticles.first : nil
|
||||||
@ -76,15 +71,13 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
|||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
||||||
dataSource = TimelineTableViewDataSource(timelineViewController: self)
|
|
||||||
tableView.dataSource = dataSource
|
|
||||||
|
|
||||||
cellAppearance = TimelineCellAppearance(theme: currentTheme, fontSize: fontSize)
|
cellAppearance = TimelineCellAppearance(theme: currentTheme, fontSize: fontSize)
|
||||||
tableView.rowHeight = calculateRowHeight()
|
|
||||||
|
|
||||||
|
tableView.dataSource = tableViewDataSource
|
||||||
|
tableView.delegate = tableViewDelegate
|
||||||
|
tableView.rowHeight = calculateRowHeight()
|
||||||
tableView.target = self
|
tableView.target = self
|
||||||
tableView.doubleAction = #selector(openArticleInBrowser(_:))
|
tableView.doubleAction = #selector(openArticleInBrowser(_:))
|
||||||
|
|
||||||
tableView.keyboardDelegate = self
|
tableView.keyboardDelegate = self
|
||||||
|
|
||||||
if !didRegisterForNotifications {
|
if !didRegisterForNotifications {
|
||||||
@ -229,36 +222,13 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
|||||||
}
|
}
|
||||||
|
|
||||||
func canMarkAllAsRead() -> Bool {
|
func canMarkAllAsRead() -> Bool {
|
||||||
|
|
||||||
for article in articles {
|
return articles.canMarkAllAsRead()
|
||||||
if !article.status.read {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexOfNextUnreadArticle() -> Int? {
|
func indexOfNextUnreadArticle() -> Int? {
|
||||||
|
|
||||||
if articles.isEmpty {
|
return articles.rowOfNextUnreadArticle(tableView.selectedRow)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var rowIndex = tableView.selectedRow
|
|
||||||
while(true) {
|
|
||||||
|
|
||||||
rowIndex = rowIndex + 1
|
|
||||||
if rowIndex >= articles.count {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let article = articleAtRow(rowIndex)!
|
|
||||||
if !article.status.read {
|
|
||||||
return rowIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
@ -368,106 +338,10 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
|||||||
|
|
||||||
private func reloadCellsForArticleIDs(_ articleIDs: Set<String>) {
|
private func reloadCellsForArticleIDs(_ articleIDs: Set<String>) {
|
||||||
|
|
||||||
let indexes = indexesForArticleIDs(articleIDs)
|
let indexes = articles.indexesForArticleIDs(articleIDs)
|
||||||
tableView.reloadData(forRowIndexes: indexes, columnIndexes: NSIndexSet(index: 0) as IndexSet)
|
tableView.reloadData(forRowIndexes: indexes, columnIndexes: NSIndexSet(index: 0) as IndexSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Articles
|
|
||||||
|
|
||||||
private func indexesForArticleIDs(_ articleIDs: Set<String>) -> IndexSet {
|
|
||||||
|
|
||||||
var indexes = IndexSet()
|
|
||||||
|
|
||||||
articleIDs.forEach { (articleID) in
|
|
||||||
let oneIndex = rowForArticleID(articleID)
|
|
||||||
if oneIndex != NSNotFound {
|
|
||||||
indexes.insert(oneIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return indexes
|
|
||||||
}
|
|
||||||
|
|
||||||
private func articlesForIndexes(_ indexes: IndexSet) -> Set<Article> {
|
|
||||||
|
|
||||||
return Set(indexes.flatMap{ (oneIndex) -> Article? in
|
|
||||||
return articleAtRow(oneIndex)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func articleAtRow(_ row: Int) -> Article? {
|
|
||||||
|
|
||||||
if row < 0 || row == NSNotFound || row > articles.count - 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return articles[row]
|
|
||||||
}
|
|
||||||
|
|
||||||
private func rowForArticle(_ article: Article) -> Int {
|
|
||||||
|
|
||||||
return rowForArticleID(article.articleID)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func rowForArticleID(_ articleID: String) -> Int {
|
|
||||||
|
|
||||||
if let index = articles.index(where: { $0.articleID == articleID }) {
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
|
|
||||||
return NSNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectedArticle() -> Article? {
|
|
||||||
|
|
||||||
return articleAtRow(tableView.selectedRow)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Sorting Articles
|
|
||||||
|
|
||||||
private func articleComparator(_ article1: Article, article2: Article) -> Bool {
|
|
||||||
|
|
||||||
return article1.logicalDatePublished > article2.logicalDatePublished
|
|
||||||
}
|
|
||||||
|
|
||||||
private func articlesSortedByDate(_ articles: Set<Article>) -> [Article] {
|
|
||||||
|
|
||||||
return Array(articles).sorted(by: articleComparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Fetching Articles
|
|
||||||
|
|
||||||
private func emptyTheTimeline() {
|
|
||||||
|
|
||||||
if !articles.isEmpty {
|
|
||||||
articles = [Article]()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func fetchArticles() {
|
|
||||||
|
|
||||||
guard let representedObjects = representedObjects else {
|
|
||||||
emptyTheTimeline()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var fetchedArticles = Set<Article>()
|
|
||||||
|
|
||||||
for object in representedObjects {
|
|
||||||
|
|
||||||
if let feed = object as? Feed {
|
|
||||||
fetchedArticles.formUnion(feed.fetchArticles())
|
|
||||||
}
|
|
||||||
else if let folder = object as? Folder {
|
|
||||||
fetchedArticles.formUnion(folder.fetchArticles())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sortedArticles = articlesSortedByDate(fetchedArticles)
|
|
||||||
if articles != sortedArticles {
|
|
||||||
articles = sortedArticles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Cell Configuring
|
// MARK: - Cell Configuring
|
||||||
|
|
||||||
private func calculateRowHeight() -> CGFloat {
|
private func calculateRowHeight() -> CGFloat {
|
||||||
@ -482,84 +356,47 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
|||||||
return height
|
return height
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) {
|
}
|
||||||
|
|
||||||
cell.objectValue = article
|
private extension TimelineViewController {
|
||||||
cell.cellData = TimelineCellData(article: article, appearance: cellAppearance, showFeedName: showFeedNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func makeTimelineCellEmpty(_ cell: TimelineTableCellView) {
|
var hasAtLeastOneSelectedArticle: Bool {
|
||||||
|
get {
|
||||||
cell.objectValue = nil
|
return tableView.selectedRow != -1
|
||||||
cell.cellData = emptyCellData
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - NSTableViewDataSource
|
|
||||||
|
|
||||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
|
||||||
|
|
||||||
return articles.count
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
|
|
||||||
|
|
||||||
return articleAtRow(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - NSTableViewDelegate
|
|
||||||
|
|
||||||
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
|
|
||||||
|
|
||||||
let rowView: TimelineTableRowView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "timelineRow"), owner: self) as! TimelineTableRowView
|
|
||||||
rowView.cellAppearance = cellAppearance
|
|
||||||
return rowView
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
|
||||||
|
|
||||||
let cell: TimelineTableCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "timelineCell"), owner: self) as! TimelineTableCellView
|
|
||||||
cell.cellAppearance = cellAppearance
|
|
||||||
|
|
||||||
if let article = articleAtRow(row) {
|
|
||||||
configureTimelineCell(cell, article: article)
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
makeTimelineCellEmpty(cell)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func postTimelineSelectionDidChangeNotification(_ selectedArticle: Article?) {
|
func emptyTheTimeline() {
|
||||||
|
|
||||||
let appInfo = AppInfo()
|
if !articles.isEmpty {
|
||||||
if let article = selectedArticle {
|
articles = [Article]()
|
||||||
appInfo.article = article
|
|
||||||
}
|
}
|
||||||
appInfo.view = tableView
|
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .TimelineSelectionDidChange, object: self, userInfo: appInfo.userInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableViewSelectionDidChange(_ notification: Notification) {
|
// MARK: Fetching Articles
|
||||||
|
|
||||||
tableView.redrawGrid()
|
func fetchArticles() {
|
||||||
|
|
||||||
let selectedRow = tableView.selectedRow
|
guard let representedObjects = representedObjects else {
|
||||||
|
emptyTheTimeline()
|
||||||
if selectedRow < 0 || selectedRow == NSNotFound || tableView.numberOfSelectedRows != 1 {
|
|
||||||
postTimelineSelectionDidChangeNotification(nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let selectedArticle = articleAtRow(selectedRow) {
|
var fetchedArticles = Set<Article>()
|
||||||
if (!selectedArticle.status.read) {
|
|
||||||
markArticles(Set([selectedArticle]), statusKey: .read, flag: true)
|
for object in representedObjects {
|
||||||
|
|
||||||
|
if let feed = object as? Feed {
|
||||||
|
fetchedArticles.formUnion(feed.fetchArticles())
|
||||||
|
}
|
||||||
|
else if let folder = object as? Folder {
|
||||||
|
fetchedArticles.formUnion(folder.fetchArticles())
|
||||||
}
|
}
|
||||||
postTimelineSelectionDidChangeNotification(selectedArticle)
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
postTimelineSelectionDidChangeNotification(nil)
|
let sortedArticles = Array(fetchedArticles).sortedByDate()
|
||||||
|
if articles != sortedArticles {
|
||||||
|
articles = sortedArticles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -586,72 +423,3 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension TimelineViewController {
|
|
||||||
|
|
||||||
var hasAtLeastOneSelectedArticle: Bool {
|
|
||||||
get {
|
|
||||||
return self.tableView.selectedRow != -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - NSTableView extension
|
|
||||||
|
|
||||||
private extension NSTableView {
|
|
||||||
|
|
||||||
func scrollTo(row: Int) {
|
|
||||||
|
|
||||||
guard let scrollView = self.enclosingScrollView else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let documentVisibleRect = scrollView.documentVisibleRect
|
|
||||||
|
|
||||||
let r = rect(ofRow: row)
|
|
||||||
if NSContainsRect(documentVisibleRect, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let rMidY = NSMidY(r)
|
|
||||||
var scrollPoint = NSZeroPoint;
|
|
||||||
let extraHeight = 150
|
|
||||||
scrollPoint.y = floor(rMidY - (documentVisibleRect.size.height / 2.0)) + CGFloat(extraHeight)
|
|
||||||
scrollPoint.y = max(scrollPoint.y, 0)
|
|
||||||
|
|
||||||
let maxScrollPointY = frame.size.height - documentVisibleRect.size.height
|
|
||||||
scrollPoint.y = min(maxScrollPointY, scrollPoint.y)
|
|
||||||
|
|
||||||
let clipView = scrollView.contentView
|
|
||||||
|
|
||||||
let rClipView = NSMakeRect(scrollPoint.x, scrollPoint.y, NSWidth(clipView.bounds), NSHeight(clipView.bounds))
|
|
||||||
|
|
||||||
clipView.animator().bounds = rClipView
|
|
||||||
}
|
|
||||||
|
|
||||||
func visibleRowViews() -> [TimelineTableRowView]? {
|
|
||||||
|
|
||||||
guard let scrollView = self.enclosingScrollView, numberOfRows > 0 else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let range = rows(in: scrollView.documentVisibleRect)
|
|
||||||
let ixMax = numberOfRows - 1
|
|
||||||
let ixStart = min(range.location, ixMax)
|
|
||||||
let ixEnd = min(((range.location + range.length) - 1), ixMax)
|
|
||||||
|
|
||||||
var visibleRows = [TimelineTableRowView]()
|
|
||||||
|
|
||||||
for ixRow in ixStart...ixEnd {
|
|
||||||
if let oneRowView = rowView(atRow: ixRow, makeIfNecessary: false) as? TimelineTableRowView {
|
|
||||||
visibleRows += [oneRowView]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return visibleRows.isEmpty ? nil : visibleRows
|
|
||||||
}
|
|
||||||
|
|
||||||
func redrawGrid() {
|
|
||||||
|
|
||||||
visibleRowViews()?.forEach { $0.invalidateGridRect() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -15,4 +15,54 @@ public extension NSTableView {
|
|||||||
return selectedRowIndexes.startIndex == selectedRowIndexes.endIndex
|
return selectedRowIndexes.startIndex == selectedRowIndexes.endIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scrollTo(row: Int) {
|
||||||
|
|
||||||
|
guard let scrollView = self.enclosingScrollView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let documentVisibleRect = scrollView.documentVisibleRect
|
||||||
|
|
||||||
|
let r = rect(ofRow: row)
|
||||||
|
if NSContainsRect(documentVisibleRect, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let rMidY = NSMidY(r)
|
||||||
|
var scrollPoint = NSZeroPoint;
|
||||||
|
let extraHeight = 150
|
||||||
|
scrollPoint.y = floor(rMidY - (documentVisibleRect.size.height / 2.0)) + CGFloat(extraHeight)
|
||||||
|
scrollPoint.y = max(scrollPoint.y, 0)
|
||||||
|
|
||||||
|
let maxScrollPointY = frame.size.height - documentVisibleRect.size.height
|
||||||
|
scrollPoint.y = min(maxScrollPointY, scrollPoint.y)
|
||||||
|
|
||||||
|
let clipView = scrollView.contentView
|
||||||
|
|
||||||
|
let rClipView = NSMakeRect(scrollPoint.x, scrollPoint.y, NSWidth(clipView.bounds), NSHeight(clipView.bounds))
|
||||||
|
|
||||||
|
clipView.animator().bounds = rClipView
|
||||||
|
}
|
||||||
|
|
||||||
|
func visibleRowViews() -> [NSTableRowView]? {
|
||||||
|
|
||||||
|
guard let scrollView = self.enclosingScrollView, numberOfRows > 0 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = rows(in: scrollView.documentVisibleRect)
|
||||||
|
let ixMax = numberOfRows - 1
|
||||||
|
let ixStart = min(range.location, ixMax)
|
||||||
|
let ixEnd = min(((range.location + range.length) - 1), ixMax)
|
||||||
|
|
||||||
|
var visibleRows = [NSTableRowView]()
|
||||||
|
|
||||||
|
for ixRow in ixStart...ixEnd {
|
||||||
|
if let oneRowView = rowView(atRow: ixRow, makeIfNecessary: false) {
|
||||||
|
visibleRows += [oneRowView]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visibleRows.isEmpty ? nil : visibleRows
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user