15 Commits

Author SHA1 Message Date
relikd
8de163859b chore: bump version 2025-12-03 15:34:26 +01:00
relikd
f739b64ceb feat: add setting to show "toggle hidden" button 2025-12-03 15:06:56 +01:00
relikd
c2fda881b1 feat: add menu option to toggle hidden articles 2025-12-03 14:48:39 +01:00
relikd
a0a5b5b82d ref: tooltips on options 2025-12-03 14:15:21 +01:00
relikd
43e32b2286 ref: remove tooltip on column icon 2025-12-03 13:43:42 +01:00
relikd
205b544acd fix: undo settings migration
for whatever reason but it breaks Sandbox flag on release build
2025-12-02 20:25:59 +01:00
relikd
56f6ec1356 chore: bump version 2025-12-02 18:52:18 +01:00
relikd
ab71c51380 feat: show hidden articles by holding down option key 2025-12-02 18:48:46 +01:00
relikd
7a805ccdc4 feat: show tooltip for all appearance settings 2025-12-02 18:42:39 +01:00
relikd
f4f4bc9271 fix: annoying negative frame warning 2025-12-01 20:10:44 +01:00
relikd
64637243b5 chore: update recommended settings + xcconfig 2025-12-01 19:41:25 +01:00
relikd
5894b12c1d fix: user provided feed title for notifications (fixes #22) 2025-10-29 18:27:32 +01:00
relikd
0700eebb13 chore: update min OS in readme 2025-10-29 15:19:50 +01:00
relikd
4c4a133fe2 chore: bump version 2025-10-29 15:12:43 +01:00
relikd
ccca329630 feat: notification open options 2025-10-29 15:10:06 +01:00
17 changed files with 234 additions and 59 deletions

View File

@@ -5,6 +5,31 @@ 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). and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.5.5] 2025-12-03
### Added
- *Settings, Appearance:* Improved tooltips on individual options
- *Status Bar Menu:* Toggle button to show hidden articles without holding down option-key.
## [1.5.4] 2025-12-02
### Added
- *Settings, Appearance:* Tooltip explanation for all options
- *Status Bar Menu:* Hold down option key before opening the menu bar icon to show hidden articles (if option "Show only unread" is active)
### Fixed
- *UI:* Table cells were rendered slightly off bounds.
## [1.5.3] 2025-10-29
### Fixed
- *Notifications:* Use user-provided feed title instead of server provided title
## [1.5.2] 2025-10-29
### Added
- *Notifications:* Reply with "Open in background", "Mark read & dismiss", or "Open but keep unread"
## [1.5.1] 2025-10-27 ## [1.5.1] 2025-10-27
### Fixed ### Fixed
- *Status Bar Menu:* Simplified options for "Show only unread" - *Status Bar Menu:* Simplified options for "Show only unread"
@@ -220,6 +245,10 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
Initial release Initial release
[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
[1.5.3]: https://github.com/relikd/baRSS/compare/v1.5.2...v1.5.3
[1.5.2]: https://github.com/relikd/baRSS/compare/v1.5.1...v1.5.2
[1.5.1]: https://github.com/relikd/baRSS/compare/v1.5.0...v1.5.1 [1.5.1]: https://github.com/relikd/baRSS/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/relikd/baRSS/compare/v1.4.1...v1.5.0 [1.5.0]: https://github.com/relikd/baRSS/compare/v1.4.1...v1.5.0
[1.4.1]: https://github.com/relikd/baRSS/compare/v1.4.0...v1.4.1 [1.4.1]: https://github.com/relikd/baRSS/compare/v1.4.0...v1.4.1

6
Config-debug.xcconfig Normal file
View File

@@ -0,0 +1,6 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
#include "Config.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS.beta

12
Config.xcconfig Normal file
View File

@@ -0,0 +1,12 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
CODE_SIGN_STYLE = Manual
CODE_SIGN_IDENTITY = Apple Development
ENABLE_HARDENED_RUNTIME = YES
MACOSX_DEPLOYMENT_TARGET = 10.14
MARKETING_VERSION = 1.5.5
PRODUCT_NAME = baRSS
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS
CURRENT_PROJECT_VERSION = 16970

View File

@@ -1,4 +1,4 @@
[![macOS 10.13+](https://img.shields.io/badge/macOS-10.13+-888)](#download--install) [![macOS 10.14+](https://img.shields.io/badge/macOS-10.14+-888)](#download--install)
[![Current release](https://img.shields.io/github/release/relikd/baRSS)](https://github.com/relikd/baRSS/releases) [![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) [![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) [![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 Download & Install
------------------ ------------------
Requires macOS High Sierra (10.13) or higher. Requires macOS Mojave (10.14) or higher.
### Easy way ### Easy way
Go to [releases](https://github.com/relikd/baRSS/releases) and downloaded the latest version. Go to [releases](https://github.com/relikd/baRSS/releases) and downloaded the latest version.
@@ -69,19 +69,24 @@ Hidden options
baRSS has no option to launch it on start. baRSS has no option to launch it on start.
However, you can still add the application to auto boot by adding it to the system login items: However, you can still add the application to auto boot by adding it to the system login items:
`System Preferences > User > Login Items` (macOS 10-12) `System Preferences > User > Login Items` (macOS 10.x-12)
`System Preferences > General > Login Items & Extensions` (macOS 13+) `System Preferences > General > Login Items & Extensions` (macOS 13+)
### UI options ### UI options
1. If you hold down the option key and click on an article item, you can mark a single item (un-)read without opening it. 1. If you hold down the option key and click on an article item, you can mark a single item (un-)read without opening it.
I am still searching for a way to keep the menu open after click (if you know of a way, let me know!).
2. To add websites without RSS feed you can use the regex converter. 2. To add websites without RSS feed you can use the regex converter.
Hold down the option key in the feed edit modal and click the red regex button. Hold down the option key in the feed edit modal and click the red regex button.
Though, admittedly, this is for experts only. Though, admittedly, this is for experts only.
I still have to find a nice user-friendly way to achieve this. I still have to find a nice user-friendly way to achieve this.
3. The option “Show only unread” will hide all items which have been read.
You can hold down option key before opening the menu bar icon to show hidden articles regardless.
This is a nice way to quickly lookup a hidden article without going into settings and twiddling with the checkbox.
### CLI options ### CLI options

View File

@@ -46,7 +46,7 @@
54AD90EA2E30C48400160925 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54AD90E92E30C48400160925 /* Quartz.framework */; }; 54AD90EA2E30C48400160925 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54AD90E92E30C48400160925 /* Quartz.framework */; };
54AD90EE2E30C48400160925 /* PreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AD90ED2E30C48400160925 /* PreviewViewController.m */; }; 54AD90EE2E30C48400160925 /* PreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AD90ED2E30C48400160925 /* PreviewViewController.m */; };
54AD90F12E30C48400160925 /* PreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54AD90EF2E30C48400160925 /* PreviewViewController.xib */; }; 54AD90F12E30C48400160925 /* PreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54AD90EF2E30C48400160925 /* PreviewViewController.xib */; };
54AD90F72E30C48400160925 /* QLOPML.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 54AD90E72E30C48400160925 /* QLOPML.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 54AD90F72E30C48400160925 /* QLOPML.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 54AD90E72E30C48400160925 /* QLOPML.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
54B51704226DC339006C1B29 /* ModalFeedEditView.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B51703226DC339006C1B29 /* ModalFeedEditView.m */; }; 54B51704226DC339006C1B29 /* ModalFeedEditView.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B51703226DC339006C1B29 /* ModalFeedEditView.m */; };
54B517072270E990006C1B29 /* NSView+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B517062270E92A006C1B29 /* NSView+Ext.m */; }; 54B517072270E990006C1B29 /* NSView+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B517062270E92A006C1B29 /* NSView+Ext.m */; };
54B6F14A231551B3002C94C9 /* FaviconDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B6F149231551B3002C94C9 /* FaviconDownload.m */; }; 54B6F14A231551B3002C94C9 /* FaviconDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B6F149231551B3002C94C9 /* FaviconDownload.m */; };
@@ -105,15 +105,15 @@
name = "Embed Frameworks"; name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
54AD90F62E30C48400160925 /* Embed App Extensions */ = { 54AD90F62E30C48400160925 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
dstPath = ""; dstPath = "";
dstSubfolderSpec = 13; dstSubfolderSpec = 13;
files = ( files = (
54AD90F72E30C48400160925 /* QLOPML.appex in Embed App Extensions */, 54AD90F72E30C48400160925 /* QLOPML.appex in Embed Foundation Extensions */,
); );
name = "Embed App Extensions"; name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
@@ -156,6 +156,8 @@
546A6A2B22C584AF0034E806 /* SettingsAppearanceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsAppearanceView.h; sourceTree = "<group>"; }; 546A6A2B22C584AF0034E806 /* SettingsAppearanceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsAppearanceView.h; sourceTree = "<group>"; };
546A6A2D22C585580034E806 /* SettingsAboutView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsAboutView.m; sourceTree = "<group>"; }; 546A6A2D22C585580034E806 /* SettingsAboutView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsAboutView.m; sourceTree = "<group>"; };
546A6A2E22C585580034E806 /* SettingsAboutView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsAboutView.h; sourceTree = "<group>"; }; 546A6A2E22C585580034E806 /* SettingsAboutView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsAboutView.h; sourceTree = "<group>"; };
546BD1882EDE156000943942 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
546BD1892EDE156000943942 /* Config-debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Config-debug.xcconfig"; sourceTree = "<group>"; };
546FC43B21188AD5007CC3A3 /* SettingsFeeds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsFeeds.h; sourceTree = "<group>"; }; 546FC43B21188AD5007CC3A3 /* SettingsFeeds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsFeeds.h; sourceTree = "<group>"; };
546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsFeeds.m; sourceTree = "<group>"; }; 546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsFeeds.m; sourceTree = "<group>"; };
546FC44021189975007CC3A3 /* SettingsGeneral.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsGeneral.h; sourceTree = "<group>"; }; 546FC44021189975007CC3A3 /* SettingsGeneral.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsGeneral.h; sourceTree = "<group>"; };
@@ -365,6 +367,8 @@
54ACC27321061B3B0020715F = { 54ACC27321061B3B0020715F = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
546BD1882EDE156000943942 /* Config.xcconfig */,
546BD1892EDE156000943942 /* Config-debug.xcconfig */,
540CD14821C094A2004AB594 /* README.md */, 540CD14821C094A2004AB594 /* README.md */,
54892F1D2235285700271CBA /* CHANGELOG.md */, 54892F1D2235285700271CBA /* CHANGELOG.md */,
54ACC27E21061B3B0020715F /* baRSS */, 54ACC27E21061B3B0020715F /* baRSS */,
@@ -536,7 +540,7 @@
54ACC27A21061B3B0020715F /* Resources */, 54ACC27A21061B3B0020715F /* Resources */,
544DCCBB212A2B4D002DBC46 /* Embed Frameworks */, 544DCCBB212A2B4D002DBC46 /* Embed Frameworks */,
54FB05D12305BFAB00A088AD /* dynamic app name in db migration */, 54FB05D12305BFAB00A088AD /* dynamic app name in db migration */,
54AD90F62E30C48400160925 /* Embed App Extensions */, 54AD90F62E30C48400160925 /* Embed Foundation Extensions */,
); );
buildRules = ( buildRules = (
); );
@@ -572,7 +576,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES; BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1640; LastUpgradeCheck = 2600;
ORGANIZATIONNAME = relikd; ORGANIZATIONNAME = relikd;
TargetAttributes = { TargetAttributes = {
54ACC27B21061B3B0020715F = { 54ACC27B21061B3B0020715F = {
@@ -772,6 +776,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
54ACC28E21061B3C0020715F /* Debug */ = { 54ACC28E21061B3C0020715F /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 546BD1892EDE156000943942 /* Config-debug.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
@@ -805,7 +810,6 @@
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 16720;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = UY657LKNHJ; DEVELOPMENT_TEAM = UY657LKNHJ;
@@ -822,8 +826,6 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.5.1;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx; SDKROOT = macosx;
@@ -832,6 +834,7 @@
}; };
54ACC28F21061B3C0020715F /* Release */ = { 54ACC28F21061B3C0020715F /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 546BD1882EDE156000943942 /* Config.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
@@ -866,7 +869,6 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 16720;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = UY657LKNHJ; DEVELOPMENT_TEAM = UY657LKNHJ;
@@ -880,8 +882,6 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.5.1;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
}; };
@@ -931,8 +931,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"$(FRAMEWORK_SEARCH_PATHS)", "$(FRAMEWORK_SEARCH_PATHS)",
); );
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS.beta; PRODUCT_NAME = "$(inherited) Beta";
PRODUCT_NAME = "$(TARGET_NAME) Beta";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
}; };
name = Debug; name = Debug;
@@ -981,8 +980,6 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
"$(FRAMEWORK_SEARCH_PATHS)", "$(FRAMEWORK_SEARCH_PATHS)",
); );
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS;
PRODUCT_NAME = "$(TARGET_NAME)";
}; };
name = Release; name = Release;
}; };
@@ -1003,7 +1000,7 @@
); );
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS.beta.QLOPML; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).QLOPML";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
}; };
@@ -1021,7 +1018,7 @@
"@executable_path/../../../../Frameworks", "@executable_path/../../../../Frameworks",
); );
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS.QLOPML; PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).QLOPML";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
}; };

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1640" LastUpgradeVersion = "2600"
version = "1.8"> version = "1.8">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@@ -225,9 +225,11 @@
return nil; // if success == NO, do not modify unread state & exit return nil; // if success == NO, do not modify unread state & exit
} }
NSInteger countChange = 0;
for (FeedArticle *fa in list) { for (FeedArticle *fa in list) {
if (fa.unread == markRead) { // only if differs if (fa.unread == markRead) { // only if differs
fa.unread = !markRead; fa.unread = !markRead;
countChange += markRead ? -1 : +1;
} }
} }
[self saveContext:moc andParent:YES]; [self saveContext:moc andParent:YES];
@@ -242,8 +244,7 @@
} }
[moc reset]; [moc reset];
NSNumber *num = [NSNumber numberWithInteger: (markRead ? -1 : +1) * (NSInteger)list.count ]; PostNotification(kNotificationTotalUnreadCountChanged, @(countChange));
PostNotification(kNotificationTotalUnreadCountChanged, num);
return dbRefs; return dbRefs;
} }

View File

@@ -17,6 +17,7 @@
// ------ Appearance matrix ------ (Preferences > Appearance Tab) ------ // ------ Appearance matrix ------ (Preferences > Appearance Tab) ------
/** default: @c YES */ static NSString* const Pref_globalTintMenuIcon = @"globalTintMenuBarIcon"; /** default: @c YES */ static NSString* const Pref_globalTintMenuIcon = @"globalTintMenuBarIcon";
/** default: @c YES */ static NSString* const Pref_globalUpdateAll = @"globalUpdateAll"; /** default: @c YES */ static NSString* const Pref_globalUpdateAll = @"globalUpdateAll";
/** default: @c NO */ static NSString* const Pref_globalToggleHidden = @"globalToggleHidden";
/** default: @c YES */ static NSString* const Pref_globalOpenUnread = @"globalOpenUnread"; /** default: @c YES */ static NSString* const Pref_globalOpenUnread = @"globalOpenUnread";
/** default: @c YES */ static NSString* const Pref_globalMarkRead = @"globalMarkRead"; /** default: @c YES */ static NSString* const Pref_globalMarkRead = @"globalMarkRead";
/** default: @c YES */ static NSString* const Pref_globalMarkUnread = @"globalMarkUnread"; /** default: @c YES */ static NSString* const Pref_globalMarkUnread = @"globalMarkUnread";

View File

@@ -20,6 +20,7 @@ void UserPrefsInit(void) {
Pref_feedUnreadIndicator Pref_feedUnreadIndicator
]); ]);
defaultsAppend(defs, @NO, @[ defaultsAppend(defs, @NO, @[
Pref_globalToggleHidden,
Pref_groupUnreadOnly, Pref_feedUnreadOnly, Pref_groupUnreadOnly, Pref_feedUnreadOnly,
Pref_groupUnreadIndicator, Pref_groupUnreadIndicator,
Pref_feedTruncateTitle, Pref_feedTruncateTitle,

View File

@@ -2,6 +2,7 @@
#import "UserPrefs.h" #import "UserPrefs.h"
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "Feed+Ext.h" #import "Feed+Ext.h"
#import "FeedGroup+Ext.h"
#import "FeedArticle+Ext.h" #import "FeedArticle+Ext.h"
/** /**
@@ -9,6 +10,11 @@
*/ */
static NSString* const kNotifyIdGlobal = @"global"; static NSString* const kNotifyIdGlobal = @"global";
static NSString* const kCategoryDismissable = @"DISMISSIBLE";
static NSString* const kActionOpenBackground = @"OPEN_IN_BACKGROUND";
static NSString* const kActionMarkRead = @"MARK_READ_DONT_OPEN";
static NSString* const kActionOpenOnly = @"OPEN_ONLY_DONT_MARK_READ";
@implementation NotifyEndpoint @implementation NotifyEndpoint
@@ -18,20 +24,27 @@ static NotificationType notifyType;
/// Ask user for permission to send notifications @b AND register delegate to respond to alert banner clicks. /// Ask user for permission to send notifications @b AND register delegate to respond to alert banner clicks.
/// @note Called every time user changes notification settings /// @note Called every time user changes notification settings
+ (void)activate { + (void)activate {
UNUserNotificationCenter *center = UNUserNotificationCenter.currentNotificationCenter;
notifyType = UserPrefsNotificationType(); notifyType = UserPrefsNotificationType();
// even if disabled, register delegate. This allows to open previously sent notifications // even if disabled, register delegate. This allows to open previously sent notifications
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
singleton = [NotifyEndpoint new]; singleton = [NotifyEndpoint new];
UNUserNotificationCenter.currentNotificationCenter.delegate = singleton; center.delegate = singleton;
}); });
if (notifyType == NotificationTypeDisabled) { if (notifyType == NotificationTypeDisabled) {
return; return;
} }
// register action types (allow mark read without opening notification)
UNNotificationAction *openBackgroundAction = [UNNotificationAction actionWithIdentifier:kActionOpenBackground title:NSLocalizedString(@"Open in background", nil) options:UNNotificationActionOptionNone];
UNNotificationAction *dontOpenAction = [UNNotificationAction actionWithIdentifier:kActionMarkRead title:NSLocalizedString(@"Mark read & dismiss", nil) options:UNNotificationActionOptionNone];
UNNotificationAction *dontReadAction = [UNNotificationAction actionWithIdentifier:kActionOpenOnly title:NSLocalizedString(@"Open but keep unread", nil) options:UNNotificationActionOptionNone];
UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:kCategoryDismissable actions:@[openBackgroundAction, dontOpenAction, dontReadAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
[center setNotificationCategories:[NSSet setWithObject:category]];
[UNUserNotificationCenter.currentNotificationCenter requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError * _Nullable error) { [center requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (error) { if (error) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
NSAlert *alert = [[NSAlert alloc] init]; NSAlert *alert = [[NSAlert alloc] init];
@@ -72,7 +85,7 @@ static NotificationType notifyType;
if (count > 0) { if (count > 0) {
[feed.managedObjectContext obtainPermanentIDsForObjects:@[feed] error:nil]; [feed.managedObjectContext obtainPermanentIDsForObjects:@[feed] error:nil];
[self send:feed.notificationID [self send:feed.notificationID
title:feed.title title:feed.group.anyName
body:[NSString stringWithFormat:NSLocalizedString(@"%ld unread articles", nil), count]]; body:[NSString stringWithFormat:NSLocalizedString(@"%ld unread articles", nil), count]];
} }
} }
@@ -84,7 +97,7 @@ static NotificationType notifyType;
} }
[article.managedObjectContext obtainPermanentIDsForObjects:@[article] error:nil]; [article.managedObjectContext obtainPermanentIDsForObjects:@[article] error:nil];
[self send:article.notificationID [self send:article.notificationID
title:article.feed.title title:article.feed.group.anyName
body:article.title]; body:article.title];
} }
@@ -105,6 +118,7 @@ static NotificationType notifyType;
if (title != nil) msg.title = title; if (title != nil) msg.title = title;
if (body != nil) msg.body = body; if (body != nil) msg.body = body;
// common settings: // common settings:
msg.categoryIdentifier = kCategoryDismissable;
// TODO: make sound configurable? // TODO: make sound configurable?
msg.sound = [UNNotificationSound defaultSound]; msg.sound = [UNNotificationSound defaultSound];
[self send:identifier content: msg]; [self send:identifier content: msg];
@@ -167,7 +181,12 @@ static NotificationType notifyType;
return; return;
} }
} }
[StoreCoordinator updateArticles:articles markRead:YES andOpen:YES inContext:moc];
// open-in-background performs the same operation as a normal click
// the "background" part is triggered by _NOT_ having the UNNotificationActionOptionForeground option
BOOL dontOpen = [response.actionIdentifier isEqualToString:kActionMarkRead];
BOOL dontMarkRead = [response.actionIdentifier isEqualToString:kActionOpenOnly];
[StoreCoordinator updateArticles:articles markRead:!dontMarkRead andOpen:!dontOpen inContext:moc];
} }
@end @end

