Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51dd688801 | ||
|
|
352428679d | ||
|
|
9b20262207 | ||
|
|
47a0e76cb3 | ||
|
|
4c9362b42e | ||
|
|
9af191834e | ||
|
|
8d2e4e4383 | ||
|
|
473d4b6057 | ||
|
|
cee3780f71 | ||
|
|
58d7660b87 |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -7,6 +7,28 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [1.1.0] - 2020-01-17
|
||||
### Added
|
||||
- *QuickLook*: Thumbnail previews for OPML files (QLOPML v1.3)
|
||||
- *Status Bar Menu*: Tint menu bar icon with Accent color (macOS 10.14+)
|
||||
|
||||
### Fixed
|
||||
- Resolved Xcode warnings in Xcode 11
|
||||
|
||||
|
||||
## [1.0.2] - 2019-10-25
|
||||
### Fixed
|
||||
- *Status Bar Menu*: Preferences could not be opened on macOS 10.15
|
||||
- *Status Bar Menu*: Menu flickering resulting in a hang on macOS 10.15
|
||||
- *UI*: Text color in `About` tab
|
||||
|
||||
|
||||
## [1.0.1] - 2019-10-04
|
||||
### Fixed
|
||||
- Crash on macOS 10.14 due to a `CGColorRef` null pointer
|
||||
|
||||
|
||||
## [1.0.0] - 2019-10-03
|
||||
### Added
|
||||
- App Signing
|
||||
@@ -115,7 +137,10 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
Initial release
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/relikd/baRSS/compare/v1.0.0...HEAD
|
||||
[Unreleased]: https://github.com/relikd/baRSS/compare/v1.1.0...HEAD
|
||||
[1.1.0]: https://github.com/relikd/baRSS/compare/v1.0.2...v1.1.0
|
||||
[1.0.2]: https://github.com/relikd/baRSS/compare/v1.0.1...v1.0.2
|
||||
[1.0.1]: https://github.com/relikd/baRSS/compare/v1.0.0...v1.0.1
|
||||
[1.0.0]: https://github.com/relikd/baRSS/compare/v0.9.4...v1.0.0
|
||||
[0.9.4]: https://github.com/relikd/baRSS/compare/v0.9.3...v0.9.4
|
||||
[0.9.3]: https://github.com/relikd/baRSS/compare/v0.9.2...v0.9.3
|
||||
|
||||
2
Cartfile
2
Cartfile
@@ -1 +1 @@
|
||||
github "relikd/RSXML2" "v2.0.0"
|
||||
github "relikd/RSXML2" "f1a02fabbdece48e3f7835725c65a7589d98782f"
|
||||
@@ -1 +1 @@
|
||||
github "relikd/RSXML2" "v2.0.0"
|
||||
github "relikd/RSXML2" "f1a02fabbdece48e3f7835725c65a7589d98782f"
|
||||
|
||||
93
README.md
93
README.md
@@ -31,39 +31,6 @@ But it will reuse `ETag` and `Last-Modified` headers to avoid unnecessary transm
|
||||
Further, tuning the update frequently will decrease the traffic even more.
|
||||
|
||||
|
||||
### Why create something that already existed?
|
||||
|
||||
First, open source is awesome!
|
||||
Secondly, RSS Menu made some design decisions I didn't like.
|
||||
For example, the new integrated browser window.
|
||||
|
||||
One thing I liked most, was the fact that feeds were opened in the default browser.
|
||||
Not like 99% of the other feed readers on the market that show a separate HTML viewer window.
|
||||
No rendering issues, no broken links, no content that is different from the actual news article.
|
||||
|
||||
I know, the whole purpose of RSS is to deliver content without the need of opening a webpage.
|
||||
But for me RSS is more about being informed whenever a blog or news feed has some updated content.
|
||||
E.g, subscribing to video channels without having to have an account.
|
||||
|
||||
|
||||
### Why is this project not written in Swift?!
|
||||
|
||||
Actually, I started this project with Swift.
|
||||
Even without adding much functionality, the app was exceeding the 10 Mb file size.
|
||||
The working alpha version, written in Objective-C, had only 500 Kb.
|
||||
The reason being that Swift frameworks are always packed into the final application.
|
||||
|
||||
Sadly, this was before Swift 5 and ABI stability.
|
||||
Had I only started the project a year later…
|
||||
But on the other hand, now it is macOS 10.12 compatible.
|
||||
|
||||
|
||||
### 3rd Party Libraries
|
||||
|
||||
This project uses a modified version of Brent Simmons [RSXML](https://github.com/brentsimmons/RSXML) for feed parsing.
|
||||
RSXML is licensed under a MIT license (same as this project).
|
||||
|
||||
|
||||
|
||||
Download & Install
|
||||
------------------
|
||||
@@ -72,18 +39,18 @@ Requires macOS Sierra (10.12) or higher.
|
||||
|
||||
### Easy way
|
||||
Go to [releases](https://github.com/relikd/baRSS/releases) and downloaded the latest version.
|
||||
Searching for the App Store release? Read this [notice](#app-store-notice).
|
||||
|
||||
### Build from source
|
||||
|
||||
You'll need Xcode and [Carthage](https://github.com/Carthage/Carthage#installing-carthage).
|
||||
The latter is optional, you can build the [RSXML2](https://github.com/relikd/RSXML2) library from source instead.
|
||||
The latter is optional, you can build the [RSXML2] library from source instead.
|
||||
Carthage just makes it more convenient.
|
||||
Download and unzip this project, navigate to the root folder and run `carthage bootstrap --platform macOS`.
|
||||
|
||||
Next, you need to clone [QLOPML](https://github.com/relikd/QLOPML) in the same folder where this project is.
|
||||
Alternatively, you can simply delete the `QLOPML` project reference without much harm.
|
||||
`QLOPML` is a Quick Look plugin for `.opml` files.
|
||||
It will display the file contents whenever you hit space.
|
||||
It will display the file contents whenever you hit spacebar.
|
||||
|
||||
That's it.
|
||||
Open Xcode and build the project.
|
||||
@@ -160,9 +127,61 @@ I may postpone some until demand increases …
|
||||
|
||||
|
||||
|
||||
FAQ / Q&A
|
||||
---------
|
||||
|
||||
### App Store Notice
|
||||
|
||||
In the last couple of months I prepared baRSS to be released on the App Store.
|
||||
With sandboxing enabled and hardened runtime environment, etc.
|
||||
|
||||
But, for the time being, I decided to not publish this app for political reasons.
|
||||
I was not happy about some decisions made in the last weeks.
|
||||
Decisions that were evaluated on monetary aspects and not on ethical considerations.
|
||||
I won't support this conduct with my own money.
|
||||
|
||||
If you find this app somewhere on the App Store, you can be sure that it is a counterfeit.
|
||||
As long as you can read this very notice, I am not responsible for the publication.
|
||||
Further, I can't guarantee the App Store version wasn't modified by a malicious actor to spy on you.
|
||||
|
||||
|
||||
### Why create something that already existed?
|
||||
|
||||
First, open source is awesome!
|
||||
Secondly, RSS Menu made some design decisions I didn't like.
|
||||
For example, the new integrated browser window.
|
||||
|
||||
One thing I liked most, was the fact that feeds were opened in the default browser.
|
||||
Not like 99% of the other feed readers on the market that show a separate HTML viewer window.
|
||||
No rendering issues, no broken links, no content that is different from the actual news article.
|
||||
|
||||
I know, the whole purpose of RSS is to deliver content without the need of opening a webpage.
|
||||
But for me RSS is more about being informed whenever a blog or news feed has some updated content.
|
||||
E.g, subscribing to video channels without having to have an account.
|
||||
|
||||
|
||||
### Why is this project not written in Swift?!
|
||||
|
||||
Actually, I started this project with Swift.
|
||||
Even without adding much functionality, the app was exceeding the 10 Mb file size.
|
||||
The working alpha version, written in Objective-C, had only 500 Kb.
|
||||
The reason being that Swift frameworks are always packed into the final application.
|
||||
|
||||
Sadly, this was before Swift 5 and ABI stability.
|
||||
Had I only started the project a year later…
|
||||
But on the other hand, now it is macOS 10.12 compatible.
|
||||
|
||||
### 3rd Party Libraries
|
||||
|
||||
This project uses a modified version of Brent Simmons' [RSXML](https://github.com/brentsimmons/RSXML) for feed parsing.
|
||||
[RSXML2] is licensed under a MIT license (same as this project).
|
||||
|
||||
|
||||
##### Trivia
|
||||
|
||||
- Start of project: __July 19, 2018__
|
||||
- Estimated development time: __1940h+__
|
||||
- Estimated development time: __1953h+__
|
||||
- First prototype used __feedparser python__ library
|
||||
|
||||
|
||||
[RSXML2]: https://github.com/relikd/RSXML2
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
54BF444A22D0F4F300660096 /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 54BF444922D0F4F300660096 /* AppIcon.icns */; };
|
||||
54D55D7322E624CD00057B98 /* SettingsFeeds+DragDrop.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D55D7222E624CD00057B98 /* SettingsFeeds+DragDrop.m */; };
|
||||
54D857CE227C5785001BA1C8 /* RefreshStatisticsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D857CD227C5785001BA1C8 /* RefreshStatisticsView.m */; };
|
||||
54DD9F1323D1D6B000B1EAA6 /* NSColor+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54DD9F1223D1D6B000B1EAA6 /* NSColor+Ext.m */; };
|
||||
54E3C02122EE076D006E2E24 /* opml-icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 54E3C02022EE076D006E2E24 /* opml-icon.icns */; };
|
||||
54E4446C2329AE0600BBF481 /* NSError+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E4446B2329AE0600BBF481 /* NSError+Ext.m */; };
|
||||
54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E8831E211B509D00064188 /* ModalFeedEdit.m */; };
|
||||
@@ -181,6 +182,8 @@
|
||||
54D857CD227C5785001BA1C8 /* RefreshStatisticsView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RefreshStatisticsView.m; sourceTree = "<group>"; };
|
||||
54D857D022802309001BA1C8 /* SettingsGeneralView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsGeneralView.h; sourceTree = "<group>"; };
|
||||
54D857D122802309001BA1C8 /* SettingsGeneralView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SettingsGeneralView.m; sourceTree = "<group>"; };
|
||||
54DD9F1123D1D6B000B1EAA6 /* NSColor+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSColor+Ext.h"; sourceTree = "<group>"; };
|
||||
54DD9F1223D1D6B000B1EAA6 /* NSColor+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSColor+Ext.m"; sourceTree = "<group>"; };
|
||||
54E3C02022EE076D006E2E24 /* opml-icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = "opml-icon.icns"; sourceTree = "<group>"; };
|
||||
54E4446A2329AE0600BBF481 /* NSError+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+Ext.h"; sourceTree = "<group>"; };
|
||||
54E4446B2329AE0600BBF481 /* NSError+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+Ext.m"; sourceTree = "<group>"; };
|
||||
@@ -226,10 +229,8 @@
|
||||
544936F721F1E51E00DEE9AA /* NSCategories */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54B517052270E8C6006C1B29 /* NSView+Ext.h */,
|
||||
54B517062270E92A006C1B29 /* NSView+Ext.m */,
|
||||
54AD4E0A2301853D000AE386 /* NSString+Ext.h */,
|
||||
54AD4E0B2301853D000AE386 /* NSString+Ext.m */,
|
||||
54DD9F1123D1D6B000B1EAA6 /* NSColor+Ext.h */,
|
||||
54DD9F1223D1D6B000B1EAA6 /* NSColor+Ext.m */,
|
||||
54BB048721FD2AB500C303A5 /* NSDate+Ext.h */,
|
||||
54BB048821FD2AB500C303A5 /* NSDate+Ext.m */,
|
||||
54E4446A2329AE0600BBF481 /* NSError+Ext.h */,
|
||||
@@ -238,6 +239,10 @@
|
||||
548C6D09230C33DE003A1AAF /* NSURL+Ext.m */,
|
||||
54B6F14C23155E1A002C94C9 /* NSURLRequest+Ext.h */,
|
||||
54B6F14D23155E1A002C94C9 /* NSURLRequest+Ext.m */,
|
||||
54AD4E0A2301853D000AE386 /* NSString+Ext.h */,
|
||||
54AD4E0B2301853D000AE386 /* NSString+Ext.m */,
|
||||
54B517052270E8C6006C1B29 /* NSView+Ext.h */,
|
||||
54B517062270E92A006C1B29 /* NSView+Ext.m */,
|
||||
);
|
||||
path = NSCategories;
|
||||
sourceTree = "<group>";
|
||||
@@ -577,6 +582,7 @@
|
||||
544B011D2114EE9100386E5C /* AppHook.m in Sources */,
|
||||
546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */,
|
||||
546A6A2922C583390034E806 /* SettingsGeneralView.m in Sources */,
|
||||
54DD9F1323D1D6B000B1EAA6 /* NSColor+Ext.m in Sources */,
|
||||
54ACC29521061E270020715F /* UpdateScheduler.m in Sources */,
|
||||
54BB048921FD2AB500C303A5 /* NSDate+Ext.m in Sources */,
|
||||
5477D34E21233C62002BA27F /* FeedGroup+Ext.m in Sources */,
|
||||
@@ -702,6 +708,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
@@ -38,8 +36,8 @@
|
||||
ReferencedContainer = "container:baRSS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -49,7 +47,7 @@
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
stopOnEveryMainThreadCheckerIssue = "YES"
|
||||
migratedStopOnEveryIssue = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
@@ -83,8 +81,6 @@
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#import "Feed+CoreDataClass.h"
|
||||
@class RSParsedFeed;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Feed (Ext)
|
||||
@property (readonly) BOOL hasIcon;
|
||||
@property (nonnull, readonly) NSImage* iconImage16;
|
||||
@@ -38,3 +40,5 @@
|
||||
// Article properties
|
||||
- (NSArray<FeedArticle*>*)sortedArticles;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -24,7 +24,11 @@
|
||||
#import "FeedArticle+CoreDataClass.h"
|
||||
@class RSParsedArticle;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FeedArticle (Ext)
|
||||
+ (instancetype)newArticle:(RSParsedArticle*)entry inContext:(NSManagedObjectContext*)moc;
|
||||
- (NSMenuItem*)newMenuItem;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -29,6 +29,7 @@ typedef NS_ENUM(int16_t, FeedGroupType) {
|
||||
GROUP = 0, FEED = 1, SEPARATOR = 2
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FeedGroup (Ext)
|
||||
/// Overwrites @c type attribute with enum. Use one of: @c GROUP, @c FEED, @c SEPARATOR.
|
||||
@@ -39,7 +40,7 @@ typedef NS_ENUM(int16_t, FeedGroupType) {
|
||||
|
||||
+ (instancetype)newGroup:(FeedGroupType)type inContext:(NSManagedObjectContext*)context;
|
||||
+ (instancetype)appendToRoot:(FeedGroupType)type inContext:(NSManagedObjectContext*)moc;
|
||||
- (void)setParent:(FeedGroup *)parent andSortIndex:(int32_t)sortIndex;
|
||||
- (void)setParent:(nullable FeedGroup *)parent andSortIndex:(int32_t)sortIndex;
|
||||
- (void)setSortIndexIfChanged:(int32_t)sortIndex;
|
||||
- (void)setNameIfChanged:(nullable NSString*)name;
|
||||
- (NSMenuItem*)newMenuItem;
|
||||
@@ -50,3 +51,5 @@ typedef NS_ENUM(int16_t, FeedGroupType) {
|
||||
// Printing
|
||||
- (NSString*)readableDescription;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
}
|
||||
|
||||
/// Set @c parent and @c sortIndex. Also if type is @c FEED calculate and set @c indexPath string.
|
||||
- (void)setParent:(FeedGroup *)parent andSortIndex:(int32_t)sortIndex {
|
||||
- (void)setParent:(nullable FeedGroup *)parent andSortIndex:(int32_t)sortIndex {
|
||||
self.parent = parent;
|
||||
self.sortIndex = sortIndex;
|
||||
if (self.type == FEED)
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
static int32_t const kDefaultFeedRefreshInterval = 30 * 60;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FeedMeta (Ext)
|
||||
+ (instancetype)newMetaInContext:(NSManagedObjectContext*)moc;
|
||||
// HTTP response
|
||||
@@ -35,3 +37,5 @@ static int32_t const kDefaultFeedRefreshInterval = 30 * 60;
|
||||
- (void)setRefreshIfChanged:(int32_t)refresh;
|
||||
- (void)scheduleNow:(NSTimeInterval)future;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSFetchRequest<ResultType> (Ext)
|
||||
// Perform core data request and fetch data
|
||||
- (NSArray<ResultType>*)fetchAllRows:(NSManagedObjectContext*)moc;
|
||||
@@ -37,3 +39,5 @@
|
||||
- (instancetype)sortDESC:(NSString*)key; // add .sortDescriptors -> ascending:NO
|
||||
- (instancetype)addFunctionExpression:(NSString*)fn onKeyPath:(NSString*)keyPath name:(NSString*)name type:(NSAttributeType)type; // add .propertiesToFetch -> (expressionForFunction:@[expressionForKeyPath:])
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
@import Cocoa;
|
||||
#import "DBv1+CoreDataModel.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface StoreCoordinator : NSObject
|
||||
// Managing contexts
|
||||
+ (NSManagedObjectContext*)getMainContext;
|
||||
@@ -44,7 +46,7 @@
|
||||
+ (NSArray<NSDictionary*>*)countAggregatedUnread;
|
||||
|
||||
// Get List Of Elements
|
||||
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(id)parent inContext:(nullable NSManagedObjectContext*)moc;
|
||||
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(nullable id)parent inContext:(nullable NSManagedObjectContext*)moc;
|
||||
+ (Feed*)feedWithIndexPath:(nonnull NSString*)path inContext:(nullable NSManagedObjectContext*)moc;
|
||||
+ (NSString*)urlForFeedWithIndexPath:(nonnull NSString*)path;
|
||||
|
||||
@@ -55,3 +57,5 @@
|
||||
+ (void)cleanupAndShowAlert:(BOOL)flag;
|
||||
+ (NSUInteger)cleanupFavicons;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
@param moc If @c nil perform requests on main context (ok for reading).
|
||||
@return Sorted list of @c FeedGroup items where @c FeedGroup.parent @c = @c parent.
|
||||
*/
|
||||
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(id)parent inContext:(nullable NSManagedObjectContext*)moc {
|
||||
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(nullable id)parent inContext:(nullable NSManagedObjectContext*)moc {
|
||||
return [[[[FeedGroup fetchRequest] where:@"parent = %@", parent] sortASC:@"sortIndex"] fetchAllRows:moc ? moc : [self getMainContext]];
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,13 @@
|
||||
|
||||
#define ENV_LOG_YOUTUBE 1
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// TODO: Make plugins extensible? community extensions.
|
||||
@interface YouTubePlugin : NSObject
|
||||
+ (NSString*)feedURL:(NSURL*)url;
|
||||
+ (NSString*)videoImage:(NSString*)videoid;
|
||||
+ (NSString*)videoImageHQ:(NSString*)videoid;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -80,5 +80,10 @@
|
||||
return [NSString stringWithFormat:@"http://i.ytimg.com/vi/%@/hqdefault.jpg", videoid];
|
||||
}
|
||||
|
||||
/// @return @c http://i.ytimg.com/vi/<videoid>/maxresdefault.jpg
|
||||
+ (NSString*)videoImage4k:(NSString*)videoid {
|
||||
return [NSString stringWithFormat:@"http://i.ytimg.com/vi/%@/maxresdefault.jpg", videoid];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
@class Feed, RSHTMLMetadata, FeedDownload;
|
||||
@protocol FaviconDownloadDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FaviconDownload : NSObject
|
||||
/// @c img and @c path are @c nil if image is not valid or couldn't be downloaded.
|
||||
typedef void(^FaviconDownloadBlock)(NSImage * _Nullable img, NSURL * _Nullable path);
|
||||
@@ -45,3 +47,5 @@ typedef void(^FaviconDownloadBlock)(NSImage * _Nullable img, NSURL * _Nullable p
|
||||
/// Called after image download. Called on error, but not if download is cancled.
|
||||
- (void)faviconDownload:(FaviconDownload*)sender didFinish:(nullable NSURL*)path;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
@class RSParsedFeed, RSHTMLMetadataFeedLink, Feed, FaviconDownload;
|
||||
@protocol FeedDownloadDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
All properties will be parsed and stored in local variables.
|
||||
This will avoid unnecessary core data operations if user decides to cancel the edit.
|
||||
@@ -61,3 +63,5 @@ typedef void (^FeedDownloadBlock)(FeedDownload *sender);
|
||||
/// Called after xml data is loaded and parsed. Called on error, but not if download is cancled.
|
||||
- (void)feedDownloadDidFinish:(FeedDownload*)sender;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -28,6 +28,8 @@ typedef NS_OPTIONS(NSUInteger, OpmlFileExportOptions) {
|
||||
OpmlFileExportOptionFullBackup = 1 << 2,
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Protocols
|
||||
|
||||
@protocol OpmlFileImportDelegate <NSObject>
|
||||
@@ -61,3 +63,5 @@ typedef NS_OPTIONS(NSUInteger, OpmlFileExportOptions) {
|
||||
- (void)showExportDialog:(NSWindow*)window;
|
||||
- (nullable NSError*)writeOPMLFile:(NSURL*)url withOptions:(OpmlFileExportOptions)opt;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
@class Feed;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UpdateScheduler : NSObject
|
||||
@property (class, readonly) NSUInteger feedsInQueue;
|
||||
@property (class, readonly) NSDate *dateScheduled;
|
||||
@@ -46,3 +48,5 @@
|
||||
+ (void)registerNetworkChangeNotification;
|
||||
+ (void)unregisterNetworkChangeNotification;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#import "DrawImage.h"
|
||||
#import "Constants.h"
|
||||
#import "UserPrefs.h"
|
||||
#import "NSColor+Ext.h"
|
||||
|
||||
|
||||
@implementation DrawSeparator
|
||||
@@ -312,18 +312,11 @@ static void Register(CGFloat size, NSImageName name, NSString *description, BOOL
|
||||
|
||||
/// Register all icons that require custom drawing in @c ImageNamed cache
|
||||
void RegisterImageViewNames(void) {
|
||||
const CGColorRef black = [NSColor controlTextColor].CGColor;
|
||||
NSColor* const orange = [NSColor colorWithCalibratedRed:251/255.f green:163/255.f blue:58/255.f alpha:1.f]; // #FBA33A
|
||||
|
||||
Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r, orange); return YES; });
|
||||
Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, black, NO); return YES; });
|
||||
Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, black, NO); return YES; });
|
||||
Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, black, NO, YES); return YES; });
|
||||
|
||||
NSColor *c1 = UserPrefsColor(Pref_colorStatusIconTint, orange);
|
||||
NSColor *c2 = UserPrefsColor(Pref_colorUnreadIndicator, [NSColor systemBlueColor]);
|
||||
|
||||
Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, c1.CGColor, YES, YES); return YES; });
|
||||
Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, c1.CGColor, YES, NO); return YES; });
|
||||
Register(14, RSSImageMenuItemUnread, NSLocalizedString(@"Unread icon", nil), ^(NSRect r) { DrawUnreadIcon(r, c2); return YES; });
|
||||
Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r, [NSColor rssOrange]); return YES; });
|
||||
Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, [NSColor controlTextColor].CGColor, NO); return YES; });
|
||||
Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, [NSColor controlTextColor].CGColor, NO); return YES; });
|
||||
Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor controlTextColor].CGColor, NO, YES); return YES; });
|
||||
Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor menuBarIconColor].CGColor, YES, YES); return YES; });
|
||||
Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor menuBarIconColor].CGColor, YES, NO); return YES; });
|
||||
Register(14, RSSImageMenuItemUnread, NSLocalizedString(@"Unread icon", nil), ^(NSRect r) { DrawUnreadIcon(r, [NSColor unreadIndicatorColor]); return YES; });
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>1.1.0</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -70,7 +70,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>14360</string>
|
||||
<string>14603</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.news</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
36
baRSS/NSCategories/NSColor+Ext.h
Normal file
36
baRSS/NSCategories/NSColor+Ext.h
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2020 Oleg Geier
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSColor (Ext)
|
||||
/** @return @c RGB(251,163,58) @c (#FBA33A) */
|
||||
+ (instancetype)rssOrange;
|
||||
/** @return User preferred color; default: @c rssOrange */
|
||||
+ (instancetype)menuBarIconColor;
|
||||
/** @return User preferred color; default: @c systemBlueColor */
|
||||
+ (instancetype)unreadIndicatorColor;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
63
baRSS/NSCategories/NSColor+Ext.m
Normal file
63
baRSS/NSCategories/NSColor+Ext.m
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2020 Oleg Geier
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#import "NSColor+Ext.h"
|
||||
#import "UserPrefs.h"
|
||||
|
||||
@implementation NSColor (Ext)
|
||||
|
||||
+ (instancetype)rssOrange {
|
||||
static NSColor *color;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
color = [NSColor colorWithCalibratedRed:251/255.f green:163/255.f blue:58/255.f alpha:1.f]; // #FBA33A
|
||||
});
|
||||
return color;
|
||||
}
|
||||
|
||||
+ (instancetype)menuBarIconColor {
|
||||
static NSColor *color;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if (@available(macOS 10.14, *)) {
|
||||
color = UserPrefsColor(Pref_colorStatusIconTint, [NSColor controlAccentColor]);
|
||||
} else {
|
||||
color = UserPrefsColor(Pref_colorStatusIconTint, [self rssOrange]);
|
||||
}
|
||||
});
|
||||
return color;
|
||||
}
|
||||
|
||||
+ (instancetype)unreadIndicatorColor {
|
||||
static NSColor *color;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if (@available(macOS 10.14, *)) {
|
||||
color = UserPrefsColor(Pref_colorStatusIconTint, [NSColor controlAccentColor]);
|
||||
} else {
|
||||
color = UserPrefsColor(Pref_colorStatusIconTint, [NSColor systemBlueColor]);
|
||||
}
|
||||
});
|
||||
return color;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -32,6 +32,8 @@ typedef NS_ENUM(int32_t, TimeUnitType) {
|
||||
TimeUnitYears = 365 * 24 * 60 * 60
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSDate (Ext)
|
||||
+ (NSString*)timeStringISO8601;
|
||||
+ (NSString*)dayStringISO8601;
|
||||
@@ -57,3 +59,5 @@ typedef NS_ENUM(int32_t, TimeUnitType) {
|
||||
@interface NSDate (Statistics)
|
||||
+ (NSDictionary*)refreshIntervalStatistics:(NSArray<NSDate*> *)list;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
/// Log error message and prepend calling class and calling method.
|
||||
#define NSLogCaller(desc) { NSLog(@"%@:%@ %@", [self class], NSStringFromSelector(_cmd), desc); }
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSError (Ext)
|
||||
// Generators
|
||||
+ (instancetype)statusCode:(NSInteger)code reason:(nullable NSString*)reason;
|
||||
@@ -35,3 +37,5 @@
|
||||
- (BOOL)inCaseLog:(nullable const char*)title;
|
||||
- (BOOL)inCasePresent:(NSApplication*)app;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSString (PlainHTML)
|
||||
+ (NSString*)plainTextFromHTMLData:(NSData*)data;
|
||||
- (nonnull NSString*)htmlToPlainText;
|
||||
@@ -30,3 +32,5 @@
|
||||
@interface NSString (HexColor)
|
||||
- (nullable NSColor*)hexColor;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
#define ENV_LOG_FILES 0
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSURL (Ext)
|
||||
// Generators
|
||||
+ (NSURL*)applicationSupportURL;
|
||||
@@ -38,3 +40,5 @@
|
||||
- (void)remove;
|
||||
- (void)moveTo:(NSURL*)destination;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -24,8 +24,12 @@
|
||||
|
||||
#define ENV_LOG_DOWNLOAD 1
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSURLRequest (Ext)
|
||||
+ (instancetype)withURL:(NSString*)urlStr;
|
||||
- (NSURLSessionDataTask*)dataTask:(nonnull void(^)(NSData * _Nullable data, NSError * _Nullable error, NSHTTPURLResponse *response))block;
|
||||
- (NSURLSessionDownloadTask*)downloadTask:(void(^)(NSURL * _Nullable path, NSError * _Nullable error))block;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
/// Static variable to calculate origin center coordinate in its @c superview. The value of this var isn't used.
|
||||
static CGFloat const CENTER = -0.015625;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Calculate @c origin.y going down from the top border of its @c superview
|
||||
static inline CGFloat YFromTop(NSView *view) { return NSHeight(view.superview.frame) - NSMinY(view.frame) - view.alignmentRectInsets.bottom; }
|
||||
/// @c MAX()
|
||||
@@ -65,7 +67,7 @@ static inline CGFloat NSMaxWidth(NSView *a, NSView *b) { return Max(NSWidth(a.fr
|
||||
+ (NSButton*)inlineButton:(NSString*)text;
|
||||
+ (NSPopUpButton*)popupButton:(CGFloat)w;
|
||||
// UI: Others
|
||||
+ (NSImageView*)imageView:(NSImageName)name size:(CGFloat)size;
|
||||
+ (NSImageView*)imageView:(nullable NSImageName)name size:(CGFloat)size;
|
||||
+ (NSButton*)checkbox:(BOOL)flag;
|
||||
+ (NSProgressIndicator*)activitySpinner;
|
||||
+ (NSView*)radioGroup:(NSArray<NSString*>*)entries target:(id)target action:(nonnull SEL)action;
|
||||
@@ -91,7 +93,7 @@ static inline CGFloat NSMaxWidth(NSView *a, NSView *b) { return Max(NSWidth(a.fr
|
||||
|
||||
|
||||
@interface NSControl (Ext)
|
||||
- (instancetype)action:(SEL)selector target:(id)target;
|
||||
- (instancetype)action:(SEL)selector target:(nullable id)target;
|
||||
- (instancetype)large;
|
||||
- (instancetype)small;
|
||||
- (instancetype)tiny;
|
||||
@@ -106,3 +108,5 @@ static inline CGFloat NSMaxWidth(NSView *a, NSView *b) { return Max(NSWidth(a.fr
|
||||
- (instancetype)selectable;
|
||||
- (instancetype)multiline:(NSSize)size;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
|
||||
|
||||
/// Create @c ImageView with square @c size
|
||||
+ (NSImageView*)imageView:(NSImageName)name size:(CGFloat)size {
|
||||
+ (NSImageView*)imageView:(nullable NSImageName)name size:(CGFloat)size {
|
||||
NSImageView *imgView = [[NSImageView alloc] initWithFrame: NSMakeRect(0, 0, size, size)];
|
||||
if (name) imgView.image = [NSImage imageNamed:name];
|
||||
return imgView;
|
||||
@@ -142,7 +142,7 @@
|
||||
NSProgressIndicator *spin = [[NSProgressIndicator alloc] initWithFrame: NSMakeRect(0, 0, HEIGHT_SPINNER, HEIGHT_SPINNER)];
|
||||
spin.indeterminate = YES;
|
||||
spin.displayedWhenStopped = NO;
|
||||
spin.style = NSProgressIndicatorSpinningStyle;
|
||||
spin.style = NSProgressIndicatorStyleSpinning;
|
||||
spin.controlSize = NSControlSizeSmall;
|
||||
return spin;
|
||||
}
|
||||
@@ -328,7 +328,7 @@ static inline void SetFrameWidth(NSView *view, CGFloat w) {
|
||||
@implementation NSControl (Ext)
|
||||
|
||||
/// Set @c target and @c action simultaneously
|
||||
- (instancetype)action:(SEL)selector target:(id)target {
|
||||
- (instancetype)action:(SEL)selector target:(nullable id)target {
|
||||
self.action = selector;
|
||||
self.target = target;
|
||||
return self;
|
||||
|
||||
@@ -74,7 +74,8 @@
|
||||
/// Helper method to insert attributed (bold) text
|
||||
- (void)str:(NSMutableAttributedString*)parent add:(NSString*)text bold:(BOOL)flag {
|
||||
NSFont *font = [NSFont systemFontOfSize:NSFont.systemFontSize weight:(flag ? NSFontWeightMedium : NSFontWeightLight)];
|
||||
[parent appendAttributedString:[[NSAttributedString alloc] initWithString:NonLocalized(text) attributes:@{ NSFontAttributeName : font }]];
|
||||
NSDictionary *style = @{ NSFontAttributeName: font, NSForegroundColorAttributeName: [NSColor controlTextColor] };
|
||||
[parent appendAttributedString:[[NSAttributedString alloc] initWithString:NonLocalized(text) attributes:style]];
|
||||
}
|
||||
|
||||
/// Helper method to insert attributed hyperlink text
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsAppearance : NSViewController
|
||||
- (void)didSelectCheckbox:(NSButton*)sender;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#import "ModalSheet.h"
|
||||
@class FeedGroup, ModalSheet;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ModalEditDialog : NSViewController
|
||||
+ (instancetype)modalWith:(FeedGroup*)group;
|
||||
- (ModalSheet*)getModalSheet;
|
||||
@@ -38,3 +40,4 @@
|
||||
@interface ModalGroupEdit : ModalEditDialog
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,23 +23,27 @@
|
||||
@import Cocoa;
|
||||
@class ModalFeedEdit;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ModalFeedEditView : NSView
|
||||
@property (weak) IBOutlet NSTextField *url;
|
||||
@property (weak) IBOutlet NSProgressIndicator *spinnerURL;
|
||||
@property (weak) IBOutlet NSImageView *favicon;
|
||||
@property (strong) IBOutlet NSTextField *url;
|
||||
@property (strong) IBOutlet NSProgressIndicator *spinnerURL;
|
||||
@property (strong) IBOutlet NSImageView *favicon;
|
||||
|
||||
@property (weak) IBOutlet NSTextField *name;
|
||||
@property (weak) IBOutlet NSProgressIndicator *spinnerName;
|
||||
@property (strong) IBOutlet NSTextField *name;
|
||||
@property (strong) IBOutlet NSProgressIndicator *spinnerName;
|
||||
|
||||
@property (weak) IBOutlet NSTextField *refreshNum;
|
||||
@property (weak) IBOutlet NSPopUpButton *refreshUnit;
|
||||
@property (strong) IBOutlet NSTextField *refreshNum;
|
||||
@property (strong) IBOutlet NSPopUpButton *refreshUnit;
|
||||
|
||||
@property (weak) IBOutlet NSButton *warningButton;
|
||||
@property (strong) IBOutlet NSButton *warningButton;
|
||||
@property NSPopover *warningPopover;
|
||||
@property (weak) IBOutlet NSTextField *warningText;
|
||||
@property (weak) IBOutlet NSButton *warningReload;
|
||||
@property (strong) IBOutlet NSTextField *warningText;
|
||||
@property (strong) IBOutlet NSButton *warningReload;
|
||||
|
||||
- (instancetype)initWithController:(ModalFeedEdit*)controller NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol RefreshIntervalButtonDelegate <NSObject>
|
||||
@required
|
||||
/// @c sender.tag is refresh interval in seconds
|
||||
@@ -34,3 +36,5 @@
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
#import "SettingsFeeds.h"
|
||||
#import "OpmlFile.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsFeeds (DragDrop) <NSOutlineViewDataSource, NSFilePromiseProviderDelegate, NSPasteboardTypeOwner, OpmlFileImportDelegate, OpmlFileExportDelegate>
|
||||
- (void)prepareOutlineViewForDragDrop:(NSOutlineView*)outline;
|
||||
- (void)importOpmlFiles:(NSArray<NSURL*>*)files;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -22,10 +22,12 @@
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** Manages the NSOutlineView and Feed creation and editing */
|
||||
@interface SettingsFeeds : NSViewController <NSOutlineViewDelegate>
|
||||
@property (strong) NSTreeController *dataStore;
|
||||
@property (strong) NSArray<NSTreeNode*> *currentlyDraggedNodes;
|
||||
@property (strong, nullable) NSArray<NSTreeNode*> *currentlyDraggedNodes;
|
||||
|
||||
- (void)editSelectedItem;
|
||||
- (void)doubleClickOutlineView:(NSOutlineView*)sender;
|
||||
@@ -40,3 +42,5 @@
|
||||
- (BOOL)endCoreDataChangeUndoEmpty:(BOOL)undoEmpty forceUndo:(BOOL)force;
|
||||
- (void)restoreOrderingAndIndexPathStr:(NSArray<NSTreeNode*>*)parentsList;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,10 +23,12 @@
|
||||
@import Cocoa;
|
||||
@class SettingsFeeds;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsFeedsView : NSView
|
||||
@property (weak) IBOutlet NSOutlineView *outline;
|
||||
@property (weak) IBOutlet NSTextField *status;
|
||||
@property (weak) IBOutlet NSProgressIndicator *spinner;
|
||||
@property (strong) IBOutlet NSOutlineView *outline;
|
||||
@property (strong) IBOutlet NSTextField *status;
|
||||
@property (strong) IBOutlet NSProgressIndicator *spinner;
|
||||
|
||||
- (instancetype)initWithController:(SettingsFeeds*)delegate NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
|
||||
@@ -45,3 +47,5 @@ extern NSUserInterfaceItemIdentifier const CustomCellRefresh;
|
||||
@interface SeparatorColumnCell : NSTableCellView
|
||||
extern NSUserInterfaceItemIdentifier const CustomCellSeparator;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsGeneral : NSViewController
|
||||
- (void)changeHttpApplication:(NSPopUpButton *)sender;
|
||||
- (void)clickHowToDefaults:(NSButton *)sender;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,12 +23,15 @@
|
||||
@import Cocoa;
|
||||
@class SettingsGeneral;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsGeneralView : NSView
|
||||
@property (weak) IBOutlet NSPopUpButton* popupHttpApplication;
|
||||
@property (weak) IBOutlet NSTextField *defaultReader;
|
||||
@property (strong) IBOutlet NSPopUpButton* popupHttpApplication;
|
||||
@property (strong) IBOutlet NSTextField *defaultReader;
|
||||
|
||||
- (instancetype)initWithController:(SettingsGeneral*)controller NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ModalSheet : NSPanel
|
||||
@property (readonly) BOOL didTapCancel;
|
||||
|
||||
@@ -31,3 +33,5 @@
|
||||
- (void)setDoneEnabled:(BOOL)accept;
|
||||
- (void)extendContentViewBy:(CGFloat)dy;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
@import Cocoa;
|
||||
@class SettingsFeeds;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Preferences : NSWindow <NSWindowDelegate>
|
||||
+ (instancetype)window;
|
||||
- (__kindof NSViewController*)selectTab:(NSUInteger)index;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
@import Cocoa;
|
||||
@class BarStatusItem;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface BarMenu : NSObject <NSMenuDelegate>
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithStatusItem:(BarStatusItem*)statusItem NS_DESIGNATED_INITIALIZER;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -82,11 +82,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/// Get rid of everything that is not needed.
|
||||
- (void)menuDidClose:(NSMenu*)menu {
|
||||
[menu cleanup];
|
||||
}
|
||||
|
||||
/// Generate items for @c FeedGroup menu.
|
||||
- (void)setFeedGroups:(NSArray<FeedGroup*>*)sortedList forMenu:(NSMenu*)menu {
|
||||
[menu insertDefaultHeader];
|
||||
@@ -125,17 +120,21 @@
|
||||
Fetch @c Feed from core data and find deepest visible @c NSMenuItem.
|
||||
@warning @c item and @c feed will often mismatch.
|
||||
*/
|
||||
- (void)updateFeedMenuItem:(NSManagedObjectID*)oid withBlock:(void(^)(Feed *feed, NSMenuItem *item))block {
|
||||
Feed *feed = [[StoreCoordinator getMainContext] objectWithID:oid];
|
||||
if ([feed isKindOfClass:[Feed class]]) {
|
||||
NSMenuItem *item = [self.statusItem.mainMenu deepestItemWithPath:feed.indexPath];
|
||||
if (item) block(feed, item);
|
||||
}
|
||||
- (BOOL)findDeepest:(NSManagedObjectID*)oid feed:(Feed*__autoreleasing*)feed menuItem:(NSMenuItem*__autoreleasing*)item {
|
||||
Feed *f = [[StoreCoordinator getMainContext] objectWithID:oid];
|
||||
if (![f isKindOfClass:[Feed class]]) return NO;
|
||||
NSMenuItem *mi = [self.statusItem.mainMenu deepestItemWithPath:f.indexPath];
|
||||
if (!mi) return NO;
|
||||
*feed = f;
|
||||
*item = mi;
|
||||
return YES;
|
||||
}
|
||||
|
||||
/// Callback method fired when feed has been updated in the background.
|
||||
- (void)articlesUpdated:(NSNotification*)notify {
|
||||
[self updateFeedMenuItem:notify.object withBlock:^(Feed *feed, NSMenuItem *item) {
|
||||
Feed *feed;
|
||||
NSMenuItem *item;
|
||||
if ([self findDeepest:notify.object feed:&feed menuItem:&item]) {
|
||||
// 1. update in-memory unread count
|
||||
UnreadTotal *updated = [UnreadTotal new];
|
||||
updated.total = feed.articles.count;
|
||||
@@ -160,15 +159,17 @@
|
||||
[item setTitleCount:uct.unread];
|
||||
item = item.parentItem;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback method fired when feed icon has changed.
|
||||
- (void)feedIconUpdated:(NSNotification*)notify {
|
||||
[self updateFeedMenuItem:notify.object withBlock:^(Feed *feed, NSMenuItem *item) {
|
||||
Feed *feed;
|
||||
NSMenuItem *item;
|
||||
if ([self findDeepest:notify.object feed:&feed menuItem:&item]) {
|
||||
if (item.submenu.isFeedMenu)
|
||||
item.image = [feed iconImage16];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface BarStatusItem : NSObject
|
||||
@property (weak, readonly) NSMenu *mainMenu;
|
||||
|
||||
@@ -32,3 +34,4 @@
|
||||
- (void)showWelcomeMessage;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#import "BarMenu.h"
|
||||
#import "AppHook.h"
|
||||
#import "NSView+Ext.h"
|
||||
#import "NSColor+Ext.h"
|
||||
|
||||
@interface BarStatusItem()
|
||||
@property (strong) BarMenu *barMenu;
|
||||
@@ -44,10 +45,9 @@
|
||||
self = [super init];
|
||||
// Show icon & prefetch unread count
|
||||
self.statusItem = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength];
|
||||
self.statusItem.highlightMode = YES;
|
||||
self.unreadCountTotal = 0;
|
||||
self.statusItem.image = [NSImage imageNamed:RSSImageMenuBarIconActive];
|
||||
self.statusItem.image.template = YES;
|
||||
self.statusItem.button.image = [NSImage imageNamed:RSSImageMenuBarIconActive];
|
||||
self.statusItem.button.image.template = YES;
|
||||
// Add empty menu (will be populated once opened)
|
||||
self.statusItem.menu = [[NSMenu alloc] initWithTitle:@"M"];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mainMenuWillOpen) name:NSMenuDidBeginTrackingNotification object:self.statusItem.menu];
|
||||
@@ -119,11 +119,25 @@
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
BOOL hasNet = [UpdateScheduler allowNetworkConnection];
|
||||
BOOL tint = (self.unreadCountTotal > 0 && hasNet && UserPrefsBool(Pref_globalTintMenuIcon));
|
||||
self.statusItem.image = [NSImage imageNamed:(hasNet ? RSSImageMenuBarIconActive : RSSImageMenuBarIconPaused)];
|
||||
self.statusItem.image.template = !tint;
|
||||
self.statusItem.button.image = [NSImage imageNamed:(hasNet ? RSSImageMenuBarIconActive : RSSImageMenuBarIconPaused)];
|
||||
|
||||
if (@available(macOS 10.14, *)) {
|
||||
// There is no proper way to display tinted icon WITHOUT tinted text!
|
||||
// - using alternate image instead of tint:
|
||||
// icon & text stays black on highlight (but only in light mode)
|
||||
// - using tint and attributed titles:
|
||||
// with controlTextColor the tint is applied regardless
|
||||
// with controlColor the color doesnt match (either normal or on highlight)
|
||||
// also, setting attributed title kills tint on icon
|
||||
self.statusItem.button.image.template = YES;
|
||||
self.statusItem.button.contentTintColor = tint ? [NSColor menuBarIconColor] : nil;
|
||||
} else {
|
||||
self.statusItem.button.image.template = !tint;
|
||||
}
|
||||
|
||||
BOOL showCount = (self.unreadCountTotal > 0 && UserPrefsBool(Pref_globalUnreadCount));
|
||||
self.statusItem.title = (showCount ? [NSString stringWithFormat:@"%ld", self.unreadCountTotal] : @"");
|
||||
self.statusItem.button.title = (showCount ? [NSString stringWithFormat:@"%ld", self.unreadCountTotal] : @"");
|
||||
self.statusItem.button.imagePosition = (showCount ? NSImageLeft : NSImageOnly);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UnreadTotal : NSObject
|
||||
@property (nonatomic, assign) NSUInteger unread;
|
||||
@property (nonatomic, assign) NSUInteger total;
|
||||
@@ -39,3 +41,5 @@
|
||||
- (UnreadTotal*)objectForKeyedSubscript:(NSString*)key;
|
||||
- (void)setObject:(UnreadTotal*)obj forKeyedSubscript:(NSString*)key;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
@import Cocoa;
|
||||
@class FeedGroup;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSMenu (Ext)
|
||||
@property (nonnull, copy, readonly) NSString *titleIndexPath;
|
||||
@property (nullable, readonly) NSMenuItem* parentItem;
|
||||
@@ -33,7 +35,6 @@
|
||||
- (NSMenuItem*)insertFeedGroupItem:(FeedGroup*)fg;
|
||||
- (void)insertDefaultHeader;
|
||||
// Update menu
|
||||
- (void)cleanup;
|
||||
- (void)setHeaderHasUnread:(BOOL)hasUnread hasRead:(BOOL)hasRead;
|
||||
- (NSMenuItem*)deepestItemWithPath:(nonnull NSString*)path;
|
||||
@end
|
||||
@@ -43,3 +44,5 @@
|
||||
- (instancetype)alternateWithTitle:(NSString*)title;
|
||||
- (void)setTitleCount:(NSUInteger)count;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -104,12 +104,6 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
|
||||
|
||||
#pragma mark - Update Menu
|
||||
|
||||
/// Replace this menu with a clean @c NSMenu. Copy old @c title and @c delegate to new menu. @b Won't work without supermenu!
|
||||
- (void)cleanup {
|
||||
NSMenu *m = [[NSMenu alloc] initWithTitle:self.title];
|
||||
m.delegate = self.delegate;
|
||||
self.parentItem.submenu = m;
|
||||
}
|
||||
|
||||
/// Loop over default header and enable 'OpenAllUnread' and 'TagMarkAllRead' based on unread count.
|
||||
- (void)setHeaderHasUnread:(BOOL)hasUnread hasRead:(BOOL)hasRead {
|
||||
|
||||
Reference in New Issue
Block a user