8 Commits
v1.6.0 ... main

Author SHA1 Message Date
relikd
31712dedf9 chore: bump version 2026-01-11 22:11:00 +01:00
relikd
c310933623 feat: restore macOS 10.13 compatibility 2026-01-11 22:00:48 +01:00
relikd
25be1033aa feat: make "Pause updates" optional 2026-01-11 21:42:32 +01:00
relikd
c30d6b82ed feat: "Update feeds" for all menu levels 2026-01-11 21:35:30 +01:00
relikd
26f95c2b13 chore: bump version 2026-01-11 19:36:55 +01:00
relikd
a08898311c fix: reschedule update after system sleep (fixes #26) 2026-01-11 19:33:59 +01:00
relikd
6a5ca09754 fix: limit schedule timer tolerance to 10 min 2026-01-11 19:30:45 +01:00
relikd
1de44071aa fix: schedule update if refresh interval changes 2026-01-11 19:29:09 +01:00
20 changed files with 169 additions and 87 deletions

View File

@@ -5,6 +5,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.6.2] 2026-01-11
### Added
- Restore macOS 10.13 compatibility (without Notification support)
- *Status Bar Menu:* "Update feeds" for all menu levels
- *Status Bar Menu:* "Pause updates" can be removed
## [1.6.1] 2026-01-11
### Fixed
- Reschedule update timer after changing the refresh interval of a feed
- Reschedule update timer after system sleep (fixes #26)
## [1.6.0] 2025-12-13
### Added
- *UI:* Limit content length for article tooltips. (fixes #25)
@@ -261,6 +274,8 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
Initial release
[1.6.2]: https://github.com/relikd/baRSS/compare/v1.6.1...v1.6.2
[1.6.1]: https://github.com/relikd/baRSS/compare/v1.6.0...v1.6.1
[1.6.0]: https://github.com/relikd/baRSS/compare/v1.5.5...v1.6.0
[1.5.5]: https://github.com/relikd/baRSS/compare/v1.5.4...v1.5.5
[1.5.4]: https://github.com/relikd/baRSS/compare/v1.5.3...v1.5.4

View File

@@ -5,8 +5,8 @@ CODE_SIGN_STYLE = Manual
CODE_SIGN_IDENTITY = Apple Development
ENABLE_HARDENED_RUNTIME = YES
MACOSX_DEPLOYMENT_TARGET = 10.14
MARKETING_VERSION = 1.6.0
MACOSX_DEPLOYMENT_TARGET = 10.13
MARKETING_VERSION = 1.6.2
PRODUCT_NAME = baRSS
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS
CURRENT_PROJECT_VERSION = 17752
CURRENT_PROJECT_VERSION = 17804

View File

@@ -1,4 +1,4 @@
[![macOS 10.14+](https://img.shields.io/badge/macOS-10.14+-888)](#download--install)
[![macOS 10.13+](https://img.shields.io/badge/macOS-10.13+-888)](#download--install)
[![Current release](https://img.shields.io/github/release/relikd/baRSS)](https://github.com/relikd/baRSS/releases)
[![All downloads](https://img.shields.io/github/downloads/relikd/baRSS/total)](https://github.com/relikd/baRSS/releases)
[![GitHub license](https://img.shields.io/github/license/relikd/baRSS)](LICENSE)
@@ -35,7 +35,7 @@ Further, tuning the update frequently will decrease the traffic even more.
Download & Install
------------------
Requires macOS Mojave (10.14) or higher.
Requires macOS High Sierra (10.13) or higher.
### Easy way
Go to [releases](https://github.com/relikd/baRSS/releases) and downloaded the latest version.

View File

@@ -47,9 +47,11 @@
if (initial) [UpdateScheduler updateAllFavicons];
}
// Notifications are disabled by default so this wont trigger for first app launch.
// Also, this will register the notification delegate and respond to click & open feed.
[NotifyEndpoint activate];
if (@available(macOS 10.14, *)) {
// Notifications are disabled by default so this wont trigger for first app launch.
// Also, this will register the notification delegate and respond to click & open feed.
[NotifyEndpoint activate];
}
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {

View File

@@ -129,7 +129,9 @@
if (deletingSet.count > 0) {
[localSet minusSet:deletingSet];
[self removeArticles:deletingSet];
[NotifyEndpoint dismiss:dismissed];
if (@available(macOS 10.14, *)) {
[NotifyEndpoint dismiss:dismissed];
}
}
return c;
}

View File

@@ -91,7 +91,9 @@
NSNumber *num = (fa.unread ? @+1 : @-1);
PostNotification(kNotificationTotalUnreadCountChanged, num);
[NotifyEndpoint dismiss:fa.feed.countUnread > 0 ? @[fa.notificationID] : @[fa.notificationID, fa.feed.notificationID]];
if (@available(macOS 10.14, *)) {
[NotifyEndpoint dismiss:fa.feed.countUnread > 0 ? @[fa.notificationID] : @[fa.notificationID, fa.feed.notificationID]];
}
}
[moc reset];
}

View File

@@ -15,7 +15,8 @@ NS_ASSUME_NONNULL_BEGIN
// Feed update
+ (NSDate*)nextScheduledUpdate;
+ (NSArray<Feed*>*)listOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(nullable NSManagedObjectContext*)moc;
+ (NSArray<Feed*>*)feedsThatNeedUpdate:(nullable NSManagedObjectContext*)moc;
+ (NSArray<Feed*>*)feedsWithIndexPath:(nullable NSString*)path inContext:(nullable NSManagedObjectContext*)moc;
// Count elements
+ (BOOL)isEmpty;

View File

@@ -79,14 +79,24 @@
/**
List of @c Feed items that need to be updated. Scheduled time is now (or in past).
@param forceAll If @c YES get a list of all @c Feed regardless of schedules time.
@param moc If @c nil perform requests on main context (ok for reading).
*/
+ (NSArray<Feed*>*)listOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(nullable NSManagedObjectContext*)moc {
+ (NSArray<Feed*>*)feedsThatNeedUpdate:(nullable NSManagedObjectContext*)moc {
NSFetchRequest *fr = [Feed fetchRequest];
if (!forceAll) {
// when fetching also get those feeds that would need update soon (now + 2s)
[fr where:@"meta.scheduled <= %@", [NSDate dateWithTimeIntervalSinceNow:+2]];
// when fetching also get those feeds that would need update soon (now + 2s)
[fr where:@"meta.scheduled <= %@", [NSDate dateWithTimeIntervalSinceNow:+2]];
return [fr fetchAllRows:moc ? moc : [self getMainContext]];
}
/** List of @c Feed items that match @c Feed.indexPath either by direct match or some child thereof.
@param path If @c nil return all @c Feed items. May match either full string OR startswith string + "."
@param moc If @c nil perform requests on main context (ok for reading).
*/
+ (NSArray<Feed*>*)feedsWithIndexPath:(nullable NSString*)path inContext:(nullable NSManagedObjectContext*)moc {
NSFetchRequest *fr = [Feed fetchRequest];
if (path && path.length > 0) {
[fr where:@"indexPath = %@ OR indexPath BEGINSWITH %@", path, [path stringByAppendingString:@"."]];
}
return [fr fetchAllRows:moc ? moc : [self getMainContext]];
}

View File

@@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString*)updatingXFeeds;
// Scheduling
+ (void)scheduleNextFeed;
+ (void)forceUpdateAllFeeds;
+ (void)forceUpdate:(NSString*)indexPath;
+ (void)downloadList:(NSArray<Feed*>*)list userInitiated:(BOOL)flag notifications:(BOOL)notify finally:(nullable os_block_t)block;
+ (void)updateAllFavicons;
// Auto Download & Parse Feed URL

View File

@@ -18,7 +18,6 @@ static NSTimer *_timer;
static SCNetworkReachabilityRef _reachability = NULL;
static BOOL _isReachable = YES;
static BOOL _updatePaused = NO;
static BOOL _nextUpdateIsForced = NO;
static _Atomic(NSUInteger) _queueSize = 0;
@implementation UpdateScheduler
@@ -90,14 +89,9 @@ static _Atomic(NSUInteger) _queueSize = 0;
nextTime = [NSDate dateWithTimeIntervalSinceNow:1];
}
[self scheduleTimer:nextTime];
}
/// Start download of all feeds (immediatelly) regardless of @c .scheduled property.
+ (void)forceUpdateAllFeeds {
if (![self allowNetworkConnection]) // timer will restart once connection exists
return;
_nextUpdateIsForced = YES;
[self scheduleTimer:[NSDate dateWithTimeIntervalSinceNow:0.05]];
#ifdef DEBUG
NSLog(@"schedule next update: %@", nextTime);
#endif
}
/**
@@ -110,28 +104,46 @@ static _Atomic(NSUInteger) _queueSize = 0;
dispatch_once(&onceToken, ^{
_timer = [NSTimer timerWithTimeInterval:NSTimeIntervalSince1970 target:[self class] selector:@selector(updateTimerCallback) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
// technically not the right place to register. But since it is run once, its easier than somewhere else.
[NSWorkspace.sharedWorkspace.notificationCenter addObserver:[self class] selector:@selector(didWakeAfterSleep) name:NSWorkspaceDidWakeNotification object:nil];
});
if (!nextTime)
nextTime = [NSDate distantFuture];
NSTimeInterval tolerance = [nextTime timeIntervalSinceNow] * 0.15;
_timer.tolerance = (tolerance < 1 ? 1 : tolerance); // at least 1 sec
int tolerance = (int)([nextTime timeIntervalSinceNow] * 0.15);
_timer.tolerance = (tolerance < 1 ? 1 : tolerance > 600 ? 600 : tolerance); // at least 1 sec, upto 10 min
_timer.fireDate = nextTime;
PostNotification(kNotificationScheduleTimerChanged, nil);
}
+ (void)didWakeAfterSleep {
#ifdef DEBUG
NSLog(@"did wake from sleep");
#endif
[UpdateScheduler scheduleNextFeed];
}
/// Called when schedule timer runs out (earliest @c .schedule date). Or if forced by user.
+ (void)updateTimerCallback {
#ifdef DEBUG
NSLog(@"fired");
#endif
BOOL updateAll = _nextUpdateIsForced;
_nextUpdateIsForced = NO;
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
NSArray<Feed*> *list = [StoreCoordinator listOfFeedsThatNeedUpdate:updateAll inContext:moc];
//NSAssert(list.count > 0, @"ERROR: Something went wrong, timer fired too early.");
NSArray<Feed*> *list = [StoreCoordinator feedsThatNeedUpdate:moc];
[self update:list userInitiated:NO context:moc];
}
[self downloadList:list userInitiated:updateAll notifications:YES finally:^{
/// Start download of feeds immediatelly, regardless of @c .scheduled property.
+ (void)forceUpdate:(NSString*)indexPath {
if (![self allowNetworkConnection]) // menu item should be disabled anyway
return;
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
NSArray<Feed*> *list = [StoreCoordinator feedsWithIndexPath:indexPath inContext:moc];
[self update:list userInitiated:YES context:moc];
}
/// Helper method for actual download
+ (void)update:(NSArray<Feed*>*)list userInitiated:(BOOL)flag context:(NSManagedObjectContext*)moc {
#ifdef DEBUG
NSLog(@"updating feeds: %ld (%@)", list.count, flag ? @"forced" : @"scheduled");
#endif
[self downloadList:list userInitiated:flag notifications:YES finally:^{
[StoreCoordinator saveContext:moc andParent:YES]; // save parents too ...
[moc reset];
[self scheduleNextFeed]; // always reset the timer
@@ -144,7 +156,7 @@ static _Atomic(NSUInteger) _queueSize = 0;
/// Perform @c FaviconDownload on all core data @c Feed entries.
+ (void)updateAllFavicons {
for (Feed *f in [StoreCoordinator listOfFeedsThatNeedUpdate:YES inContext:nil])
for (Feed *f in [StoreCoordinator feedsWithIndexPath:nil inContext:nil])
[FaviconDownload updateFeed:f finally:nil];
}
@@ -199,26 +211,28 @@ static inline void AlertDownloadError(NSError *err, NSString *url) {
// after save, update notifications
// dismiss previously delivered notifications
if (deleted) {
NSMutableArray *ids = [NSMutableArray array];
for (FeedArticle *article in deleted) { // will contain non-articles too
if ([article isKindOfClass:[FeedArticle class]] || [article isKindOfClass:[Feed class]]) {
[ids addObject:article.notificationID];
if (@available(macOS 10.14, *)) {
if (deleted) {
NSMutableArray *ids = [NSMutableArray array];
for (FeedArticle *article in deleted) { // will contain non-articles too
if ([article isKindOfClass:[FeedArticle class]] || [article isKindOfClass:[Feed class]]) {
[ids addObject:article.notificationID];
}
}
[NotifyEndpoint dismiss:ids]; // no-op if empty
}
[NotifyEndpoint dismiss:ids]; // no-op if empty
}
// post new notification (if needed)
if (notify && inserted) {
BOOL didAddAny = NO;
for (FeedArticle *article in inserted) { // will contain non-articles too
if ([article isKindOfClass:[FeedArticle class]]) {
[NotifyEndpoint postArticle:article];
didAddAny = YES;
// post new notification (if needed)
if (notify && inserted) {
BOOL didAddAny = NO;
for (FeedArticle *article in inserted) { // will contain non-articles too
if ([article isKindOfClass:[FeedArticle class]]) {
[NotifyEndpoint postArticle:article];
didAddAny = YES;
}
}
if (didAddAny)
[NotifyEndpoint postFeed:f];
}
if (didAddAny)
[NotifyEndpoint postFeed:f];
}
if (needsNotification)

View File

@@ -16,8 +16,11 @@
/** default: @c nil */ static NSString* const Pref_notificationType = @"notificationType";
// ------ Appearance matrix ------ (Preferences > Appearance Tab) ------
// menu buttons
/** default: @c YES */ static NSString* const Pref_globalPauseUpdates = @"globalPauseUpdates";
/** default: @c NO */ static NSString* const Pref_globalToggleHidden = @"globalToggleHidden";
/** default: @c YES */ static NSString* const Pref_globalUpdateAll = @"globalUpdateAll";
/** default: @c YES */ static NSString* const Pref_groupUpdateAll = @"groupUpdateAll";
/** default: @c YES */ static NSString* const Pref_feedUpdateAll = @"feedUpdateAll";
/** default: @c YES */ static NSString* const Pref_globalOpenUnread = @"globalOpenUnread";
/** default: @c YES */ static NSString* const Pref_groupOpenUnread = @"groupOpenUnread";
/** default: @c YES */ static NSString* const Pref_feedOpenUnread = @"feedOpenUnread";

View File

@@ -12,7 +12,8 @@ void UserPrefsInit(void) {
NSMutableDictionary *defs = [NSMutableDictionary dictionary];
defaultsAppend(defs, @YES, @[
Pref_globalTintMenuIcon,
Pref_globalUpdateAll,
Pref_globalPauseUpdates,
Pref_globalUpdateAll, Pref_groupUpdateAll, Pref_feedUpdateAll,
Pref_globalOpenUnread, Pref_groupOpenUnread, Pref_feedOpenUnread,
Pref_globalMarkRead, Pref_groupMarkRead, Pref_feedMarkRead,
Pref_globalMarkUnread, Pref_groupMarkUnread, Pref_feedMarkUnread,

View File

@@ -5,6 +5,7 @@
NS_ASSUME_NONNULL_BEGIN
API_AVAILABLE(macos(10.14))
@interface NotifyEndpoint : NSObject <UNUserNotificationCenterDelegate>
+ (void)activate;

View File

@@ -18,6 +18,7 @@ static NSString* const kActionOpenOnly = @"OPEN_ONLY_DONT_MARK_READ";
@implementation NotifyEndpoint
API_AVAILABLE(macos(10.14))
static NotifyEndpoint *singleton = nil;
static NotificationType notifyType;

View File

@@ -49,16 +49,16 @@
RSSImageSettingsFeed, NSLocalizedString(@"Feed menu", nil),
]];
[self entry:NSLocalizedString(@"“Pause updates”", nil)
help:NSLocalizedString(@"Show button to temporarily disable feed updates. E.g., no distrations during focus hours.", nil)
tip:nil
c1:Pref_globalPauseUpdates c2:nil c3:nil c4:nil];
[self entry:NSLocalizedString(@"“Show hidden feeds”", nil)
help:NSLocalizedString(@"Show button to quickly toggle whether hidden entries should be shown. See option “Show only unread”.", nil)
tip:NSLocalizedString(@"You can hold down option-key before opening the main menu to temporarily show all hidden entries.", nil)
c1:Pref_globalToggleHidden c2:nil c3:nil c4:nil];
[self entry:NSLocalizedString(@"“Update all feeds”", nil)
help:NSLocalizedString(@"Show button to reload all feeds. This will force fetch new online content regardless of next-update timer.", nil)
tip:nil
c1:Pref_globalUpdateAll c2:nil c3:nil c4:nil];
[self entry:NSLocalizedString(@"“Open all unread”", nil)
help:NSLocalizedString(@"Show button to open unread articles.", nil)
tip:nil
@@ -74,6 +74,11 @@
tip:NSLocalizedString(@"Alternatively, you can hold down option-key and click on an article to toggle that item (un-)read.", nil)
c1:Pref_globalMarkUnread c2:Pref_groupMarkUnread c3:Pref_feedMarkUnread c4:nil];
[self entry:NSLocalizedString(@"“Update feeds”", nil)
help:NSLocalizedString(@"Show button to reload all feeds. This will force fetch new online content regardless of next-update timer.", nil)
tip:nil
c1:Pref_globalUpdateAll c2:Pref_groupUpdateAll c3:Pref_feedUpdateAll c4:nil];
// self.y += PAD_M;
[self intInput:Pref_openFewLinksLimit
unit:NSLocalizedString(@"%ld unread", nil)

View File

@@ -114,11 +114,15 @@
Interval intv = [NSDate intervalForPopup:self.view.refreshUnit andField:self.view.refreshNum];
[self.feedGroup setNameIfChanged:self.view.name.stringValue];
[f.meta setRefreshIfChanged:intv];
if (self.memFeed) {
if (self.memFeed) { // newly created
[self.memFeed copyValuesTo:f ignoreError:YES];
if (self.faviconFile) // only if downloaded anything (nil deletes icon!)
[f setNewIcon:self.faviconFile];
self.faviconFile = nil;
} else { // updating existing feed meta
if (f.meta.scheduled == nil || f.meta.scheduled.timeIntervalSinceNow > f.meta.refresh) {
[f.meta scheduleNow:f.meta.refresh];
}
}
}

View File

@@ -86,7 +86,9 @@
- (void)changeNotificationType:(NSPopUpButton *)sender {
UserPrefsSet(Pref_notificationType, sender.selectedItem.representedObject);
self.view.notificationHelp.stringValue = [self notificationHelpString:UserPrefsNotificationType()];
[NotifyEndpoint activate];
if (@available(macOS 10.14, *)) {
[NotifyEndpoint activate];
}
}
/// Help string explaining the different notification settings (for the current configuration)

View File

@@ -8,12 +8,12 @@
#import "NotifyEndpoint.h"
#import "NSView+Ext.h"
#import "NSColor+Ext.h"
#import "NSMenu+Ext.h"
@interface BarStatusItem()
@property (strong) BarMenu *barMenu;
@property (strong) NSStatusItem *statusItem;
@property (assign) NSInteger unreadCountTotal;
@property (weak) NSMenuItem *updateAllItem;
/// Set to `true` if user toggled the `"Show hidden feeds"` menu option.
@property (assign) BOOL optShowHidden;
/// Set to `true` if menu bar was opened while holding down option-key.
@@ -49,8 +49,8 @@
/// Fired when network conditions change.
- (void)networkChanged:(NSNotification*)notify {
BOOL available = [[notify object] boolValue];
self.updateAllItem.enabled = available;
[self updateBarIcon];
[self.statusItem.menu recursiveSetNetworkAvailable:available];
}
/// Fired when a single feed has been updated. Object contains relative unread count change.
@@ -77,7 +77,9 @@
NSInteger oldCount = _unreadCountTotal;
_unreadCountTotal = count > 0 ? (NSInteger)count : 0;
[self updateBarIcon];
[NotifyEndpoint setGlobalCount:_unreadCountTotal previousCount:oldCount];
if (@available(macOS 10.14, *)) {
[NotifyEndpoint setGlobalCount:_unreadCountTotal previousCount:oldCount];
}
}
/// Assign new value by adding @c count to total unread count (may be negative).
@@ -88,7 +90,9 @@
_unreadCountTotal = 0;
}
[self updateBarIcon];
[NotifyEndpoint setGlobalCount:_unreadCountTotal previousCount:oldCount];
if (@available(macOS 10.14, *)) {
[NotifyEndpoint setGlobalCount:_unreadCountTotal previousCount:oldCount];
}
}
/// Fetch new total unread count from core data and assign it as new value (dispatch async on main thread).
@@ -179,10 +183,12 @@
- (void)insertMainMenuHeader:(NSMenu*)menu {
// 'Pause Updates' item
NSMenuItem *pause = [menu addItemWithTitle:NSLocalizedString(@"Pause updates", nil) action:@selector(pauseUpdates) keyEquivalent:@""];
pause.target = self;
if ([UpdateScheduler isPaused])
pause.title = NSLocalizedString(@"Resume updates", nil);
if (UserPrefsBool(Pref_globalPauseUpdates)) {
NSMenuItem *pause = [menu addItemWithTitle:NSLocalizedString(@"Pause updates", nil) action:@selector(pauseUpdates) keyEquivalent:@""];
pause.target = self;
if ([UpdateScheduler isPaused])
pause.title = NSLocalizedString(@"Resume updates", nil);
}
// 'show hidden feeds' item
if (UserPrefsBool(Pref_globalToggleHidden)) {
@@ -197,15 +203,10 @@
}
}
// 'Update all feeds' item
if (UserPrefsBool(Pref_globalUpdateAll)) {
NSMenuItem *updateAll = [menu addItemWithTitle:NSLocalizedString(@"Update all feeds", nil) action:@selector(updateAllFeeds) keyEquivalent:@""];
updateAll.target = self;
updateAll.enabled = [UpdateScheduler allowNetworkConnection];
self.updateAllItem = updateAll;
if (menu.numberOfItems > 0) {
// Separator between main header and default header
[menu addItem:[NSMenuItem separatorItem]];
}
// Separator between main header and default header
[menu addItem:[NSMenuItem separatorItem]];
}
/// Called when user clicks on 'Pause Updates' (main menu only).
@@ -220,10 +221,4 @@
self.barMenu.showHidden = self.optShowHidden;
}
/// Called when user clicks on `Update all feeds` (main menu only).
- (void)updateAllFeeds {
// [self asyncReloadUnreadCount]; // should not be necessary
[UpdateScheduler forceUpdateAllFeeds];
}
@end

View File

@@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)insertDefaultHeader;
// Update menu
- (void)setHeaderHasUnread:(UnreadTotal*)count;
- (void)recursiveSetNetworkAvailable:(BOOL)flag;
- (nullable NSMenuItem*)deepestItemWithPath:(nonnull NSString*)path;
@end

View File

@@ -6,12 +6,14 @@
#import "Constants.h"
#import "MapUnreadTotal.h"
#import "NotifyEndpoint.h"
#import "UpdateScheduler.h"
typedef NS_ENUM(NSInteger, MenuItemTag) {
/// Used in @c allowDisplayOfHeaderItem: to identify and enable items
TagMarkAllRead = 1,
TagMarkAllUnread = 2,
TagOpenAllUnread = 3,
TagUpdateFeeds = 4,
/// Delimiter item between default header and core data items
TagHeaderDelimiter = 8,
/// Indicator whether unread count is currently shown in menu item title or not
@@ -83,6 +85,9 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
}
[self addItemIfAllowed:TagMarkAllRead title:NSLocalizedString(@"Mark all read", nil)];
[self addItemIfAllowed:TagMarkAllUnread title:NSLocalizedString(@"Mark all unread", nil)];
[self addItemIfAllowed:TagUpdateFeeds title:self.isFeedMenu ? NSLocalizedString(@"Update feed", nil) : NSLocalizedString(@"Update feeds", nil)]
.enabled = [UpdateScheduler allowNetworkConnection];
if (self.numberOfItems > 0) {
// in case someone has disabled all header items. Else, during articles menu rebuild it will stay on top.
NSMenuItem *sep = [NSMenuItem separatorItem];
@@ -112,6 +117,16 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
}
}
/// Call this method whenever network availability changes to mark "Update feeds" button en-/disabled.
- (void)recursiveSetNetworkAvailable:(BOOL)flag {
[self itemWithTag:TagUpdateFeeds].enabled = flag;
for (NSMenuItem *item in self.itemArray) {
if (item.hasSubmenu) {
[item.submenu recursiveSetNetworkAvailable:flag];
}
}
}
/**
Iterate over all menu items in @c self.itemArray and find the item where @c submenu.title matches
the first @c sortIndex in @c path. Recursively repeat the process for the items of this submenu and so on.
@@ -141,11 +156,13 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
static NSString* const mr[] = {Pref_globalMarkRead, Pref_groupMarkRead, Pref_feedMarkRead};
static NSString* const mu[] = {Pref_globalMarkUnread, Pref_groupMarkUnread, Pref_feedMarkUnread};
static NSString* const ou[] = {Pref_globalOpenUnread, Pref_groupOpenUnread, Pref_feedOpenUnread};
static NSString* const ua[] = {Pref_globalUpdateAll, Pref_groupUpdateAll, Pref_feedUpdateAll};
int i = (self.supermenu == nil ? 0 : (self.isFeedMenu ? 2 : 1));
switch (tag) {
case TagMarkAllRead: return UserPrefsBool(mr[i]);
case TagMarkAllUnread: return UserPrefsBool(mu[i]);
case TagOpenAllUnread: return UserPrefsBool(ou[i]);
case TagUpdateFeeds: return UserPrefsBool(ua[i]);
default: return NO;
}
}
@@ -165,6 +182,10 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
/// Prepare @c userInfo dictionary and send @c NSNotification. Callback for every default header menu item.
+ (void)headerMenuItemCallback:(NSMenuItem*)sender {
if (sender.tag == TagUpdateFeeds) {
[UpdateScheduler forceUpdate:sender.menu.titleIndexPath];
return;
}
BOOL openLinks = NO;
NSUInteger limit = 0;
if (sender.tag == TagOpenAllUnread) {
@@ -185,8 +206,10 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
}
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
NSArray<FeedArticle*> *list = [StoreCoordinator articlesAtPath:path isFeed:isFeedMenu sorted:openLinks unread:markRead inContext:moc limit:limit];
[NotifyEndpoint dismiss:
[StoreCoordinator updateArticles:list markRead:markRead andOpen:openLinks inContext:moc]];
NSArray<NSString *> *notificationIds = [StoreCoordinator updateArticles:list markRead:markRead andOpen:openLinks inContext:moc];
if (@available(macOS 10.14, *)) {
[NotifyEndpoint dismiss:notificationIds];
}
}
@end