View File

@@ -18,29 +18,97 @@
- (instancetype)init { - (instancetype)init {
self = [super initWithFrame:NSMakeRect(0, 0, 320, 327)]; self = [super initWithFrame:NSMakeRect(0, 0, 320, 327)];
// Insert matrix header (icons above checkbox matrix) // Insert matrix header (icons above checkbox matrix)
ColumnIcon(self, X__, RSSImageSettingsGlobal, NSLocalizedString(@"Show in menu bar", nil)); ColumnIcon(self, X__, RSSImageSettingsGlobal);
ColumnIcon(self, _X_, RSSImageSettingsGroup, NSLocalizedString(@"Show in group menu", nil)); ColumnIcon(self, _X_, RSSImageSettingsGroup);
ColumnIcon(self, __X, RSSImageSettingsFeed, NSLocalizedString(@"Show in feed menu", nil)); ColumnIcon(self, __X, RSSImageSettingsFeed);
// Generate checkbox matrix // Generate checkbox matrix
self.y = PAD_WIN + IconSize + PAD_S; self.y = PAD_WIN + IconSize + PAD_S;
[self entry:NSLocalizedString(@"Tint menu bar icon on unread", nil) c1:Pref_globalTintMenuIcon c2:nil c3:nil]; [self entry:NSLocalizedString(@"Tint menu bar icon on unread", nil)
[self entry:NSLocalizedString(@"Update all feeds", nil) c1:Pref_globalUpdateAll c2:nil c3:nil]; help:NSLocalizedString(@"If active, a color will indicate if there are unread articles.", nil)
[self entry:NSLocalizedString(@"Open all unread", nil) c1:Pref_globalOpenUnread c2:Pref_groupOpenUnread c3:Pref_feedOpenUnread]; tip:nil
[self entry:NSLocalizedString(@"Mark all read", nil) c1:Pref_globalMarkRead c2:Pref_groupMarkRead c3:Pref_feedMarkRead]; c1:Pref_globalTintMenuIcon c1tt:NSLocalizedString(@"menu bar icon", nil)
[self entry:NSLocalizedString(@"Mark all unread", nil) c1:Pref_globalMarkUnread c2:Pref_groupMarkUnread c3:Pref_feedMarkUnread]; c2:nil c2tt:nil
[self entry:NSLocalizedString(@"Number of unread articles", nil) c1:Pref_globalUnreadCount c2:Pref_groupUnreadCount c3:Pref_feedUnreadCount]; c3:nil c3tt:nil];
[self entry:NSLocalizedString(@"Indicator for unread articles", nil) c1:nil c2:Pref_groupUnreadIndicator c3:Pref_feedUnreadIndicator];
[self entry:NSLocalizedString(@"Show only unread / hide read", nil) c1:nil c2:Pref_groupUnreadOnly c3:Pref_feedUnreadOnly]; [self entry:NSLocalizedString(@"Update all feeds", nil)
[[self entry:NSLocalizedString(@"Truncate article title", nil) c1:nil c2:nil c3:Pref_feedTruncateTitle] help:NSLocalizedString(@"Show button in main menu to reload all feeds. This will force fetch new online content regardless of next-update timer.", nil)
tooltip:NSLocalizedString(@"Truncate article title after 60 characters", nil)]; tip:nil
[[self entry:NSLocalizedString(@"Limit number of articles", nil) c1:nil c2:nil c3:Pref_feedLimitArticles] c1:Pref_globalUpdateAll c1tt:NSLocalizedString(@"in main menu", nil)
tooltip:NSLocalizedString(@"Display at most 40 articles in feed menu", nil)]; c2:nil c2tt:nil
c3:nil c3tt:nil];
[self entry:NSLocalizedString(@"Toggle “Show Hidden Articles”", nil)
help:NSLocalizedString(@"Show button in main menu to quickly toggle whether hidden articles should be shown. See option “Show only unread”.", nil)
tip:nil
c1:Pref_globalToggleHidden c1tt:NSLocalizedString(@"in main menu", nil)
c2:nil c2tt:nil
c3:nil c3tt:nil];
[self entry:NSLocalizedString(@"Open all unread", nil)
help:NSLocalizedString(@"Show button to open unread articles.", nil)
tip:NSLocalizedString(@"If you hold down option-key, this will become an “open a few” unread articles button.", nil)
c1:Pref_globalOpenUnread c1tt: NSLocalizedString(@"in main menu", nil)
c2:Pref_groupOpenUnread c2tt: NSLocalizedString(@"in group menu", nil)
c3:Pref_feedOpenUnread c3tt: NSLocalizedString(@"in feed menu", nil)];
[self entry:NSLocalizedString(@"Mark all read", nil)
help:NSLocalizedString(@"Show button to mark articles read.", nil)
tip:nil
c1:Pref_globalMarkRead c1tt: NSLocalizedString(@"in main menu", nil)
c2:Pref_groupMarkRead c2tt: NSLocalizedString(@"in group menu", nil)
c3:Pref_feedMarkRead c3tt: NSLocalizedString(@"in feed menu", nil)];
[self entry:NSLocalizedString(@"Mark all unread", nil)
help:NSLocalizedString(@"Show button to mark articles unread.", nil)
tip:NSLocalizedString(@"You can hold down option-key and click on an article to toggle that item (un-)read.", nil)
c1:Pref_globalMarkUnread c1tt: NSLocalizedString(@"in main menu", nil)
c2:Pref_groupMarkUnread c2tt: NSLocalizedString(@"in group menu", nil)
c3:Pref_feedMarkUnread c3tt: NSLocalizedString(@"in feed menu", nil)];
[self entry:NSLocalizedString(@"Number of unread articles", nil)
help:NSLocalizedString(@"Show count of unread articles in parenthesis.", nil)
tip:nil
c1:Pref_globalUnreadCount c1tt:NSLocalizedString(@"on menu bar icon", nil)
c2:Pref_groupUnreadCount c2tt:NSLocalizedString(@"on group folder", nil)
c3:Pref_feedUnreadCount c3tt:NSLocalizedString(@"on feed folder", nil)];
[self entry:NSLocalizedString(@"Indicator for unread articles", nil)
help:NSLocalizedString(@"Show blue dot on menu items with unread articles.", nil)
tip:nil
c1:nil c1tt:nil
c2:Pref_groupUnreadIndicator c2tt:NSLocalizedString(@"on group & feed folder", nil)
c3:Pref_feedUnreadIndicator c3tt:NSLocalizedString(@"on article entry", nil)];
[self entry:NSLocalizedString(@"Show only unread", nil)
help:NSLocalizedString(@"Hide articles which have been read.", nil)
tip:NSLocalizedString(@"You can hold down option-key before opening the main menu to temporarily disable this setting.", nil)
c1:nil c1tt:nil
c2:Pref_groupUnreadOnly c2tt:NSLocalizedString(@"hide group & feed folders with 0 unread articles", nil)
c3:Pref_feedUnreadOnly c3tt:NSLocalizedString(@"hide articles inside of feed folder", nil)];
[self entry:NSLocalizedString(@"Truncate article title", nil)
help:NSLocalizedString(@"Truncate article title after 60 characters. If a title is longer than that, show an ellipsis character “…” instead.", nil)
tip:nil
c1:nil c1tt:nil
c2:nil c2tt:nil
c3:Pref_feedTruncateTitle c3tt:NSLocalizedString(@"article title", nil)];
[self entry:NSLocalizedString(@"Limit number of articles", nil)
help:NSLocalizedString(@"Display at most 40 articles in feed menu. Remaining articles will be hidden from view but are still there. Unread count may be confusing as it will also count unread and hidden articles.", nil)
tip:nil
c1:nil c1tt:nil
c2:nil c2tt:nil
c3:Pref_feedLimitArticles c3tt:NSLocalizedString(@"in feed menu", nil)];
[[[[[NSView label:@"Note: you can hover over all options to display explanatory tooltips."]
multiline:NSMakeSize(100, 2 * HEIGHT_LABEL)] gray]
placeIn:self x:PAD_WIN yTop:self.y + PAD_L] sizeToRight:PAD_WIN];
return self; return self;
} }
/// Helper method for matrix table header icons /// Helper method for matrix table header icons
static inline void ColumnIcon(id this, CGFloat x, const NSImageName img, NSString *ttip) { static inline void ColumnIcon(id this, CGFloat x, const NSImageName img) {
[[[NSView imageView:img size:IconSize] placeIn:this x:x yTop:PAD_WIN] tooltip:ttip]; [[NSView imageView:img size:IconSize] placeIn:this x:x yTop:PAD_WIN];
} }
/// Helper method for generating a checkbox /// Helper method for generating a checkbox
@@ -51,14 +119,22 @@ static inline NSButton* Checkbox(id this, CGFloat x, CGFloat y, NSString *key) {
} }
/// Create new entry with 1-3 checkboxes and a descriptive label /// Create new entry with 1-3 checkboxes and a descriptive label
- (NSTextField*)entry:(NSString*)label c1:(NSString*)pref1 c2:(NSString*)pref2 c3:(NSString*)pref3 { - (NSTextField*)entry:(NSString*)label help:(NSString*)ttip tip:(NSString*)extraTip
c1:(NSString*)pref1 c1tt:(NSString*)ttip1
c2:(NSString*)pref2 c2tt:(NSString*)ttip2
c3:(NSString*)pref3 c3tt:(NSString*)ttip3
{
CGFloat y = self.y; CGFloat y = self.y;
self.y += (PAD_S + HEIGHT_LABEL); self.y += (PAD_S + HEIGHT_LABEL);
// TODO: localize: global, group, feed // TODO: localize: global, group, feed
if (pref1) Checkbox(self, X__ + 2, y + 2, pref1).accessibilityLabel = [label stringByAppendingString:@" (global)"]; if (pref1) [Checkbox(self, X__ + 2, y + 2, pref1) tooltip:ttip1].accessibilityLabel = [label stringByAppendingString:@" (global)"];
if (pref2) Checkbox(self, _X_ + 2, y + 2, pref2).accessibilityLabel = [label stringByAppendingString:@" (group)"]; if (pref2) [Checkbox(self, _X_ + 2, y + 2, pref2) tooltip:ttip2].accessibilityLabel = [label stringByAppendingString:@" (group)"];
if (pref3) Checkbox(self, __X + 2, y + 2, pref3).accessibilityLabel = [label stringByAppendingString:@" (feed)"]; if (pref3) [Checkbox(self, __X + 2, y + 2, pref3) tooltip:ttip3].accessibilityLabel = [label stringByAppendingString:@" (feed)"];
return [[[NSView label:label] placeIn:self x:PAD_WIN + 3 * colWidth yTop:y] sizeToRight:PAD_WIN]; if (extraTip != nil) {
label = [label stringByAppendingString:@" *"];
ttip = [ttip stringByAppendingFormat:@"\n\nTip: %@", extraTip];
}
return [[[[NSView label:label] placeIn:self x:PAD_WIN + 3 * colWidth yTop:y] sizeToRight:PAD_WIN] tooltip:ttip];
} }
@end @end

