Add contextual menu to timeline.
This commit is contained in:
parent
46cae22987
commit
d46ae4df33
|
@ -136,6 +136,7 @@
|
|||
84DAEE321F870B390058304B /* DockBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE311F870B390058304B /* DockBadge.swift */; };
|
||||
84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; };
|
||||
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; };
|
||||
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */; };
|
||||
84E95CF71FABB3C800552D99 /* FeedList.plist in Resources */ = {isa = PBXBuildFile; fileRef = 84E95CF61FABB3C800552D99 /* FeedList.plist */; };
|
||||
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; };
|
||||
84EB381F1FBA8B9F000D2111 /* KeyboardShortcuts.html in Resources */ = {isa = PBXBuildFile; fileRef = 84EB38101FBA8B9F000D2111 /* KeyboardShortcuts.html */; };
|
||||
|
@ -629,6 +630,7 @@
|
|||
84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = "<group>"; };
|
||||
84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDefaults.swift; path = Evergreen/AppDefaults.swift; sourceTree = "<group>"; };
|
||||
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorAvatarDownloader.swift; sourceTree = "<group>"; };
|
||||
84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelineViewController+ContextualMenus.swift"; sourceTree = "<group>"; };
|
||||
84E95CF61FABB3C800552D99 /* FeedList.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = FeedList.plist; sourceTree = "<group>"; };
|
||||
84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticlePasteboardWriter.swift; sourceTree = "<group>"; };
|
||||
84EB38101FBA8B9F000D2111 /* KeyboardShortcuts.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = KeyboardShortcuts.html; sourceTree = "<group>"; };
|
||||
|
@ -940,6 +942,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
849A976B1ED9EBC8007D329B /* TimelineViewController.swift */,
|
||||
84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */,
|
||||
84F204DF1FAACBB30076E152 /* ArticleArray.swift */,
|
||||
849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */,
|
||||
849A976A1ED9EBC8007D329B /* TimelineTableView.swift */,
|
||||
|
@ -1878,6 +1881,7 @@
|
|||
84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */,
|
||||
D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */,
|
||||
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
|
||||
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */,
|
||||
849A97791ED9EC04007D329B /* TimelineStringUtilities.swift in Sources */,
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */,
|
||||
84F204CE1FAACB660076E152 /* FeedListViewController.swift in Sources */,
|
||||
|
|
|
@ -650,7 +650,11 @@
|
|||
<outlet property="timelineViewController" destination="36G-bQ-b96" id="rED-2Z-kh6"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="iD1-KK-gFc" customClass="TimelineContextualMenuDelegate" customModule="Evergreen" customModuleProvider="target"/>
|
||||
<customObject id="iD1-KK-gFc" customClass="TimelineContextualMenuDelegate" customModule="Evergreen" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="timelineViewController" destination="36G-bQ-b96" id="oE9-uV-TNi"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="62" y="394"/>
|
||||
</scene>
|
||||
|
|
|
@ -78,13 +78,38 @@ extension Array where Element == Article {
|
|||
|
||||
func canMarkAllAsRead() -> Bool {
|
||||
|
||||
return anyArticleIsUnread()
|
||||
}
|
||||
|
||||
func anyArticlePassesTest(_ test: ((Article) -> Bool)) -> Bool {
|
||||
|
||||
for article in self {
|
||||
if !article.status.read {
|
||||
if test(article) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func anyArticleIsRead() -> Bool {
|
||||
|
||||
return anyArticlePassesTest { $0.status.read }
|
||||
}
|
||||
|
||||
func anyArticleIsUnread() -> Bool {
|
||||
|
||||
return anyArticlePassesTest { !$0.status.read }
|
||||
}
|
||||
|
||||
func anyArticleIsStarred() -> Bool {
|
||||
|
||||
return anyArticlePassesTest { $0.status.starred }
|
||||
}
|
||||
|
||||
func anyArticleIsUnstarred() -> Bool {
|
||||
|
||||
return anyArticlePassesTest { !$0.status.starred }
|
||||
}
|
||||
}
|
||||
|
||||
private extension Array where Element == Article {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import AppKit
|
||||
import RSCore
|
||||
|
||||
@objc final class TimelineContextualMenuDelegate: NSObject, NSMenuDelegate {
|
||||
|
||||
|
@ -14,21 +15,17 @@ import AppKit
|
|||
|
||||
public func menuNeedsUpdate(_ menu: NSMenu) {
|
||||
|
||||
// guard let timelineViewController = timelineViewController else {
|
||||
// return
|
||||
// }
|
||||
guard let timelineViewController = timelineViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
// menu.removeAllItems()
|
||||
menu.removeAllItems()
|
||||
|
||||
// guard let contextualMenu = sidebarViewController.contextualMenuForClickedRows() else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let items = contextualMenu.items
|
||||
// contextualMenu.removeAllItems()
|
||||
// for menuItem in items {
|
||||
// menu.addItem(menuItem)
|
||||
// }
|
||||
guard let contextualMenu = timelineViewController.contextualMenuForClickedRows() else {
|
||||
return
|
||||
}
|
||||
|
||||
menu.takeItems(from: contextualMenu)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
//
|
||||
// TimelineViewController+ContextualMenus.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/9/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Data
|
||||
import Account
|
||||
|
||||
extension TimelineViewController {
|
||||
|
||||
func contextualMenuForClickedRows() -> NSMenu? {
|
||||
|
||||
let row = tableView.clickedRow
|
||||
guard row != -1, let article = articles.articleAtRow(row) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if selectedArticles.contains(article) {
|
||||
// If the clickedRow is part of the selected rows, then do a contextual menu for all the selected rows.
|
||||
return menu(for: selectedArticles)
|
||||
}
|
||||
return menu(for: [article])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Contextual Menu Actions
|
||||
|
||||
extension TimelineViewController {
|
||||
|
||||
@objc func markArticlesReadFromContextualMenu(_ sender: Any?) {
|
||||
|
||||
guard let articles = articles(from: sender) else {
|
||||
return
|
||||
}
|
||||
markArticles(articles, read: true)
|
||||
}
|
||||
|
||||
@objc func markArticlesUnreadFromContextualMenu(_ sender: Any?) {
|
||||
|
||||
guard let articles = articles(from: sender) else {
|
||||
return
|
||||
}
|
||||
markArticles(articles, read: false)
|
||||
}
|
||||
|
||||
@objc func markArticlesStarredFromContextualMenu(_ sender: Any?) {
|
||||
|
||||
}
|
||||
|
||||
@objc func markArticlesUnstarredFromContextualMenu(_ sender: Any?) {
|
||||
|
||||
}
|
||||
|
||||
@objc func openInBrowserFromContextualMenu(_ sender: Any?) {
|
||||
|
||||
guard let menuItem = sender as? NSMenuItem, let urlString = menuItem.representedObject as? String else {
|
||||
return
|
||||
}
|
||||
Browser.open(urlString, inBackground: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
runCommand(markReadCommand)
|
||||
}
|
||||
|
||||
func unreadArticles(from articles: [Article]) -> [Article]? {
|
||||
|
||||
let filteredArticles = articles.filter { !$0.status.read }
|
||||
return filteredArticles.isEmpty ? nil : filteredArticles
|
||||
}
|
||||
|
||||
func readArticles(from articles: [Article]) -> [Article]? {
|
||||
|
||||
let filteredArticles = articles.filter { $0.status.read }
|
||||
return filteredArticles.isEmpty ? nil : filteredArticles
|
||||
}
|
||||
|
||||
func articles(from sender: Any?) -> [Article]? {
|
||||
|
||||
return (sender as? NSMenuItem)?.representedObject as? [Article]
|
||||
}
|
||||
|
||||
func menu(for articles: [Article]) -> NSMenu? {
|
||||
|
||||
let menu = NSMenu(title: "")
|
||||
|
||||
if articles.anyArticleIsUnread() {
|
||||
menu.addItem(markReadMenuItem(articles))
|
||||
}
|
||||
if articles.anyArticleIsRead() {
|
||||
menu.addItem(markUnreadMenuItem(articles))
|
||||
}
|
||||
if menu.items.count > 0 {
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
if articles.count == 1, let link = articles.first!.preferredLink {
|
||||
menu.addItem(openInBrowserMenuItem(link))
|
||||
}
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
func markReadMenuItem(_ articles: [Article]) -> NSMenuItem {
|
||||
|
||||
return menuItem(NSLocalizedString("Mark as Read", comment: "Command"), #selector(markArticlesReadFromContextualMenu(_:)), articles)
|
||||
}
|
||||
|
||||
func markUnreadMenuItem(_ articles: [Article]) -> NSMenuItem {
|
||||
|
||||
return menuItem(NSLocalizedString("Mark as Unread", comment: "Command"), #selector(markArticlesUnreadFromContextualMenu(_:)), articles)
|
||||
}
|
||||
|
||||
func markStarredMenuItem(_ articles: [Article]) -> NSMenuItem {
|
||||
|
||||
return menuItem(NSLocalizedString("Mark as Starred", comment: "Command"), #selector(markArticlesStarredFromContextualMenu(_:)), articles)
|
||||
}
|
||||
|
||||
func markUnstarredMenuItem(_ articles: [Article]) -> NSMenuItem {
|
||||
|
||||
return menuItem(NSLocalizedString("Mark as Unstarred", comment: "Command"), #selector(markArticlesUnstarredFromContextualMenu(_:)), articles)
|
||||
}
|
||||
|
||||
func openInBrowserMenuItem(_ urlString: String) -> NSMenuItem {
|
||||
|
||||
return menuItem(NSLocalizedString("Open in Browser", comment: "Command"), #selector(openInBrowserFromContextualMenu(_:)), urlString)
|
||||
}
|
||||
|
||||
func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem {
|
||||
|
||||
let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
|
||||
item.representedObject = representedObject
|
||||
item.target = self
|
||||
return item
|
||||
}
|
||||
}
|
|
@ -29,6 +29,16 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
}
|
||||
}
|
||||
|
||||
var articles = ArticleArray() {
|
||||
didSet {
|
||||
if articles != oldValue {
|
||||
clearUndoableCommands()
|
||||
updateShowAvatars()
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var undoableCommands = [UndoableCommand]()
|
||||
private var cellAppearance: TimelineCellAppearance!
|
||||
private var cellAppearanceWithAvatar: TimelineCellAppearance!
|
||||
|
@ -61,16 +71,6 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
|||
}
|
||||
}
|
||||
}
|
||||
private var articles = ArticleArray() {
|
||||
didSet {
|
||||
if articles != oldValue {
|
||||
clearUndoableCommands()
|
||||
updateShowAvatars()
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var fontSize: FontSize = AppDefaults.shared.timelineFontSize {
|
||||
didSet {
|
||||
if fontSize != oldValue {
|
||||
|
|
Loading…
Reference in New Issue