Merge remote-tracking branch 'brentsimmons/master'
This commit is contained in:
commit
f563c2f78e
|
@ -103,6 +103,7 @@
|
|||
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 */; };
|
||||
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 */; };
|
||||
|
@ -135,6 +136,8 @@
|
|||
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 */; };
|
||||
84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0EA202F693600562D8F /* DetailWebView.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 */; };
|
||||
|
@ -619,6 +622,7 @@
|
|||
84A37CBA201ECE590087C5AF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Evergreen/Base.lproj/RenameSheet.xib; sourceTree = SOURCE_ROOT; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -644,6 +648,8 @@
|
|||
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>"; };
|
||||
84E8E0EA202F693600562D8F /* DetailWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebView.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>"; };
|
||||
|
@ -962,12 +968,14 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
849A976B1ED9EBC8007D329B /* TimelineViewController.swift */,
|
||||
84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */,
|
||||
84F204DF1FAACBB30076E152 /* ArticleArray.swift */,
|
||||
849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */,
|
||||
849A976A1ED9EBC8007D329B /* TimelineTableView.swift */,
|
||||
844B5B6C1FEA282400C7C76A /* Keyboard */,
|
||||
84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */,
|
||||
8414AD241FCF5A1E00955102 /* TimelineHeaderView.swift */,
|
||||
84AAF2BE202CF684004A0BC4 /* TimelineContextualMenuDelegate.swift */,
|
||||
849A976F1ED9EC04007D329B /* Cell */,
|
||||
);
|
||||
path = Timeline;
|
||||
|
@ -990,6 +998,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
849A977E1ED9EC42007D329B /* DetailViewController.swift */,
|
||||
84E8E0EA202F693600562D8F /* DetailWebView.swift */,
|
||||
849A977D1ED9EC42007D329B /* ArticleRenderer.swift */,
|
||||
84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */,
|
||||
849A979A1ED9EFEB007D329B /* styleSheet.css */,
|
||||
|
@ -1906,6 +1915,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 */,
|
||||
|
@ -1914,6 +1924,7 @@
|
|||
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */,
|
||||
849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */,
|
||||
849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */,
|
||||
84AAF2BF202CF684004A0BC4 /* TimelineContextualMenuDelegate.swift in Sources */,
|
||||
849A97831ED9EC63007D329B /* SidebarStatusBarView.swift in Sources */,
|
||||
84F2D5381FC22FCC00998D64 /* TodayFeedDelegate.swift in Sources */,
|
||||
841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */,
|
||||
|
@ -1933,6 +1944,7 @@
|
|||
D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */,
|
||||
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 */,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14087.3" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<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="14087.3"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14092"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
|
@ -21,9 +21,9 @@
|
|||
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="YMs-P5-Xhn"/>
|
||||
<toolbarItem implicitItemIdentifier="DD0FA79F-72C1-488B-B113-0D2DE89AA468" label="Search" paletteLabel="Search" toolTip="Search Articles" id="1Ql-WJ-KYi">
|
||||
<size key="minSize" width="96" height="22"/>
|
||||
<size key="maxSize" width="256" height="28"/>
|
||||
<size key="maxSize" width="320" height="28"/>
|
||||
<searchField key="view" wantsLayer="YES" verticalHuggingPriority="750" id="Fcs-4u-xuP">
|
||||
<rect key="frame" x="0.0" y="14" width="256" height="22"/>
|
||||
<rect key="frame" x="0.0" y="14" width="320" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="syc-TO-rPc">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -167,25 +167,6 @@
|
|||
<action selector="refreshAll:" target="Oky-zY-oP4" id="KRz-Df-3zA"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="BEE79EC5-174E-40DB-AA76-0F1141C97804" label="Action" paletteLabel="Action" toolTip="Action" image="action" id="eyg-zr-m4a">
|
||||
<size key="minSize" width="38" height="25"/>
|
||||
<size key="maxSize" width="38" height="28"/>
|
||||
<popUpButton key="view" verticalHuggingPriority="750" id="3z3-Db-0Kb">
|
||||
<rect key="frame" x="2" y="14" width="38" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="action" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" pullsDown="YES" selectedItem="NHu-v8-gM6" id="lev-Yv-VWb">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="jk1-Ql-ozP">
|
||||
<items>
|
||||
<menuItem state="on" image="action" hidden="YES" id="NHu-v8-gM6"/>
|
||||
<menuItem title="Item 2" id="1rz-c0-zPY"/>
|
||||
<menuItem title="Item 3" id="9ZT-Uj-OTH"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
</toolbarItem>
|
||||
</allowedToolbarItems>
|
||||
<defaultToolbarItems>
|
||||
<toolbarItem reference="Skp-5r-70Q"/>
|
||||
|
@ -193,13 +174,13 @@
|
|||
<toolbarItem reference="QD0-SQ-OIM"/>
|
||||
<toolbarItem reference="YMs-P5-Xhn"/>
|
||||
<toolbarItem reference="lst-vn-0Iw"/>
|
||||
<toolbarItem reference="Gxg-WQ-ufC"/>
|
||||
<toolbarItem reference="1Ql-WJ-KYi"/>
|
||||
<toolbarItem reference="YMs-P5-Xhn"/>
|
||||
<toolbarItem reference="p7Y-Vm-ILH"/>
|
||||
<toolbarItem reference="Gxg-WQ-ufC"/>
|
||||
<toolbarItem reference="N7D-g2-EPD"/>
|
||||
<toolbarItem reference="tid-SB-me3"/>
|
||||
<toolbarItem reference="nv0-Ju-lP7"/>
|
||||
<toolbarItem reference="YMs-P5-Xhn"/>
|
||||
<toolbarItem reference="1Ql-WJ-KYi"/>
|
||||
</defaultToolbarItems>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="B8D-0N-5wS" id="hrC-d9-Cge"/>
|
||||
|
@ -592,6 +573,7 @@
|
|||
<outlet property="dataSource" destination="36G-bQ-b96" id="OpB-zC-ItJ"/>
|
||||
<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"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
|
@ -623,15 +605,37 @@
|
|||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="contextualMenuDelegate" destination="iD1-KK-gFc" id="b0j-aW-e4B"/>
|
||||
<outlet property="tableView" destination="DRs-j8-R9a" id="2AG-SP-7n2"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="Ebq-4s-EwK" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<menu id="gb5-z4-YPr">
|
||||
<items>
|
||||
<menuItem title="Item 1" id="Ikx-w7-cua">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Item 2" id="QX3-hL-Dqh">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Item 3" id="IsQ-j7-Njb">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="iD1-KK-gFc" id="q4j-wE-tnf"/>
|
||||
</connections>
|
||||
</menu>
|
||||
<customObject id="ZOV-xh-WJE" customClass="TimelineKeyboardDelegate" customModule="Evergreen" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="timelineViewController" destination="36G-bQ-b96" id="rED-2Z-kh6"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<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>
|
||||
|
@ -705,12 +709,12 @@
|
|||
<image name="NSMobileMe" width="32" height="32"/>
|
||||
<image name="NSRefreshTemplate" width="11" height="15"/>
|
||||
<image name="NSShareTemplate" width="11" height="16"/>
|
||||
<image name="action" width="19" height="19"/>
|
||||
<image name="markAllRead" width="22" height="19"/>
|
||||
<image name="markRead" width="19" height="19"/>
|
||||
<image name="newFolder" width="19" height="19"/>
|
||||
<image name="nextUnread" width="24" height="19"/>
|
||||
<image name="openInBrowser" width="19" height="19"/>
|
||||
<image name="star" width="19" height="19"/>
|
||||
<image name="action" width="9.5" height="9.5"/>
|
||||
<image name="markAllRead" width="11" height="9.5"/>
|
||||
<image name="markRead" width="9.5" height="9.5"/>
|
||||
<image name="newFolder" width="9.5" height="9.5"/>
|
||||
<image name="nextUnread" width="12" height="9.5"/>
|
||||
<image name="openInBrowser" width="9.5" height="9.5"/>
|
||||
<image name="star" width="9.5" height="9.5"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
|
@ -16,7 +16,7 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
|
|||
|
||||
@IBOutlet var containerView: DetailContainerView!
|
||||
|
||||
var webview: WKWebView!
|
||||
var webview: DetailWebView!
|
||||
var noSelectionView: NoSelectionView!
|
||||
|
||||
var article: Article? {
|
||||
|
@ -54,7 +54,7 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
|
|||
userContentController.add(self, name: MessageName.mouseDidExit)
|
||||
configuration.userContentController = userContentController
|
||||
|
||||
webview = WKWebView(frame: self.view.bounds, configuration: configuration)
|
||||
webview = DetailWebView(frame: self.view.bounds, configuration: configuration)
|
||||
webview.uiDelegate = self
|
||||
webview.navigationDelegate = self
|
||||
webview.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// DetailWebView.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/10/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import WebKit
|
||||
|
||||
// There’s no API for affecting a WKWebView’s contextual menu.
|
||||
// (WebView had API for this.)
|
||||
//
|
||||
// This a minor hack. It hides unwanted menu items.
|
||||
// The menu item identifiers are not documented anywhere;
|
||||
// they could change, and this code would need updating.
|
||||
|
||||
final class DetailWebView: WKWebView {
|
||||
|
||||
// MARK: NSView
|
||||
|
||||
override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {
|
||||
|
||||
for menuItem in menu.items {
|
||||
if shouldHideMenuItem(menuItem) {
|
||||
menuItem.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
super.willOpenMenu(menu, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
private extension NSUserInterfaceItemIdentifier {
|
||||
|
||||
static let DetailMenuItemIdentifierReload = NSUserInterfaceItemIdentifier(rawValue: "WKMenuItemIdentifierReload")
|
||||
static let DetailMenuItemIdentifierOpenLink = NSUserInterfaceItemIdentifier(rawValue: "WKMenuItemIdentifierOpenLink")
|
||||
}
|
||||
|
||||
private extension DetailWebView {
|
||||
|
||||
static let menuItemIdentifiersToHide: [NSUserInterfaceItemIdentifier] = [.DetailMenuItemIdentifierReload, .DetailMenuItemIdentifierOpenLink]
|
||||
static let menuItemIdentifierMatchStrings = ["newwindow", "download"]
|
||||
|
||||
func shouldHideMenuItem(_ menuItem: NSMenuItem) -> Bool {
|
||||
|
||||
guard let identifier = menuItem.identifier else {
|
||||
return false
|
||||
}
|
||||
|
||||
if DetailWebView.menuItemIdentifiersToHide.contains(identifier) {
|
||||
return true
|
||||
}
|
||||
|
||||
let lowerIdentifier = identifier.rawValue.lowercased()
|
||||
for matchString in DetailWebView.menuItemIdentifierMatchStrings {
|
||||
if lowerIdentifier.contains(matchString) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import AppKit
|
||||
import RSCore
|
||||
|
||||
@objc final class SidebarContextualMenuDelegate: NSObject, NSMenuDelegate {
|
||||
|
||||
|
@ -24,11 +25,7 @@ import AppKit
|
|||
return
|
||||
}
|
||||
|
||||
let items = contextualMenu.items
|
||||
contextualMenu.removeAllItems()
|
||||
for menuItem in items {
|
||||
menu.addItem(menuItem)
|
||||
}
|
||||
menu.takeItems(from: contextualMenu)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// TimelineContextualMenuDelegate.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 2/8/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import RSCore
|
||||
|
||||
@objc final class TimelineContextualMenuDelegate: NSObject, NSMenuDelegate {
|
||||
|
||||
@IBOutlet weak var timelineViewController: TimelineViewController?
|
||||
|
||||
public func menuNeedsUpdate(_ menu: NSMenu) {
|
||||
|
||||
guard let timelineViewController = timelineViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
menu.removeAllItems()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -15,7 +15,8 @@ import Account
|
|||
class TimelineViewController: NSViewController, UndoableCommandRunner {
|
||||
|
||||
@IBOutlet var tableView: TimelineTableView!
|
||||
|
||||
@IBOutlet var contextualMenuDelegate: TimelineContextualMenuDelegate?
|
||||
|
||||
var selectedArticles: [Article] {
|
||||
get {
|
||||
return Array(articles.articlesForIndexes(tableView.selectedRowIndexes))
|
||||
|
@ -28,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!
|
||||
|
@ -60,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 {
|
||||
|
@ -678,11 +679,8 @@ private extension TimelineViewController {
|
|||
|
||||
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())
|
||||
if let articleFetcher = object as? ArticleFetcher {
|
||||
fetchedArticles.formUnion(articleFetcher.fetchArticles())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import RSCore
|
|||
import Data
|
||||
import Account
|
||||
|
||||
protocol SmartFeedDelegate: DisplayNameProvider {
|
||||
protocol SmartFeedDelegate: DisplayNameProvider, ArticleFetcher {
|
||||
|
||||
func fetchUnreadCount(for: Account, callback: @escaping (Int) -> Void)
|
||||
}
|
||||
|
@ -51,6 +51,19 @@ final class SmartFeed: PseudoFeed {
|
|||
}
|
||||
}
|
||||
|
||||
extension SmartFeed: ArticleFetcher {
|
||||
|
||||
func fetchArticles() -> Set<Article> {
|
||||
|
||||
return delegate.fetchArticles()
|
||||
}
|
||||
|
||||
func fetchUnreadArticles() -> Set<Article> {
|
||||
|
||||
return delegate.fetchUnreadArticles()
|
||||
}
|
||||
}
|
||||
|
||||
private extension SmartFeed {
|
||||
|
||||
// MARK: - Unread Counts
|
||||
|
|
|
@ -19,4 +19,18 @@ struct StarredFeedDelegate: SmartFeedDelegate {
|
|||
|
||||
account.fetchUnreadCountForStarredArticles(callback)
|
||||
}
|
||||
|
||||
// MARK: ArticleFetcher
|
||||
|
||||
func fetchArticles() -> Set<Article> {
|
||||
|
||||
// TODO
|
||||
return Set<Article>()
|
||||
}
|
||||
|
||||
func fetchUnreadArticles() -> Set<Article> {
|
||||
|
||||
return fetchArticles().unreadArticles()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,5 +18,21 @@ struct TodayFeedDelegate: SmartFeedDelegate {
|
|||
|
||||
account.fetchUnreadCountForToday(callback)
|
||||
}
|
||||
|
||||
// MARK: ArticleFetcher
|
||||
|
||||
func fetchArticles() -> Set<Article> {
|
||||
|
||||
var articles = Set<Article>()
|
||||
for account in AccountManager.shared.accounts {
|
||||
articles.formUnion(account.fetchTodayArticles())
|
||||
}
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchUnreadArticles() -> Set<Article> {
|
||||
|
||||
return fetchArticles().unreadArticles()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import Data
|
||||
|
||||
// This just shows the global unread count, which appDelegate already has. Easy.
|
||||
|
||||
|
@ -34,3 +36,20 @@ final class UnreadFeed: PseudoFeed {
|
|||
unreadCount = appDelegate.unreadCount
|
||||
}
|
||||
}
|
||||
|
||||
extension UnreadFeed: ArticleFetcher {
|
||||
|
||||
func fetchArticles() -> Set<Article> {
|
||||
|
||||
return fetchUnreadArticles()
|
||||
}
|
||||
|
||||
func fetchUnreadArticles() -> Set<Article> {
|
||||
|
||||
var articles = Set<Article>()
|
||||
for account in AccountManager.shared.accounts {
|
||||
articles.formUnion(account.fetchUnreadArticles())
|
||||
}
|
||||
return articles
|
||||
}
|
||||
}
|
||||
|
|
|
@ -341,14 +341,29 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
return articles
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles() -> Set<Article> {
|
||||
|
||||
return fetchUnreadArticles(forContainer: self)
|
||||
}
|
||||
|
||||
public func fetchArticles(folder: Folder) -> Set<Article> {
|
||||
|
||||
let feeds = folder.flattenedFeeds()
|
||||
return fetchUnreadArticles(forContainer: folder)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles(forContainer container: Container) -> Set<Article> {
|
||||
|
||||
let feeds = container.flattenedFeeds()
|
||||
let articles = database.fetchUnreadArticles(for: feeds)
|
||||
feeds.forEach { validateUnreadCount($0, articles) }
|
||||
return articles
|
||||
}
|
||||
|
||||
public func fetchTodayArticles() -> Set<Article> {
|
||||
|
||||
return database.fetchTodayArticles(for: flattenedFeeds())
|
||||
}
|
||||
|
||||
private func validateUnreadCount(_ feed: Feed, _ articles: Set<Article>) {
|
||||
|
||||
// articles must contain all the unread articles for the feed.
|
||||
|
|
|
@ -75,6 +75,12 @@ public extension Set where Element == Article {
|
|||
|
||||
return Set<String>(map { $0.articleID })
|
||||
}
|
||||
|
||||
public func unreadArticles() -> Set<Article> {
|
||||
|
||||
let articles = self.filter { !$0.status.read }
|
||||
return Set(articles)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Array where Element == Article {
|
||||
|
|
|
@ -72,6 +72,11 @@ final class ArticlesTable: DatabaseTable {
|
|||
return fetchUnreadArticles(feeds.feedIDs())
|
||||
}
|
||||
|
||||
public func fetchTodayArticles(for feeds: Set<Feed>) -> Set<Article> {
|
||||
|
||||
return fetchTodayArticles(feeds.feedIDs())
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
|
||||
func update(_ feed: Feed, _ parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
|
||||
|
@ -328,8 +333,14 @@ private extension ArticlesTable {
|
|||
// * Must not be deleted.
|
||||
// * Must be either 1) starred or 2) dateArrived must be newer than cutoff date.
|
||||
|
||||
let sql = withLimits ? "select * from articles natural join statuses where \(whereClause) and userDeleted=0 and (starred=1 or dateArrived>?);" : "select * from articles natural join statuses where \(whereClause);"
|
||||
return articlesWithSQL(sql, parameters + [articleCutoffDate as AnyObject], database)
|
||||
if withLimits {
|
||||
let sql = "select * from articles natural join statuses where \(whereClause) and userDeleted=0 and (starred=1 or dateArrived>?);"
|
||||
return articlesWithSQL(sql, parameters + [articleCutoffDate as AnyObject], database)
|
||||
}
|
||||
else {
|
||||
let sql = "select * from articles natural join statuses where \(whereClause);"
|
||||
return articlesWithSQL(sql, parameters, database)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUnreadCount(_ feedID: String, _ database: FMDatabase) -> Int {
|
||||
|
@ -369,6 +380,31 @@ private extension ArticlesTable {
|
|||
return articles
|
||||
}
|
||||
|
||||
func fetchTodayArticles(_ 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 (datePublished > ? || (datePublished is null and dateArrived > ?)
|
||||
//
|
||||
// datePublished may be nil, so we fall back to dateArrived.
|
||||
|
||||
let startOfToday = NSCalendar.startOfToday()
|
||||
let parameters = feedIDs.map { $0 as AnyObject } + [startOfToday as AnyObject, startOfToday as AnyObject]
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and userDeleted = 0"
|
||||
// let whereClause = "feedID in \(placeholders) and datePublished > ? 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 {
|
||||
|
|
|
@ -57,6 +57,11 @@ public final class Database {
|
|||
return articlesTable.fetchUnreadArticles(for: feeds)
|
||||
}
|
||||
|
||||
public func fetchTodayArticles(for feeds: Set<Feed>) -> Set<Article> {
|
||||
|
||||
return articlesTable.fetchTodayArticles(for: feeds)
|
||||
}
|
||||
|
||||
// MARK: - Unread Counts
|
||||
|
||||
public func fetchUnreadCounts(for feeds: Set<Feed>, _ completion: @escaping UnreadCountCompletionBlock) {
|
||||
|
|
|
@ -23,7 +23,7 @@ extension Article {
|
|||
let authors = Author.authorsWithParsedAuthors(parsedItem.authors)
|
||||
let attachments = Attachment.attachmentsWithParsedAttachments(parsedItem.attachments)
|
||||
|
||||
self.init(accountID: accountID, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: parsedItem.datePublished, dateModified: parsedItem.dateModified, authors: authors, attachments: attachments, status: status)
|
||||
self.init(accountID: accountID, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: parsedItem.datePublished ?? parsedItem.dateModified, dateModified: parsedItem.dateModified, authors: authors, attachments: attachments, status: status)
|
||||
}
|
||||
|
||||
private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath<Article,String?>, _ otherArticle: Article, _ key: String, _ dictionary: NSMutableDictionary) {
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
84CFF56E1AC3D20A00CEA6C8 /* NSImage+RSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 84CFF56C1AC3D20A00CEA6C8 /* NSImage+RSCore.m */; };
|
||||
84D5BA1E201E87E2009092BD /* URLPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D5BA1D201E87E2009092BD /* URLPasteboardWriter.swift */; };
|
||||
84E34DA61F9FA1070077082F /* UndoableCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E34DA51F9FA1070077082F /* UndoableCommand.swift */; };
|
||||
84E8E0D9202EC39800562D8F /* NSMenu+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0D8202EC39800562D8F /* NSMenu+Extensions.swift */; };
|
||||
84F20F831F16BA6200D8E682 /* PropertyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F20F821F16BA6200D8E682 /* PropertyList.swift */; };
|
||||
84FE9FC31C00453900081CE9 /* NSStoryboard+RSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 84FE9FC11C00453900081CE9 /* NSStoryboard+RSCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
84FE9FC41C00453900081CE9 /* NSStoryboard+RSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 84FE9FC21C00453900081CE9 /* NSStoryboard+RSCore.m */; };
|
||||
|
@ -276,6 +277,7 @@
|
|||
84CFF56C1AC3D20A00CEA6C8 /* NSImage+RSCore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSImage+RSCore.m"; sourceTree = "<group>"; };
|
||||
84D5BA1D201E87E2009092BD /* URLPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLPasteboardWriter.swift; path = AppKit/URLPasteboardWriter.swift; sourceTree = "<group>"; };
|
||||
84E34DA51F9FA1070077082F /* UndoableCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UndoableCommand.swift; path = RSCore/UndoableCommand.swift; sourceTree = "<group>"; };
|
||||
84E8E0D8202EC39800562D8F /* NSMenu+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "NSMenu+Extensions.swift"; path = "AppKit/NSMenu+Extensions.swift"; sourceTree = "<group>"; };
|
||||
84F20F821F16BA6200D8E682 /* PropertyList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyList.swift; sourceTree = "<group>"; };
|
||||
84FE9FC11C00453900081CE9 /* NSStoryboard+RSCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSStoryboard+RSCore.h"; sourceTree = "<group>"; };
|
||||
84FE9FC21C00453900081CE9 /* NSStoryboard+RSCore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSStoryboard+RSCore.m"; sourceTree = "<group>"; };
|
||||
|
@ -454,6 +456,7 @@
|
|||
8415CB891BF84D24007B1E98 /* NSEvent+RSCore.m */,
|
||||
84CFF56B1AC3D20A00CEA6C8 /* NSImage+RSCore.h */,
|
||||
84CFF56C1AC3D20A00CEA6C8 /* NSImage+RSCore.m */,
|
||||
84E8E0D8202EC39800562D8F /* NSMenu+Extensions.swift */,
|
||||
842635581D7FA24800196285 /* NSOutlineView+Extensions.swift */,
|
||||
849B08951BF7BCE30090CEE4 /* NSPasteboard+RSCore.h */,
|
||||
849B08961BF7BCE30090CEE4 /* NSPasteboard+RSCore.m */,
|
||||
|
@ -661,6 +664,7 @@
|
|||
};
|
||||
84CFF4F31AC3C69700CEA6C8 = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
DevelopmentTeam = 9C84TZ7Q6Z;
|
||||
LastSwiftMigration = 0800;
|
||||
};
|
||||
84CFF4FE1AC3C69700CEA6C8 = {
|
||||
|
@ -784,6 +788,7 @@
|
|||
84C687321FBAA3DF00345C9E /* LogWindowController.swift in Sources */,
|
||||
84C687381FBC028900345C9E /* LogItem.swift in Sources */,
|
||||
8432B1861DACA0E90057D6DF /* NSResponder-Extensions.swift in Sources */,
|
||||
84E8E0D9202EC39800562D8F /* NSMenu+Extensions.swift in Sources */,
|
||||
84D5BA1E201E87E2009092BD /* URLPasteboardWriter.swift in Sources */,
|
||||
849B08981BF7BCE30090CEE4 /* NSPasteboard+RSCore.m in Sources */,
|
||||
842635571D7FA1C800196285 /* NSTableView+Extensions.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// NSMenu+Extensions.swift
|
||||
// RSCore
|
||||
//
|
||||
// Created by Brent Simmons on 2/9/18.
|
||||
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
|
||||
public extension NSMenu {
|
||||
|
||||
public func takeItems(from menu: NSMenu) {
|
||||
|
||||
// The passed-in menu gets all its items removed.
|
||||
|
||||
let items = menu.items
|
||||
menu.removeAllItems()
|
||||
for menuItem in items {
|
||||
addItem(menuItem)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,5 +27,6 @@
|
|||
|
||||
- (NSRect)rs_rectCentered:(NSRect)originalRect;
|
||||
|
||||
- (NSTableView *)rs_enclosingTableView;
|
||||
|
||||
@end
|
||||
|
|
|
@ -65,4 +65,18 @@
|
|||
}
|
||||
|
||||
|
||||
- (NSTableView *)rs_enclosingTableView {
|
||||
|
||||
NSView *nomad = self.superview;
|
||||
|
||||
while (nomad != nil) {
|
||||
if ([nomad isKindOfClass:[NSTableView class]]) {
|
||||
return (NSTableView *)nomad;
|
||||
}
|
||||
nomad = nomad.superview;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
@import RSCore;
|
||||
#import "RSMultiLineView.h"
|
||||
#import "RSMultiLineRenderer.h"
|
||||
#import "RSMultiLineRendererMeasurements.h"
|
||||
|
@ -137,6 +138,16 @@ static NSAttributedString *emptyAttributedString = nil;
|
|||
}
|
||||
|
||||
|
||||
- (NSMenu *)menuForEvent:(NSEvent *)event {
|
||||
|
||||
NSTableView *tableView = [self rs_enclosingTableView];
|
||||
if (tableView) {
|
||||
return [tableView menuForEvent:event];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (void)drawRect:(NSRect)r {
|
||||
|
||||
if (self.selected) {
|
||||
|
|
|
@ -124,6 +124,14 @@ static NSAttributedString *emptyAttributedString = nil;
|
|||
return self.intrinsicSize;
|
||||
}
|
||||
|
||||
- (NSMenu *)menuForEvent:(NSEvent *)event {
|
||||
|
||||
NSTableView *tableView = [self rs_enclosingTableView];
|
||||
if (tableView) {
|
||||
return [tableView menuForEvent:event];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)r {
|
||||
|
||||
|
|
Loading…
Reference in New Issue