View File

@@ -170,7 +170,7 @@
NSUserInterfaceItemIdentifier const CustomCellName = @"NameColumnCell"; NSUserInterfaceItemIdentifier const CustomCellName = @"NameColumnCell";
- (instancetype)initWithFrame:(NSRect)frameRect { - (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect]; self = [super initWithFrame:NSMakeRect(0, 0, 100, 0)];
self.identifier = CustomCellName; self.identifier = CustomCellName;
self.imageView = [[NSView imageView:nil size:16] placeIn:self x:1 yTop:1]; self.imageView = [[NSView imageView:nil size:16] placeIn:self x:1 yTop:1];
self.imageView.accessibilityLabel = NSLocalizedString(@"Feed icon", nil); self.imageView.accessibilityLabel = NSLocalizedString(@"Feed icon", nil);
@@ -195,7 +195,7 @@ NSUserInterfaceItemIdentifier const CustomCellName = @"NameColumnCell";
NSUserInterfaceItemIdentifier const CustomCellRefresh = @"RefreshColumnCell"; NSUserInterfaceItemIdentifier const CustomCellRefresh = @"RefreshColumnCell";
- (instancetype)initWithFrame:(NSRect)frameRect { - (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect]; self = [super initWithFrame:NSMakeRect(0, 0, 100, 0)];
self.identifier = CustomCellRefresh; self.identifier = CustomCellRefresh;
self.textField = [[[[NSView label:@""] textRight] placeIn:self x:0 yTop:0] sizeToRight:0]; self.textField = [[[[NSView label:@""] textRight] placeIn:self x:0 yTop:0] sizeToRight:0];
self.textField.accessibilityTitle = @" "; // otherwise groups and separators will say 'text' self.textField.accessibilityTitle = @" "; // otherwise groups and separators will say 'text'
@@ -224,7 +224,7 @@ NSUserInterfaceItemIdentifier const CustomCellRefresh = @"RefreshColumnCell";
NSUserInterfaceItemIdentifier const CustomCellSeparator = @"SeparatorColumnCell"; NSUserInterfaceItemIdentifier const CustomCellSeparator = @"SeparatorColumnCell";
- (instancetype)initWithFrame:(NSRect)frameRect { - (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect]; self = [super initWithFrame:NSMakeRect(0, 0, 100, 0)];
self.identifier = CustomCellSeparator; self.identifier = CustomCellSeparator;
[[[[DrawSeparator alloc] initWithFrame:self.frame] placeIn:self x:0 y:0] sizableWidthAndHeight]; [[[[DrawSeparator alloc] initWithFrame:self.frame] placeIn:self x:0 y:0] sizableWidthAndHeight];
return self; return self;

View File

@@ -4,6 +4,7 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface BarMenu : NSObject <NSMenuDelegate> @interface BarMenu : NSObject <NSMenuDelegate>
@property (assign) BOOL showHidden;
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithStatusItem:(BarStatusItem*)statusItem NS_DESIGNATED_INITIALIZER; - (instancetype)initWithStatusItem:(BarStatusItem*)statusItem NS_DESIGNATED_INITIALIZER;
@end @end

View File

@@ -64,7 +64,7 @@
- (void)setFeedGroups:(NSArray<FeedGroup*>*)sortedList forMenu:(NSMenu*)menu { - (void)setFeedGroups:(NSArray<FeedGroup*>*)sortedList forMenu:(NSMenu*)menu {
[menu insertDefaultHeader]; [menu insertDefaultHeader];
for (FeedGroup *fg in sortedList) { for (FeedGroup *fg in sortedList) {
[menu insertFeedGroupItem:fg withUnread:self.unreadMap].submenu.delegate = self; [menu insertFeedGroupItem:fg withUnread:self.unreadMap showHidden:_showHidden].submenu.delegate = self;
} }
[menu setHeaderHasUnread:self.unreadMap[menu.titleIndexPath]]; [menu setHeaderHasUnread:self.unreadMap[menu.titleIndexPath]];
} }
@@ -78,7 +78,7 @@
BOOL onlyUnread = UserPrefsBool(Pref_feedUnreadOnly); BOOL onlyUnread = UserPrefsBool(Pref_feedUnreadOnly);
for (FeedArticle *fa in sortedList) { for (FeedArticle *fa in sortedList) {
if (onlyUnread && !fa.unread) if (onlyUnread && !fa.unread && !_showHidden)
continue; continue;
if (--mc < 0) // mc == 0 will first decrement to -1, then evaluate if (--mc < 0) // mc == 0 will first decrement to -1, then evaluate
break; break;

View File

@@ -14,6 +14,10 @@
@property (strong) NSStatusItem *statusItem; @property (strong) NSStatusItem *statusItem;
@property (assign) NSInteger unreadCountTotal; @property (assign) NSInteger unreadCountTotal;
@property (weak) NSMenuItem *updateAllItem; @property (weak) NSMenuItem *updateAllItem;
/// Set to `true` if user toggled the `"Show Hidden Articles"` menu option.
@property (assign) BOOL optShowHidden;
/// Set to `true` if menu bar was opened while holding down option-key.
@property (assign) BOOL holdingOptKey;
@end @end
@implementation BarStatusItem @implementation BarStatusItem
@@ -150,8 +154,10 @@
#pragma mark - Main Menu Handling #pragma mark - Main Menu Handling
-(void)menuWillOpen:(NSMenu *)menu { -(void)menuWillOpen:(NSMenu *)menu {
self.holdingOptKey = NSEvent.modifierFlags & NSEventModifierFlagOption;
_mainMenu = menu; // autoreleased once closed _mainMenu = menu; // autoreleased once closed
self.barMenu = [[BarMenu alloc] initWithStatusItem:self]; self.barMenu = [[BarMenu alloc] initWithStatusItem:self];
self.barMenu.showHidden = self.optShowHidden || self.holdingOptKey;
[self insertMainMenuHeader:menu]; [self insertMainMenuHeader:menu];
[self.barMenu menuNeedsUpdate:menu]; [self.barMenu menuNeedsUpdate:menu];
@@ -165,6 +171,7 @@
self.barMenu = nil; self.barMenu = nil;
self.statusItem.menu = [[NSMenu alloc] initWithTitle:@"M"]; self.statusItem.menu = [[NSMenu alloc] initWithTitle:@"M"];
self.statusItem.menu.delegate = self; self.statusItem.menu.delegate = self;
self.holdingOptKey = NO;
} }
- (void)insertMainMenuHeader:(NSMenu*)menu { - (void)insertMainMenuHeader:(NSMenu*)menu {
@@ -173,6 +180,20 @@
pause.target = self; pause.target = self;
if ([UpdateScheduler isPaused]) if ([UpdateScheduler isPaused])
pause.title = NSLocalizedString(@"Resume Updates", nil); pause.title = NSLocalizedString(@"Resume Updates", nil);
// 'show hidden articles' item
if (UserPrefsBool(Pref_globalToggleHidden)) {
NSMenuItem *toggleHidden = [menu addItemWithTitle:NSLocalizedString(@"Show Hidden Articles", nil) action:@selector(toggleHiddenArticles) keyEquivalent:@"h"];
toggleHidden.target = self;
toggleHidden.enabled = !self.holdingOptKey && (UserPrefsBool(Pref_groupUnreadOnly) || UserPrefsBool(Pref_feedUnreadOnly));
[toggleHidden setState:self.barMenu.showHidden ? NSControlStateValueOn : NSControlStateValueOff];
if (!toggleHidden.enabled) {
toggleHidden.toolTip = self.holdingOptKey
? NSLocalizedString(@"Option disabled because overwritten by holding down option-key.", nil)
: NSLocalizedString(@"Option disabled because appearance setting for “Show only unread” is disabled.", nil);
}
}
// 'Update all feeds' item // 'Update all feeds' item
if (UserPrefsBool(Pref_globalUpdateAll)) { if (UserPrefsBool(Pref_globalUpdateAll)) {
NSMenuItem *updateAll = [menu addItemWithTitle:NSLocalizedString(@"Update all feeds", nil) action:@selector(updateAllFeeds) keyEquivalent:@""]; NSMenuItem *updateAll = [menu addItemWithTitle:NSLocalizedString(@"Update all feeds", nil) action:@selector(updateAllFeeds) keyEquivalent:@""];
@@ -190,6 +211,12 @@
[self updateBarIcon]; [self updateBarIcon];
} }
/// Called when user clicks on 'Show Hidden Articles' (main menu only).
- (void)toggleHiddenArticles {
self.optShowHidden = !self.optShowHidden;
self.barMenu.showHidden = self.optShowHidden;
}
/// Called when user clicks on 'Update all feeds' (main menu only). /// Called when user clicks on 'Update all feeds' (main menu only).
- (void)updateAllFeeds { - (void)updateAllFeeds {
// [self asyncReloadUnreadCount]; // should not be necessary // [self asyncReloadUnreadCount]; // should not be necessary

View File

@@ -9,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (readonly) BOOL isFeedMenu; @property (readonly) BOOL isFeedMenu;
// Generator // Generator
- (nullable NSMenuItem*)insertFeedGroupItem:(FeedGroup*)fg withUnread:(MapUnreadTotal*)unreadMap; - (nullable NSMenuItem*)insertFeedGroupItem:(FeedGroup*)fg withUnread:(MapUnreadTotal*)unreadMap showHidden:(BOOL)showHidden;
- (void)insertDefaultHeader; - (void)insertDefaultHeader;
// Update menu // Update menu
- (void)setHeaderHasUnread:(UnreadTotal*)count; - (void)setHeaderHasUnread:(UnreadTotal*)count;

View File

@@ -44,7 +44,7 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
#pragma mark - Generator - #pragma mark - Generator -
/// Create new @c NSMenuItem with empty submenu and append it to the menu. @return Inserted item. /// Create new @c NSMenuItem with empty submenu and append it to the menu. @return Inserted item.
- (nullable NSMenuItem*)insertFeedGroupItem:(FeedGroup*)fg withUnread:(MapUnreadTotal*)unreadMap { - (nullable NSMenuItem*)insertFeedGroupItem:(FeedGroup*)fg withUnread:(MapUnreadTotal*)unreadMap showHidden:(BOOL)showHidden {
unichar chr = '-'; unichar chr = '-';
NSMenuItem *item = nil; NSMenuItem *item = nil;
switch (fg.type) { switch (fg.type) {
@@ -57,7 +57,7 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
NSUInteger unread = unreadMap[[t substringFromIndex:2]].unread; NSUInteger unread = unreadMap[[t substringFromIndex:2]].unread;
// Check user preferences to show only unread entries // Check user preferences to show only unread entries
if (unread == 0 if (unread == 0 && !showHidden
&& (fg.type == FEED || fg.type == GROUP) && (fg.type == FEED || fg.type == GROUP)
&& UserPrefsBool(Pref_groupUnreadOnly)) { && UserPrefsBool(Pref_groupUnreadOnly)) {
item.hidden = YES; item.hidden = YES;