Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8de163859b | ||
|
|
f739b64ceb | ||
|
|
c2fda881b1 | ||
|
|
a0a5b5b82d | ||
|
|
43e32b2286 | ||
|
|
205b544acd | ||
|
|
56f6ec1356 | ||
|
|
ab71c51380 | ||
|
|
7a805ccdc4 | ||
|
|
f4f4bc9271 | ||
|
|
64637243b5 | ||
|
|
5894b12c1d | ||
|
|
0700eebb13 | ||
|
|
4c4a133fe2 | ||
|
|
ccca329630 | ||
|
|
831159904c | ||
|
|
cf3e9e4b4a | ||
|
|
184e5c0882 | ||
|
|
575d1eaec8 | ||
|
|
0c481d18dd | ||
|
|
c281573044 | ||
|
|
d164c6bcb0 | ||
|
|
9f4de8fc8d | ||
|
|
c099c32cca | ||
|
|
bdf9d11853 | ||
|
|
c14af92289 | ||
|
|
b6978662fc | ||
|
|
89f90ddb11 | ||
|
|
0b6a338fa3 | ||
|
|
3235bffdca | ||
|
|
0a23819428 | ||
|
|
def174c65f | ||
|
|
e63d6c5784 | ||
|
|
46fa898807 | ||
|
|
63509faef6 | ||
|
|
7047d99205 | ||
|
|
614e4abb50 | ||
|
|
20835cd155 | ||
|
|
ba76f6a206 | ||
|
|
256fd55d32 | ||
|
|
4eb2248142 | ||
|
|
6ef23ef599 | ||
|
|
f65c5b9546 | ||
|
|
9c3814b470 | ||
|
|
131bfaa14d | ||
|
|
fc6c3a3df2 | ||
|
|
f2bdc5b555 | ||
|
|
060f538240 | ||
|
|
5eed090e9c | ||
|
|
f7872c4f80 | ||
|
|
0fdb8d9ccc | ||
|
|
5d7242cc73 | ||
|
|
b846319335 | ||
|
|
82e9365272 | ||
|
|
839eee7d39 | ||
|
|
f577ec1ec2 | ||
|
|
df0b5b1c91 | ||
|
|
86f5abde0c | ||
|
|
02759ba0be | ||
|
|
3189015ce1 | ||
|
|
6cf86d3bf8 | ||
|
|
fb8f5be289 | ||
|
|
51e1f07531 | ||
|
|
b21cc20746 | ||
|
|
be600b6c5f | ||
|
|
a9c3ccc1f7 | ||
|
|
c4c5559d2d | ||
|
|
68b25d10dd | ||
|
|
24c785662a | ||
|
|
2a589f51a8 | ||
|
|
30527d50e6 | ||
|
|
eb06793b8f | ||
|
|
af89e58a9c | ||
|
|
364811642a | ||
|
|
a342104219 | ||
|
|
26151819c4 | ||
|
|
e0dec3adf9 | ||
|
|
f7eb63bed9 | ||
|
|
23f4f125db | ||
|
|
b3940f103a | ||
|
|
239527908f | ||
|
|
7df70a7936 | ||
|
|
e8c4c06d33 | ||
|
|
a9c0e64689 | ||
|
|
51dd688801 | ||
|
|
352428679d | ||
|
|
9b20262207 | ||
|
|
47a0e76cb3 | ||
|
|
4c9362b42e | ||
|
|
9af191834e | ||
|
|
8d2e4e4383 | ||
|
|
473d4b6057 | ||
|
|
cee3780f71 | ||
|
|
58d7660b87 |
180
CHANGELOG.md
180
CHANGELOG.md
@@ -5,9 +5,139 @@ 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).
|
||||
|
||||
|
||||
## [Unreleased]
|
||||
## [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.0.0] - 2019-10-03
|
||||
|
||||
## [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
|
||||
### Fixed
|
||||
- *Status Bar Menu:* Simplified options for "Show only unread"
|
||||
|
||||
|
||||
## [1.5.0] – 2025-10-27
|
||||
### Added
|
||||
- *UI:* Notifications
|
||||
|
||||
|
||||
## [1.4.1] – 2025-07-29
|
||||
### Fixed
|
||||
- Re-compiled because previous certificate was revoked (again!)
|
||||
|
||||
|
||||
## [1.4.0] – 2025-07-23
|
||||
### Added
|
||||
- *QuickLook:* Updated to new extension framework
|
||||
|
||||
|
||||
## [1.3.2] – 2025-07-23
|
||||
### Fixed
|
||||
- Previous version did not run on macOS 10.15
|
||||
|
||||
|
||||
## [1.3.1] – 2025-07-21
|
||||
### Fixed
|
||||
- *Status Bar Menu:* Always recreate main menu (hopefully fixes #13)
|
||||
- *Status Bar Menu:* Enable global mark read menu items on background update
|
||||
- *Status Bar Menu:* Keyboard navigation over alternate items ("Open a few") (fixes #15)
|
||||
- *Status Bar Menu:* Alternate item ("Open a few") was displayed as normal menu item in macOS 15
|
||||
- *UI:* Welcome message was displayed at the bottom left corner
|
||||
- *UI:* Tooltip will not remove preceding whitespace if html starts with a list
|
||||
- Update Xcode build flags
|
||||
|
||||
|
||||
## [1.3.0] – 2025-06-24
|
||||
### Added
|
||||
- *Adding feed:* Regex Converter for websites without RSS feed (hold down option key during edit)
|
||||
|
||||
### Fixed
|
||||
- *Adding feed:* Keep aspect ratio of favicon inside button (related to fix in v1.2.3)
|
||||
|
||||
|
||||
## [1.2.3] – 2025-06-09
|
||||
### Fixed
|
||||
- *Adding feed:* Favicon size inside button
|
||||
- *DB:* Feeds with changing urls -> use guid for unique check
|
||||
|
||||
|
||||
## [1.2.2] – 2023-06-18
|
||||
### Fixed
|
||||
- Feed menu sporadically not opening
|
||||
|
||||
|
||||
## [1.2.1] – 2023-06-17
|
||||
### Added
|
||||
- Universal binary (Intel+AppleSilicon)
|
||||
|
||||
### Fixed
|
||||
- Autoresize issues of UI elements in macOS Ventura
|
||||
- Flexible width TabBarItem
|
||||
- Updated About page (removed dead link)
|
||||
|
||||
|
||||
## [1.2.0] – 2022-10-01
|
||||
### Added
|
||||
- *UI:* Add option to hide read articles (show only unread)
|
||||
|
||||
|
||||
## [1.1.3] – 2020-12-18
|
||||
### Fixed
|
||||
- Recognize YouTube channel URLs in the format `/c/channel-name`
|
||||
|
||||
|
||||
## [1.1.2] – 2020-11-27
|
||||
### Fixed
|
||||
- Fixes hidden color option for marking unread entries. Unread menu entries did use `colorStatusIconTint` instead of `colorUnreadIndicator` (thanks @tchek)
|
||||
- Workaround for not displaying status bar highlight color in macOS 11.0 (issue #7)
|
||||
|
||||
|
||||
## [1.1.1] – 2020-08-31
|
||||
### Fixed
|
||||
- Feed indices weren't updated properly which resulted in empty feed menus (issue: #6)
|
||||
|
||||
|
||||
## [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
|
||||
- Sandboxing & hardened runtime environment
|
||||
@@ -42,7 +172,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
- *Settings, Feeds:* Status info with accurate download count (instead of `Updating feeds …`)
|
||||
- *Settings, Feeds:* Status info shows `No network connection` and `Updates paused`
|
||||
- *Settings, Feeds:* After feed edit, run update scheduler immediately
|
||||
- *Status Bar Menu*: Feed title is updated properly
|
||||
- *Status Bar Menu:* Feed title is updated properly
|
||||
- *UI:* If an error occurs, show document URL (path to file or web url)
|
||||
- Comparison of existing articles with nonexistent guid and link
|
||||
- Don't mark articles read if opening URLs failed
|
||||
@@ -54,17 +184,17 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
- *Adding feed:* Refresh interval hotkeys set to: `⌘1` … `⌘6`
|
||||
- *Settings, Feeds:* Single add button for feeds, groups, and separators
|
||||
- *Settings, Feeds:* Always append new items at the end
|
||||
- *Settings, General*: Moved `Fix cache` button to `About` text section
|
||||
- *Settings, General*: Changing default feed reader is prohibited within sandbox
|
||||
- *Settings, General*: [Auxiliary application](https://github.com/relikd/URL-Scheme-Defaults) for changing default feed reader
|
||||
- *Status Bar Menu*: Show `(no title)` instead of `(error)`
|
||||
- *Status Bar Menu*: `Update all feeds` will show error alert for broken URLs
|
||||
- *DB*: Dropping table `FeedIcon` in favor of image files cache
|
||||
- *Settings, General:* Moved `Fix cache` button to `About` text section
|
||||
- *Settings, General:* Changing default feed reader is prohibited within sandbox
|
||||
- *Settings, General:* [Auxiliary application](https://github.com/relikd/URL-Scheme-Defaults) for changing default feed reader
|
||||
- *Status Bar Menu:* Show `(no title)` instead of `(error)`
|
||||
- *Status Bar Menu:* `Update all feeds` will show error alert for broken URLs
|
||||
- *DB:* Dropping table `FeedIcon` in favor of image files cache
|
||||
- *UI:* Interface builder files replaced with code equivalent
|
||||
- *UI:* Mark unread articles with blue dot, instead of tick mark
|
||||
|
||||
|
||||
## [0.9.4] - 2019-04-02
|
||||
## [0.9.4] – 2019-04-02
|
||||
### Fixed
|
||||
- Article order got mixed up for some feeds (issue: #4)
|
||||
- If multiple consecutive items reappear in the middle of the feed mark them read
|
||||
@@ -73,7 +203,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
- *UI:* Removed checkbox `Start on login`. Use Preferences > Users > Login Items instead.
|
||||
|
||||
|
||||
## [0.9.3] - 2019-03-14
|
||||
## [0.9.3] – 2019-03-14
|
||||
### Added
|
||||
- Changelog
|
||||
- *UI:* Show body tag in article tooltip if abstract tag is empty
|
||||
@@ -83,7 +213,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
- Fixed update for feeds where all article URLs point to the same resource (issue: #3)
|
||||
|
||||
|
||||
## [0.9.2] - 2019-03-07
|
||||
## [0.9.2] – 2019-03-07
|
||||
### Added
|
||||
- Limit number of articles that are displayed in feed menu (issue: #2)
|
||||
|
||||
@@ -93,7 +223,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
- libxml2 will ignore lower ascii characters (`0x00`–`0x1F`)
|
||||
|
||||
|
||||
## [0.9.1] - 2019-02-14
|
||||
## [0.9.1] – 2019-02-14
|
||||
### Added
|
||||
- Mark single article as un/read (hold down option key and click on article)
|
||||
|
||||
@@ -111,11 +241,31 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
- Remove html tags from abstract on save (not on display)
|
||||
|
||||
|
||||
## [0.9] - 2019-02-11
|
||||
## [0.9] – 2019-02-11
|
||||
Initial release
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/relikd/baRSS/compare/v1.0.0...HEAD
|
||||
[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.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.0]: https://github.com/relikd/baRSS/compare/v1.3.2...v1.4.0
|
||||
[1.3.2]: https://github.com/relikd/baRSS/compare/v1.3.1...v1.3.2
|
||||
[1.3.1]: https://github.com/relikd/baRSS/compare/v1.3.0...v1.3.1
|
||||
[1.3.0]: https://github.com/relikd/baRSS/compare/v1.2.3...v1.3.0
|
||||
[1.2.3]: https://github.com/relikd/baRSS/compare/v1.2.2...v1.2.3
|
||||
[1.2.2]: https://github.com/relikd/baRSS/compare/v1.2.1...v1.2.2
|
||||
[1.2.1]: https://github.com/relikd/baRSS/compare/v1.2.0...v1.2.1
|
||||
[1.2.0]: https://github.com/relikd/baRSS/compare/v1.1.3...v1.2.0
|
||||
[1.1.3]: https://github.com/relikd/baRSS/compare/v1.1.2...v1.1.3
|
||||
[1.1.2]: https://github.com/relikd/baRSS/compare/v1.1.1...v1.1.2
|
||||
[1.1.1]: https://github.com/relikd/baRSS/compare/v1.1.0...v1.1.1
|
||||
[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
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
github "relikd/RSXML2" "v2.0.0"
|
||||
6
Config-debug.xcconfig
Normal file
6
Config-debug.xcconfig
Normal 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
12
Config.xcconfig
Normal 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
|
||||
22
LICENSE
22
LICENSE
@@ -1,7 +1,21 @@
|
||||
Copyright 2018 Oleg Geier
|
||||
MIT License
|
||||
|
||||
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:
|
||||
Copyright (c) 2018 relikd
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
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 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.
|
||||
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.
|
||||
|
||||
21
QLOPML/Base.lproj/PreviewViewController.xib
Normal file
21
QLOPML/Base.lproj/PreviewViewController.xib
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="PreviewViewController" customModuleProvider="">
|
||||
<connections>
|
||||
<outlet property="view" destination="c22-O7-iKe" id="NRM-P4-wb6"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe" userLabel="Preview View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</customView>
|
||||
</objects>
|
||||
</document>
|
||||
44
QLOPML/Info.plist
Normal file
44
QLOPML/Info.plist
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>QLOPML</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>QLSupportedContentTypes</key>
|
||||
<array>
|
||||
<string>org.opml.opml</string>
|
||||
</array>
|
||||
<key>QLSupportsSearchableItems</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.quicklook.preview</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>PreviewViewController</string>
|
||||
</dict>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2025 relikd.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
5
QLOPML/PreviewViewController.h
Normal file
5
QLOPML/PreviewViewController.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface PreviewViewController : NSViewController
|
||||
|
||||
@end
|
||||
29
QLOPML/PreviewViewController.m
Normal file
29
QLOPML/PreviewViewController.m
Normal file
@@ -0,0 +1,29 @@
|
||||
#import "PreviewViewController.h"
|
||||
#import <Quartz/Quartz.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#include "opml-lib.h"
|
||||
|
||||
@interface PreviewViewController () <QLPreviewingController>
|
||||
@end
|
||||
|
||||
@implementation PreviewViewController
|
||||
|
||||
- (NSString *)nibName {
|
||||
return @"PreviewViewController";
|
||||
}
|
||||
|
||||
- (void)preparePreviewOfFileAtURL:(NSURL *)url completionHandler:(void (^)(NSError * _Nullable))handler {
|
||||
NSData *data = generateHTMLData(url, [NSBundle mainBundle], NO);
|
||||
// sure, we could use `WKWebView`, but that requires the `com.apple.security.network.client` entitlement
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
WebView *web = [[WebView alloc] initWithFrame:self.view.bounds];
|
||||
#pragma clang diagnostic pop
|
||||
web.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
[self.view addSubview:web];
|
||||
// [web.mainFrame loadHTMLString:html baseURL:nil];
|
||||
[web.mainFrame loadData:data MIMEType:@"text/html" textEncodingName:@"UTF-8" baseURL:nil];
|
||||
handler(nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
10
QLOPML/QLOPML.entitlements
Normal file
10
QLOPML/QLOPML.entitlements
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
7
QLOPML/opml-lib.h
Normal file
7
QLOPML/opml-lib.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef opml_lib_h
|
||||
#define opml_lib_h
|
||||
|
||||
NSData* generateHTMLData(NSURL *url, NSBundle *bundle, BOOL thumb);
|
||||
//void renderThumbnail(CFURLRef url, CFBundleRef bundle, CGContextRef context, CGSize maxSize);
|
||||
|
||||
#endif /* opml_lib_h */
|
||||
116
QLOPML/opml-lib.m
Normal file
116
QLOPML/opml-lib.m
Normal file
@@ -0,0 +1,116 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
//#import <WebKit/WebKit.h>
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// |
|
||||
// | OPML renderer
|
||||
// |
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
NSXMLElement* make(NSString *tag, NSString *text, NSXMLElement *parent) {
|
||||
NSXMLElement *div = [NSXMLElement elementWithName:tag];
|
||||
if (text) div.stringValue = text;
|
||||
[parent addChild:div];
|
||||
return div;
|
||||
}
|
||||
|
||||
void attribute(NSXMLElement *parent, NSString *key, NSString *value) {
|
||||
[parent addAttribute:[NSXMLElement attributeWithName:key stringValue:value]];
|
||||
}
|
||||
|
||||
NSXMLElement* section(NSString *title, NSString *container, NSXMLElement *parent) {
|
||||
make(@"h3", title, parent);
|
||||
NSXMLElement *div = make(container, nil, parent);
|
||||
attribute(div, @"class", @"section");
|
||||
return div;
|
||||
}
|
||||
|
||||
void appendNode(NSXMLElement *child, NSXMLElement *parent, Boolean thumb) {
|
||||
|
||||
if ([child.name isEqualToString:@"head"]) {
|
||||
if (thumb)
|
||||
return;
|
||||
NSXMLElement *dl = section(@"Metadata:", @"dl", parent);
|
||||
for (NSXMLElement *head in child.children) {
|
||||
make(@"dt", head.name, dl);
|
||||
make(@"dd", head.stringValue, dl);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ([child.name isEqualToString:@"body"]) {
|
||||
parent = thumb ? make(@"ul", nil, parent) : section(@"Content:", @"ul", parent);
|
||||
|
||||
} else if ([child.name isEqualToString:@"outline"]) {
|
||||
if ([child attributeForName:@"separator"].stringValue) {
|
||||
make(@"hr", nil, parent);
|
||||
} else {
|
||||
NSString *desc = [child attributeForName:@"title"].stringValue;
|
||||
if (!desc || desc.length == 0)
|
||||
desc = [child attributeForName:@"text"].stringValue;
|
||||
// refreshInterval
|
||||
NSXMLElement *li = make(@"li", desc, parent);
|
||||
if (!thumb) {
|
||||
NSString *xmlUrl = [child attributeForName:@"xmlUrl"].stringValue;
|
||||
if (xmlUrl && xmlUrl.length > 0) {
|
||||
[li addChild:[NSXMLNode textWithStringValue:@" — "]];
|
||||
attribute(make(@"a", xmlUrl, li), @"href", xmlUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (child.childCount > 0) {
|
||||
parent = make(@"ul", nil, parent);
|
||||
}
|
||||
}
|
||||
for (NSXMLElement *c in child.children) {
|
||||
appendNode(c, parent, thumb);
|
||||
}
|
||||
}
|
||||
|
||||
NSData* generateHTMLData(NSURL *url, NSBundle *bundle, BOOL thumb) {
|
||||
NSError *err;
|
||||
NSXMLDocument *doc = [[NSXMLDocument alloc] initWithContentsOfURL:url options:0 error:&err];
|
||||
if (err || !doc) {
|
||||
printf("ERROR: %s\n", err.description.UTF8String);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSXMLElement *html = [NSXMLElement elementWithName:@"html"];
|
||||
NSXMLElement *head = make(@"head", nil, html);
|
||||
make(@"title", @"OPML file", head);
|
||||
|
||||
NSString *cssPath = [bundle pathForResource:thumb ? @"style-thumb" : @"style" ofType:@"css"];
|
||||
NSString *data = [NSString stringWithContentsOfFile:cssPath encoding:NSUTF8StringEncoding error:nil];
|
||||
make(@"style", data, head);
|
||||
|
||||
NSXMLElement *body = make(@"body", nil, html);
|
||||
|
||||
for (NSXMLElement *child in doc.children) {
|
||||
appendNode(child, body, thumb);
|
||||
}
|
||||
NSXMLDocument *xml = [NSXMLDocument documentWithRootElement:html];
|
||||
return [xml XMLDataWithOptions:NSXMLNodePrettyPrint | NSXMLNodeCompactEmptyElement];
|
||||
}
|
||||
|
||||
|
||||
/*void renderThumbnail(CFURLRef url, CFBundleRef bundle, CGContextRef context, CGSize maxSize) {
|
||||
NSData *data = generateHTMLData((__bridge NSURL*)url, bundle, true);
|
||||
if (data) {
|
||||
CGRect rect = CGRectMake(0, 0, 600, 800);
|
||||
float scale = maxSize.height / rect.size.height;
|
||||
|
||||
WebView *webView = [[WebView alloc] initWithFrame:rect];
|
||||
[webView.mainFrame.frameView scaleUnitSquareToSize:CGSizeMake(scale, scale)];
|
||||
[webView.mainFrame.frameView setAllowsScrolling:NO];
|
||||
[webView.mainFrame loadData:data MIMEType:@"text/html" textEncodingName:@"utf-8" baseURL:nil];
|
||||
|
||||
while ([webView isLoading])
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
|
||||
[webView display];
|
||||
|
||||
NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)context
|
||||
flipped:webView.isFlipped];
|
||||
[webView displayRectIgnoringOpacity:webView.bounds inContext:gc];
|
||||
}
|
||||
}*/
|
||||
12
QLOPML/style.css
Normal file
12
QLOPML/style.css
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
* { font-family: Courier; }
|
||||
body { padding: 30px; background-color: #AAA; color: black; }
|
||||
dd, li, hr { font-weight: bold; line-height: 1.5em; }
|
||||
ul { list-style-type: none; padding-bottom: 1em; }
|
||||
a { font-size: 0.75em; color: #FBA43A; }
|
||||
.section { padding: 1em 1.5em; border-radius: 7px; background-color: #EEE; }
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body { background-color: #555; color: white; }
|
||||
.section { background-color: #222; }
|
||||
}
|
||||
244
README.md
244
README.md
@@ -1,4 +1,4 @@
|
||||
[](#download--install)
|
||||
[](#download--install)
|
||||
[](https://github.com/relikd/baRSS/releases)
|
||||
[](https://github.com/relikd/baRSS/releases)
|
||||
[](LICENSE)
|
||||
@@ -31,10 +31,141 @@ But it will reuse `ETag` and `Last-Modified` headers to avoid unnecessary transm
|
||||
Further, tuning the update frequently will decrease the traffic even more.
|
||||
|
||||
|
||||
|
||||
Download & Install
|
||||
------------------
|
||||
|
||||
Requires macOS Mojave (10.14) 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, [RSXML2] \(required), and [QLOPML] \(optional).
|
||||
|
||||
```sh
|
||||
git clone https://github.com/relikd/baRSS
|
||||
git clone https://github.com/relikd/RSXML2
|
||||
git clone https://github.com/relikd/QLOPML
|
||||
```
|
||||
|
||||
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 spacebar.
|
||||
|
||||
That's it.
|
||||
Open `baRSS/baRSS.xcodeproj` and build the project.
|
||||
Note, there are some compiler flags that append 'beta' to the development release.
|
||||
If you prefer the optimized release version go to `Product > Archive`.
|
||||
|
||||
|
||||
|
||||
Hidden options
|
||||
--------------
|
||||
|
||||
### Launch on start / reboot
|
||||
|
||||
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:
|
||||
|
||||
`System Preferences > User > Login Items` (macOS 10.x-12)
|
||||
`System Preferences > General > Login Items & Extensions` (macOS 13+)
|
||||
|
||||
|
||||
### 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.
|
||||
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.
|
||||
Hold down the option key in the feed edit modal and click the red regex button.
|
||||
Though, admittedly, this is for experts only.
|
||||
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
|
||||
|
||||
The following options have no UI equivalent and must be configured in Terminal.
|
||||
Most likely, you will never stumble upon these if not reading this chapter.
|
||||
**Note:** To reset an option run `defaults delete de.relikd.baRSS {KEY}`, where `{KEY}` is an option from below.
|
||||
|
||||
|
||||
1. When holding down the option key, the menu will show an item to open only a few unread items at a time.
|
||||
This number can be changed with the following Terminal command (default: 10):
|
||||
```
|
||||
defaults write de.relikd.baRSS openFewLinksLimit -int 10
|
||||
```
|
||||
|
||||
2. In preferences you can choose to show 'Short article names'.
|
||||
This will limit the number of displayed characters to 60 (default).
|
||||
With this Terminal command you can customize this limit:
|
||||
```
|
||||
defaults write de.relikd.baRSS shortArticleNamesLimit -int 50
|
||||
```
|
||||
|
||||
3. Limit the number of displayed articles per feed menu.
|
||||
**Note:** displayed unread count may be different than the unread items inside. 'Open all unread' will open hidden items too.
|
||||
```
|
||||
defaults write de.relikd.baRSS articlesInMenuLimit -int 40
|
||||
```
|
||||
|
||||
4. You can change the appearance of colors throughout the application.
|
||||
E.g., The tint color of the menu bar icon and the color of the blue unread articles dot.
|
||||
```
|
||||
defaults write de.relikd.baRSS colorStatusIconTint -string "#37F"
|
||||
defaults write de.relikd.baRSS colorUnreadIndicator -string "#FBA33A"
|
||||
```
|
||||
|
||||
5. To backup your list of subscribed feeds, here is a one-liner:
|
||||
```
|
||||
open barss:backup && cp "$HOME/Library/Containers/de.relikd.baRSS/Data/Library/Application Support/baRSS/backup/feeds_latest.opml" "$HOME/Desktop/baRSS_backup_$(date "+%Y-%m-%d").opml"
|
||||
```
|
||||
|
||||
|
||||
|
||||
ToDo
|
||||
----
|
||||
|
||||
The following list is a collection of ideas that may be added if people request it.
|
||||
|
||||
- [ ] Localizations
|
||||
- [x] Feed generator for websites without feeds
|
||||
- [ ] Automatically choose best update interval (e.g., avg)
|
||||
- [ ] Sync with online services
|
||||
- [ ] Feeds with authentication
|
||||
- [x] Notification Center
|
||||
- [ ] Distraction Mode
|
||||
- [ ] Distract less: Sleep timer. (e.g., disable updates during working hours)
|
||||
- [ ] Distract more: Automatically open feed items
|
||||
- [ ] Add support for media types
|
||||
- [ ] music / video? (open media player)
|
||||
- [ ] Pure image feed? (show images directly in menu)
|
||||
- [ ] Per feed / group settings
|
||||
- [ ] select launch application (e.g., for podcasts)
|
||||
- [ ] exclude unread count from menu bar (e.g., unimportant feeds)
|
||||
- [ ] ~~Infinite storage. (load more button)~~
|
||||
|
||||
|
||||
|
||||
FAQ / Q&A
|
||||
---------
|
||||
|
||||
### App Store Notice
|
||||
|
||||
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.
|
||||
Second, 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.
|
||||
@@ -42,7 +173,7 @@ Not like 99% of the other feed readers on the market that show a separate HTML v
|
||||
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.
|
||||
But for me RSS is more about being informed whenever a blog or news feed has updated content.
|
||||
E.g, subscribing to video channels without having to have an account.
|
||||
|
||||
|
||||
@@ -57,112 +188,19 @@ 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
|
||||
------------------
|
||||
|
||||
Requires macOS Sierra (10.12) or higher.
|
||||
|
||||
### Easy way
|
||||
Go to [releases](https://github.com/relikd/baRSS/releases) and downloaded the latest version.
|
||||
|
||||
### 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.
|
||||
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.
|
||||
|
||||
That's it.
|
||||
Open Xcode and build the project.
|
||||
Note, there are some compiler flags that append 'beta' to the development release.
|
||||
If you prefer the optimized release version go to `Product > Archive`.
|
||||
|
||||
|
||||
|
||||
Hidden options
|
||||
--------------
|
||||
|
||||
This listing contains of options that have no UI that can be configured.
|
||||
Most likely, you wouldn't ever stumble upon these if not reading this chapter.
|
||||
**Note:** To reset an option run `defaults delete de.relikd.baRSS {KEY}`, where `{KEY}` is an option from below.
|
||||
|
||||
|
||||
1. If you hold down the option key and click on an article item, you can mark a single item (un-)read.
|
||||
|
||||
2. When holding down the option key, the menu will show an item to open only a few unread items at a time.
|
||||
This number can be changed with the following Terminal command (default: 10):
|
||||
```
|
||||
defaults write de.relikd.baRSS openFewLinksLimit -int 10
|
||||
```
|
||||
|
||||
3. In preferences you can choose to show 'Short article names'.
|
||||
This will limit the number of displayed characters to 60 (default).
|
||||
With this Terminal command you can customize this limit:
|
||||
```
|
||||
defaults write de.relikd.baRSS shortArticleNamesLimit -int 50
|
||||
```
|
||||
|
||||
4. Limit the number of displayed articles per feed menu.
|
||||
**Note:** displayed unread count may be different than the unread items inside ('Open unread' will open hidden items too).
|
||||
```
|
||||
defaults write de.relikd.baRSS articlesInMenuLimit -int 40
|
||||
```
|
||||
|
||||
5. You can change the appearance of colors throughout the application.
|
||||
E.g., The tint color of the menu bar icon and the color of the blue dot of unread articles.
|
||||
```
|
||||
defaults write de.relikd.baRSS colorStatusIconTint -string "#37F"
|
||||
defaults write de.relikd.baRSS colorUnreadIndicator -string "#FBA33A"
|
||||
```
|
||||
|
||||
6. To backup your list of subscribed feeds, here is a one-liner:
|
||||
```
|
||||
open barss:backup && cp "$HOME/Library/Containers/de.relikd.baRSS/Data/Library/Application Support/baRSS/backup/feeds_latest.opml" "$HOME/Desktop/baRSS_backup_$(date "+%Y-%m-%d").opml"
|
||||
```
|
||||
|
||||
|
||||
|
||||
ToDo
|
||||
----
|
||||
|
||||
The following list is not exhaustive but rather a collection of nice things that will be added eventually.
|
||||
I may postpone some until demand increases …
|
||||
|
||||
- [ ] Localizations
|
||||
- [ ] Feed generator for websites without feeds
|
||||
- [ ] Automatically choose best update interval (e.g., avg)
|
||||
- [ ] Sync with online services
|
||||
- [ ] Feeds with authentication
|
||||
- [ ] Notification Center
|
||||
- [ ] Distraction Mode
|
||||
- [ ] Distract less: Sleep timer. (e.g., disable updates during working hours)
|
||||
- [ ] Distract more: Automatically open feed items
|
||||
- [ ] Add support for media types
|
||||
- [ ] music / video? (open media player)
|
||||
- [ ] Pure image feed? (show images directly in menu)
|
||||
- [ ] Per feed / group settings
|
||||
- [ ] select launch application (e.g., for podcasts)
|
||||
- [ ] exclude unread count from menu bar (e.g., unimportant feeds)
|
||||
- [ ] ~~Infinite storage. (load more button)~~
|
||||
|
||||
This project uses a modified version of Brent Simmons' [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: __2053h+__
|
||||
- First prototype used __feedparser python__ library
|
||||
|
||||
|
||||
[QLOPML]: https://github.com/relikd/QLOPML
|
||||
[RSXML2]: https://github.com/relikd/RSXML2
|
||||
[RSXML]: https://github.com/brentsimmons/RSXML
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -12,12 +12,17 @@
|
||||
54195886218E1BDB00581B79 /* NSMenu+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54195885218E1BDB00581B79 /* NSMenu+Ext.m */; };
|
||||
541C67C32255470B004D2CE6 /* SettingsAppearance.m in Sources */ = {isa = PBXBuildFile; fileRef = 541C67C22255470B004D2CE6 /* SettingsAppearance.m */; };
|
||||
54209E942117325100F3B5EF /* DrawImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 54209E932117325100F3B5EF /* DrawImage.m */; };
|
||||
54229F552E02491A0019ACB0 /* TinySVG.m in Sources */ = {isa = PBXBuildFile; fileRef = 54229F542E02491A0019ACB0 /* TinySVG.m */; };
|
||||
54253C7F2C47303A00742695 /* RegexConverter+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54253C7E2C47303A00742695 /* RegexConverter+Ext.m */; };
|
||||
54253C932C49BFCD00742695 /* RegexConverterModal.m in Sources */ = {isa = PBXBuildFile; fileRef = 54253C8A2C49A92400742695 /* RegexConverterModal.m */; };
|
||||
54253C942C49BFDC00742695 /* RegexConverterController.m in Sources */ = {isa = PBXBuildFile; fileRef = 54253C872C49A6A800742695 /* RegexConverterController.m */; };
|
||||
54253C952C49BFE400742695 /* RegexConverterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 54253C842C47369000742695 /* RegexConverterView.m */; };
|
||||
544B011A2114B41200386E5C /* ModalSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B01192114B41200386E5C /* ModalSheet.m */; };
|
||||
544B011D2114EE9100386E5C /* AppHook.m in Sources */ = {isa = PBXBuildFile; fileRef = 544B011C2114EE9100386E5C /* AppHook.m */; };
|
||||
544DCCB9212A2B4D002DBC46 /* RSXML2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 544DCCB8212A2B4D002DBC46 /* RSXML2.framework */; };
|
||||
544DCCBA212A2B4D002DBC46 /* RSXML2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 544DCCB8212A2B4D002DBC46 /* RSXML2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
544DCCBE212A2B6F002DBC46 /* RSXML2.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = 544DCCBD212A2B6F002DBC46 /* RSXML2.framework.dSYM */; };
|
||||
544F5A752E30EFC700674F81 /* style.css in Resources */ = {isa = PBXBuildFile; fileRef = 544F5A722E30EFC700674F81 /* style.css */; };
|
||||
544F5A762E30EFC700674F81 /* opml-lib.m in Sources */ = {isa = PBXBuildFile; fileRef = 544F5A702E30EFC700674F81 /* opml-lib.m */; };
|
||||
54501010230E9C8600F0B165 /* FeedDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 5450100F230E9C8600F0B165 /* FeedDownload.m */; };
|
||||
5469E13C2EA90C6C00D46CE7 /* NotifyEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 5469E13B2EA90C6C00D46CE7 /* NotifyEndpoint.m */; };
|
||||
546A6A2922C583390034E806 /* SettingsGeneralView.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D857D122802309001BA1C8 /* SettingsGeneralView.m */; };
|
||||
546A6A2C22C584AF0034E806 /* SettingsAppearanceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 546A6A2A22C584AF0034E806 /* SettingsAppearanceView.m */; };
|
||||
546A6A2F22C585580034E806 /* SettingsAboutView.m in Sources */ = {isa = PBXBuildFile; fileRef = 546A6A2D22C585580034E806 /* SettingsAboutView.m */; };
|
||||
@@ -25,18 +30,23 @@
|
||||
546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */ = {isa = PBXBuildFile; fileRef = 546FC44121189975007CC3A3 /* SettingsGeneral.m */; };
|
||||
5477D34E21233C62002BA27F /* FeedGroup+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 5477D34D21233C62002BA27F /* FeedGroup+Ext.m */; };
|
||||
5478DF04225A7AE200D30C64 /* SettingsFeedsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5478DF03225A7AE200D30C64 /* SettingsFeedsView.m */; };
|
||||
5483296C2A3CDC38000688B9 /* RSXML2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 548329652A3CDB22000688B9 /* RSXML2.framework */; };
|
||||
5483296D2A3CDC38000688B9 /* RSXML2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 548329652A3CDB22000688B9 /* RSXML2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
548C6D0A230C33DE003A1AAF /* NSURL+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 548C6D09230C33DE003A1AAF /* NSURL+Ext.m */; };
|
||||
5491005D2331435E00858AE2 /* Download3rdParty.m in Sources */ = {isa = PBXBuildFile; fileRef = 5491005C2331435E00858AE2 /* Download3rdParty.m */; };
|
||||
54910067233A4D4000858AE2 /* URLScheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 54910066233A4D4000858AE2 /* URLScheme.m */; };
|
||||
5496B511214D6275003ED4ED /* UserPrefs.m in Sources */ = {isa = PBXBuildFile; fileRef = 5496B510214D6275003ED4ED /* UserPrefs.m */; };
|
||||
54A07A7F220E04CF00082C51 /* NSFetchRequest+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54A07A7E220E04CF00082C51 /* NSFetchRequest+Ext.m */; };
|
||||
54A07A82220E723D00082C51 /* MapUnreadTotal.m in Sources */ = {isa = PBXBuildFile; fileRef = 54A07A81220E723D00082C51 /* MapUnreadTotal.m */; };
|
||||
54A2D63922EF81A4007C61F3 /* QLOPML.qlgenerator in CopyFiles */ = {isa = PBXBuildFile; fileRef = 54A2D63822EF8193007C61F3 /* QLOPML.qlgenerator */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; };
|
||||
54ACC29521061E270020715F /* UpdateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* UpdateScheduler.m */; };
|
||||
54ACC29821061FBA0020715F /* Preferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29721061FBA0020715F /* Preferences.m */; };
|
||||
54AD4E0C2301853D000AE386 /* NSString+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AD4E0B2301853D000AE386 /* NSString+Ext.m */; };
|
||||
54AD4EE72305B17D000AE386 /* container-migration.plist in Resources */ = {isa = PBXBuildFile; fileRef = 54AD4EE62305B17D000AE386 /* container-migration.plist */; };
|
||||
54AD90EA2E30C48400160925 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54AD90E92E30C48400160925 /* Quartz.framework */; };
|
||||
54AD90EE2E30C48400160925 /* PreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AD90ED2E30C48400160925 /* PreviewViewController.m */; };
|
||||
54AD90F12E30C48400160925 /* PreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54AD90EF2E30C48400160925 /* PreviewViewController.xib */; };
|
||||
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 */; };
|
||||
54B517072270E990006C1B29 /* NSView+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B517062270E92A006C1B29 /* NSView+Ext.m */; };
|
||||
54B6F14A231551B3002C94C9 /* FaviconDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B6F149231551B3002C94C9 /* FaviconDownload.m */; };
|
||||
@@ -45,8 +55,10 @@
|
||||
54B749E0220636200022CC6D /* FeedArticle+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B749DF220635CD0022CC6D /* FeedArticle+Ext.m */; };
|
||||
54BB048921FD2AB500C303A5 /* NSDate+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54BB048821FD2AB500C303A5 /* NSDate+Ext.m */; };
|
||||
54BF444A22D0F4F300660096 /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 54BF444922D0F4F300660096 /* AppIcon.icns */; };
|
||||
54D10DDB2C6E930F0008F621 /* RegexFeed.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D10DDA2C6E930F0008F621 /* RegexFeed.m */; };
|
||||
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 */; };
|
||||
@@ -58,11 +70,25 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
54A2D63722EF8193007C61F3 /* PBXContainerItemProxy */ = {
|
||||
548329642A3CDB22000688B9 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 54A2D62E22EF8183007C61F3 /* QLOPML.xcodeproj */;
|
||||
containerPortal = 5483295E2A3CDB22000688B9 /* RSXML2.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 540A649822EE78B200470937;
|
||||
remoteGlobalIDString = 84F22C0D1B52DDEA000060CE;
|
||||
remoteInfo = RSXML2;
|
||||
};
|
||||
548329662A3CDB22000688B9 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5483295E2A3CDB22000688B9 /* RSXML2.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 84F22C171B52DDEA000060CE;
|
||||
remoteInfo = RSXML2Tests;
|
||||
};
|
||||
54AD90F42E30C48400160925 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 54ACC27421061B3B0020715F /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 54AD90E62E30C48400160925;
|
||||
remoteInfo = QLOPML;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
@@ -74,29 +100,20 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
544DCCBA212A2B4D002DBC46 /* RSXML2.framework in Embed Frameworks */,
|
||||
5483296D2A3CDC38000688B9 /* RSXML2.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
544DCCBC212A2B5A002DBC46 /* CopyFiles */ = {
|
||||
54AD90F62E30C48400160925 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 16;
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
544DCCBE212A2B6F002DBC46 /* RSXML2.framework.dSYM in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54CE4D4522EF509400E89C16 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = Contents/Library/QuickLook;
|
||||
dstSubfolderSpec = 1;
|
||||
files = (
|
||||
54A2D63922EF81A4007C61F3 /* QLOPML.qlgenerator in CopyFiles */,
|
||||
54AD90F72E30C48400160925 /* QLOPML.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
@@ -114,18 +131,33 @@
|
||||
541C67C22255470B004D2CE6 /* SettingsAppearance.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SettingsAppearance.m; sourceTree = "<group>"; };
|
||||
54209E922117325100F3B5EF /* DrawImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DrawImage.h; sourceTree = "<group>"; };
|
||||
54209E932117325100F3B5EF /* DrawImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DrawImage.m; sourceTree = "<group>"; };
|
||||
54229F532E02491A0019ACB0 /* TinySVG.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TinySVG.h; sourceTree = "<group>"; };
|
||||
54229F542E02491A0019ACB0 /* TinySVG.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TinySVG.m; sourceTree = "<group>"; };
|
||||
54253C7A2C47303A00742695 /* RegexConverter+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RegexConverter+Ext.h"; sourceTree = "<group>"; };
|
||||
54253C7E2C47303A00742695 /* RegexConverter+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "RegexConverter+Ext.m"; sourceTree = "<group>"; };
|
||||
54253C832C47368F00742695 /* RegexConverterView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RegexConverterView.h; sourceTree = "<group>"; };
|
||||
54253C842C47369000742695 /* RegexConverterView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RegexConverterView.m; sourceTree = "<group>"; };
|
||||
54253C872C49A6A800742695 /* RegexConverterController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RegexConverterController.m; sourceTree = "<group>"; };
|
||||
54253C882C49A6A800742695 /* RegexConverterController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RegexConverterController.h; sourceTree = "<group>"; };
|
||||
54253C8A2C49A92400742695 /* RegexConverterModal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RegexConverterModal.m; sourceTree = "<group>"; };
|
||||
54253C8B2C49A92400742695 /* RegexConverterModal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RegexConverterModal.h; sourceTree = "<group>"; };
|
||||
544B01182114B41200386E5C /* ModalSheet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModalSheet.h; sourceTree = "<group>"; };
|
||||
544B01192114B41200386E5C /* ModalSheet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModalSheet.m; sourceTree = "<group>"; };
|
||||
544B011B2114EE9100386E5C /* AppHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppHook.h; sourceTree = "<group>"; };
|
||||
544B011C2114EE9100386E5C /* AppHook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppHook.m; sourceTree = "<group>"; };
|
||||
544DCCB8212A2B4D002DBC46 /* RSXML2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RSXML2.framework; path = Carthage/Build/Mac/RSXML2.framework; sourceTree = "<group>"; };
|
||||
544DCCBD212A2B6F002DBC46 /* RSXML2.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = RSXML2.framework.dSYM; path = Carthage/Build/Mac/RSXML2.framework.dSYM; sourceTree = "<group>"; };
|
||||
544F5A6F2E30EFC700674F81 /* opml-lib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "opml-lib.h"; sourceTree = "<group>"; };
|
||||
544F5A702E30EFC700674F81 /* opml-lib.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "opml-lib.m"; sourceTree = "<group>"; };
|
||||
544F5A722E30EFC700674F81 /* style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = style.css; sourceTree = "<group>"; };
|
||||
5450100E230E9C8600F0B165 /* FeedDownload.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FeedDownload.h; sourceTree = "<group>"; };
|
||||
5450100F230E9C8600F0B165 /* FeedDownload.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FeedDownload.m; sourceTree = "<group>"; };
|
||||
5469E13A2EA90C6C00D46CE7 /* NotifyEndpoint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotifyEndpoint.h; sourceTree = "<group>"; };
|
||||
5469E13B2EA90C6C00D46CE7 /* NotifyEndpoint.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotifyEndpoint.m; sourceTree = "<group>"; };
|
||||
546A6A2A22C584AF0034E806 /* SettingsAppearanceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsAppearanceView.m; 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -134,6 +166,7 @@
|
||||
5477D34D21233C62002BA27F /* FeedGroup+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FeedGroup+Ext.m"; sourceTree = "<group>"; };
|
||||
5478DF02225A7AE200D30C64 /* SettingsFeedsView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsFeedsView.h; sourceTree = "<group>"; };
|
||||
5478DF03225A7AE200D30C64 /* SettingsFeedsView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SettingsFeedsView.m; sourceTree = "<group>"; };
|
||||
5483295E2A3CDB22000688B9 /* RSXML2.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSXML2.xcodeproj; path = ../RSXML2/RSXML2.xcodeproj; sourceTree = "<group>"; };
|
||||
54892F1D2235285700271CBA /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
|
||||
548C6D08230C33DE003A1AAF /* NSURL+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSURL+Ext.h"; sourceTree = "<group>"; };
|
||||
548C6D09230C33DE003A1AAF /* NSURL+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+Ext.m"; sourceTree = "<group>"; };
|
||||
@@ -147,7 +180,6 @@
|
||||
54A07A7E220E04CF00082C51 /* NSFetchRequest+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSFetchRequest+Ext.m"; sourceTree = "<group>"; };
|
||||
54A07A80220E723D00082C51 /* MapUnreadTotal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapUnreadTotal.h; sourceTree = "<group>"; };
|
||||
54A07A81220E723D00082C51 /* MapUnreadTotal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MapUnreadTotal.m; sourceTree = "<group>"; };
|
||||
54A2D62E22EF8183007C61F3 /* QLOPML.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = QLOPML.xcodeproj; path = ../QLOPML/QLOPML.xcodeproj; sourceTree = "<group>"; };
|
||||
54ACC27C21061B3B0020715F /* baRSS Beta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "baRSS Beta.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
54ACC28321061B3B0020715F /* DBv1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DBv1.xcdatamodel; sourceTree = "<group>"; };
|
||||
54ACC28A21061B3C0020715F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -160,6 +192,13 @@
|
||||
54AD4E0B2301853D000AE386 /* NSString+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+Ext.m"; sourceTree = "<group>"; };
|
||||
54AD4EE42305AF60000AE386 /* baRSS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = baRSS.entitlements; sourceTree = "<group>"; };
|
||||
54AD4EE62305B17D000AE386 /* container-migration.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "container-migration.plist"; sourceTree = "<group>"; };
|
||||
54AD90E72E30C48400160925 /* QLOPML.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = QLOPML.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
54AD90E92E30C48400160925 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; };
|
||||
54AD90EC2E30C48400160925 /* PreviewViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PreviewViewController.h; sourceTree = "<group>"; };
|
||||
54AD90ED2E30C48400160925 /* PreviewViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PreviewViewController.m; sourceTree = "<group>"; };
|
||||
54AD90F02E30C48400160925 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PreviewViewController.xib; sourceTree = "<group>"; };
|
||||
54AD90F22E30C48400160925 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
54AD90F32E30C48400160925 /* QLOPML.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QLOPML.entitlements; sourceTree = "<group>"; };
|
||||
54B51702226DC339006C1B29 /* ModalFeedEditView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ModalFeedEditView.h; sourceTree = "<group>"; };
|
||||
54B51703226DC339006C1B29 /* ModalFeedEditView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ModalFeedEditView.m; sourceTree = "<group>"; };
|
||||
54B517052270E8C6006C1B29 /* NSView+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSView+Ext.h"; sourceTree = "<group>"; };
|
||||
@@ -175,12 +214,16 @@
|
||||
54BB048721FD2AB500C303A5 /* NSDate+Ext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+Ext.h"; sourceTree = "<group>"; };
|
||||
54BB048821FD2AB500C303A5 /* NSDate+Ext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+Ext.m"; sourceTree = "<group>"; };
|
||||
54BF444922D0F4F300660096 /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = "<group>"; };
|
||||
54D10DD92C6E930F0008F621 /* RegexFeed.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RegexFeed.h; sourceTree = "<group>"; };
|
||||
54D10DDA2C6E930F0008F621 /* RegexFeed.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RegexFeed.m; sourceTree = "<group>"; };
|
||||
54D55D7122E624CD00057B98 /* SettingsFeeds+DragDrop.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SettingsFeeds+DragDrop.h"; sourceTree = "<group>"; };
|
||||
54D55D7222E624CD00057B98 /* SettingsFeeds+DragDrop.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SettingsFeeds+DragDrop.m"; sourceTree = "<group>"; };
|
||||
54D857CC227C5785001BA1C8 /* RefreshStatisticsView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RefreshStatisticsView.h; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -201,7 +244,15 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
544DCCB9212A2B4D002DBC46 /* RSXML2.framework in Frameworks */,
|
||||
5483296C2A3CDC38000688B9 /* RSXML2.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54AD90E42E30C48400160925 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
54AD90EA2E30C48400160925 /* Quartz.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -223,13 +274,26 @@
|
||||
path = "Status Bar Menu";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54253C862C49A5A900742695 /* Regex Editor */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54253C8B2C49A92400742695 /* RegexConverterModal.h */,
|
||||
54253C8A2C49A92400742695 /* RegexConverterModal.m */,
|
||||
54253C882C49A6A800742695 /* RegexConverterController.h */,
|
||||
54253C872C49A6A800742695 /* RegexConverterController.m */,
|
||||
54253C832C47368F00742695 /* RegexConverterView.h */,
|
||||
54253C842C47369000742695 /* RegexConverterView.m */,
|
||||
54D10DD92C6E930F0008F621 /* RegexFeed.h */,
|
||||
54D10DDA2C6E930F0008F621 /* RegexFeed.m */,
|
||||
);
|
||||
path = "Regex Editor";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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,17 +302,21 @@
|
||||
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>";
|
||||
};
|
||||
544FBD4321064AEB008A260C /* Frameworks */ = {
|
||||
5469E1372EA90C3500D46CE7 /* Notifications */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
544DCCB8212A2B4D002DBC46 /* RSXML2.framework */,
|
||||
544DCCBD212A2B6F002DBC46 /* RSXML2.framework.dSYM */,
|
||||
5469E13A2EA90C6C00D46CE7 /* NotifyEndpoint.h */,
|
||||
5469E13B2EA90C6C00D46CE7 /* NotifyEndpoint.m */,
|
||||
);
|
||||
name = Frameworks;
|
||||
path = Notifications;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
546FC44D2118B357007CC3A3 /* Preferences */ = {
|
||||
@@ -266,6 +334,15 @@
|
||||
path = Preferences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5483295F2A3CDB22000688B9 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
548329652A3CDB22000688B9 /* RSXML2.framework */,
|
||||
548329672A3CDB22000688B9 /* RSXML2Tests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54A07A8322105E0800082C51 /* Core Data */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -279,29 +356,26 @@
|
||||
5477D34D21233C62002BA27F /* FeedGroup+Ext.m */,
|
||||
540F704321B6C16C0022E69D /* FeedMeta+Ext.h */,
|
||||
540F704421B6C16C0022E69D /* FeedMeta+Ext.m */,
|
||||
54253C7A2C47303A00742695 /* RegexConverter+Ext.h */,
|
||||
54253C7E2C47303A00742695 /* RegexConverter+Ext.m */,
|
||||
54B749DE220635BE0022CC6D /* FeedArticle+Ext.h */,
|
||||
54B749DF220635CD0022CC6D /* FeedArticle+Ext.m */,
|
||||
);
|
||||
path = "Core Data";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54A2D63422EF8193007C61F3 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54A2D63822EF8193007C61F3 /* QLOPML.qlgenerator */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54ACC27321061B3B0020715F = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
546BD1882EDE156000943942 /* Config.xcconfig */,
|
||||
546BD1892EDE156000943942 /* Config-debug.xcconfig */,
|
||||
540CD14821C094A2004AB594 /* README.md */,
|
||||
54892F1D2235285700271CBA /* CHANGELOG.md */,
|
||||
54ACC27E21061B3B0020715F /* baRSS */,
|
||||
54A2D62E22EF8183007C61F3 /* QLOPML.xcodeproj */,
|
||||
5483295E2A3CDB22000688B9 /* RSXML2.xcodeproj */,
|
||||
54AD90EB2E30C48400160925 /* QLOPML */,
|
||||
54AD90E82E30C48400160925 /* Frameworks */,
|
||||
54ACC27D21061B3B0020715F /* Products */,
|
||||
544FBD4321064AEB008A260C /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -309,6 +383,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54ACC27C21061B3B0020715F /* baRSS Beta.app */,
|
||||
54AD90E72E30C48400160925 /* QLOPML.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -323,8 +398,10 @@
|
||||
54E9CF2F225913850023696F /* Helper */,
|
||||
544936F721F1E51E00DEE9AA /* NSCategories */,
|
||||
541A90EF21257D4F002680A6 /* Status Bar Menu */,
|
||||
5469E1372EA90C3500D46CE7 /* Notifications */,
|
||||
54A07A8322105E0800082C51 /* Core Data */,
|
||||
54AD4E04230084FD000AE386 /* Feed Import */,
|
||||
54253C862C49A5A900742695 /* Regex Editor */,
|
||||
546FC44D2118B357007CC3A3 /* Preferences */,
|
||||
54ACC28A21061B3C0020715F /* Info.plist */,
|
||||
54F7101322EE0DDA006985D1 /* Artwork */,
|
||||
@@ -352,6 +429,29 @@
|
||||
path = "Feed Import";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54AD90E82E30C48400160925 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54AD90E92E30C48400160925 /* Quartz.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54AD90EB2E30C48400160925 /* QLOPML */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
544F5A6F2E30EFC700674F81 /* opml-lib.h */,
|
||||
544F5A702E30EFC700674F81 /* opml-lib.m */,
|
||||
54AD90EC2E30C48400160925 /* PreviewViewController.h */,
|
||||
54AD90ED2E30C48400160925 /* PreviewViewController.m */,
|
||||
54AD90EF2E30C48400160925 /* PreviewViewController.xib */,
|
||||
54AD90F22E30C48400160925 /* Info.plist */,
|
||||
54AD90F32E30C48400160925 /* QLOPML.entitlements */,
|
||||
544F5A722E30EFC700674F81 /* style.css */,
|
||||
);
|
||||
path = QLOPML;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54D857CF228022AB001BA1C8 /* General Tab */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -413,6 +513,8 @@
|
||||
54209E932117325100F3B5EF /* DrawImage.m */,
|
||||
54910065233A4D4000858AE2 /* URLScheme.h */,
|
||||
54910066233A4D4000858AE2 /* URLScheme.m */,
|
||||
54229F532E02491A0019ACB0 /* TinySVG.h */,
|
||||
54229F542E02491A0019ACB0 /* TinySVG.m */,
|
||||
);
|
||||
path = Helper;
|
||||
sourceTree = "<group>";
|
||||
@@ -437,27 +539,44 @@
|
||||
54ACC27921061B3B0020715F /* Frameworks */,
|
||||
54ACC27A21061B3B0020715F /* Resources */,
|
||||
544DCCBB212A2B4D002DBC46 /* Embed Frameworks */,
|
||||
54CE4D4522EF509400E89C16 /* CopyFiles */,
|
||||
544DCCBC212A2B5A002DBC46 /* CopyFiles */,
|
||||
543964EE2215C27B0016AAA3 /* ShellScript */,
|
||||
54FB05D12305BFAB00A088AD /* ShellScript */,
|
||||
54FB05D12305BFAB00A088AD /* dynamic app name in db migration */,
|
||||
54AD90F62E30C48400160925 /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
54AD90F52E30C48400160925 /* PBXTargetDependency */,
|
||||
);
|
||||
name = baRSS;
|
||||
productName = baRRS;
|
||||
productReference = 54ACC27C21061B3B0020715F /* baRSS Beta.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
54AD90E62E30C48400160925 /* QLOPML */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 54AD90F82E30C48400160925 /* Build configuration list for PBXNativeTarget "QLOPML" */;
|
||||
buildPhases = (
|
||||
54AD90E32E30C48400160925 /* Sources */,
|
||||
54AD90E42E30C48400160925 /* Frameworks */,
|
||||
54AD90E52E30C48400160925 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = QLOPML;
|
||||
productName = QLOPML;
|
||||
productReference = 54AD90E72E30C48400160925 /* QLOPML.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
54ACC27421061B3B0020715F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0940;
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 2600;
|
||||
ORGANIZATIONNAME = relikd;
|
||||
TargetAttributes = {
|
||||
54ACC27B21061B3B0020715F = {
|
||||
@@ -475,6 +594,9 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
54AD90E62E30C48400160925 = {
|
||||
CreatedOnToolsVersion = 12.4;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 54ACC27721061B3B0020715F /* Build configuration list for PBXProject "baRSS" */;
|
||||
@@ -490,23 +612,31 @@
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = 54A2D63422EF8193007C61F3 /* Products */;
|
||||
ProjectRef = 54A2D62E22EF8183007C61F3 /* QLOPML.xcodeproj */;
|
||||
ProductGroup = 5483295F2A3CDB22000688B9 /* Products */;
|
||||
ProjectRef = 5483295E2A3CDB22000688B9 /* RSXML2.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
54ACC27B21061B3B0020715F /* baRSS */,
|
||||
54AD90E62E30C48400160925 /* QLOPML */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
54A2D63822EF8193007C61F3 /* QLOPML.qlgenerator */ = {
|
||||
548329652A3CDB22000688B9 /* RSXML2.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = RSXML2.framework;
|
||||
remoteRef = 548329642A3CDB22000688B9 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
548329672A3CDB22000688B9 /* RSXML2Tests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = QLOPML.qlgenerator;
|
||||
remoteRef = 54A2D63722EF8193007C61F3 /* PBXContainerItemProxy */;
|
||||
path = RSXML2Tests.xctest;
|
||||
remoteRef = 548329662A3CDB22000688B9 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
@@ -522,28 +652,21 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54AD90E52E30C48400160925 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
54AD90F12E30C48400160925 /* PreviewViewController.xib in Resources */,
|
||||
544F5A752E30EFC700674F81 /* style.css in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
543964EE2215C27B0016AAA3 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# https://crunchybagel.com/auto-incrementing-build-numbers-in-xcode/\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n";
|
||||
};
|
||||
54FB05D12305BFAB00A088AD /* ShellScript */ = {
|
||||
54FB05D12305BFAB00A088AD /* dynamic app name in db migration */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
@@ -551,6 +674,7 @@
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "dynamic app name in db migration";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
@@ -567,8 +691,10 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
54253C932C49BFCD00742695 /* RegexConverterModal.m in Sources */,
|
||||
54B51704226DC339006C1B29 /* ModalFeedEditView.m in Sources */,
|
||||
54AD4E0C2301853D000AE386 /* NSString+Ext.m in Sources */,
|
||||
54D10DDB2C6E930F0008F621 /* RegexFeed.m in Sources */,
|
||||
546A6A2C22C584AF0034E806 /* SettingsAppearanceView.m in Sources */,
|
||||
54E9CF32225914300023696F /* SettingsAbout.m in Sources */,
|
||||
54B749E0220636200022CC6D /* FeedArticle+Ext.m in Sources */,
|
||||
@@ -577,8 +703,10 @@
|
||||
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 */,
|
||||
54253C7F2C47303A00742695 /* RegexConverter+Ext.m in Sources */,
|
||||
5477D34E21233C62002BA27F /* FeedGroup+Ext.m in Sources */,
|
||||
54B6F14A231551B3002C94C9 /* FaviconDownload.m in Sources */,
|
||||
54E4446C2329AE0600BBF481 /* NSError+Ext.m in Sources */,
|
||||
@@ -593,6 +721,7 @@
|
||||
54195886218E1BDB00581B79 /* NSMenu+Ext.m in Sources */,
|
||||
54910067233A4D4000858AE2 /* URLScheme.m in Sources */,
|
||||
54F6025D21C1D4170006D338 /* OpmlFile.m in Sources */,
|
||||
54229F552E02491A0019ACB0 /* TinySVG.m in Sources */,
|
||||
5496B511214D6275003ED4ED /* UserPrefs.m in Sources */,
|
||||
546A6A2F22C585580034E806 /* SettingsAboutView.m in Sources */,
|
||||
54B517072270E990006C1B29 /* NSView+Ext.m in Sources */,
|
||||
@@ -601,21 +730,53 @@
|
||||
541C67C32255470B004D2CE6 /* SettingsAppearance.m in Sources */,
|
||||
54B749DA2204A85C0022CC6D /* BarStatusItem.m in Sources */,
|
||||
546FC43D21188AD5007CC3A3 /* SettingsFeeds.m in Sources */,
|
||||
54253C952C49BFE400742695 /* RegexConverterView.m in Sources */,
|
||||
548C6D0A230C33DE003A1AAF /* NSURL+Ext.m in Sources */,
|
||||
54E88320211B509D00064188 /* ModalFeedEdit.m in Sources */,
|
||||
54195883218A061100581B79 /* Feed+Ext.m in Sources */,
|
||||
5469E13C2EA90C6C00D46CE7 /* NotifyEndpoint.m in Sources */,
|
||||
54501010230E9C8600F0B165 /* FeedDownload.m in Sources */,
|
||||
54209E942117325100F3B5EF /* DrawImage.m in Sources */,
|
||||
54253C942C49BFDC00742695 /* RegexConverterController.m in Sources */,
|
||||
54FE73D021220DEC003EAC65 /* StoreCoordinator.m in Sources */,
|
||||
54A07A7F220E04CF00082C51 /* NSFetchRequest+Ext.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54AD90E32E30C48400160925 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
544F5A762E30EFC700674F81 /* opml-lib.m in Sources */,
|
||||
54AD90EE2E30C48400160925 /* PreviewViewController.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
54AD90F52E30C48400160925 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 54AD90E62E30C48400160925 /* QLOPML */;
|
||||
targetProxy = 54AD90F42E30C48400160925 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
54AD90EF2E30C48400160925 /* PreviewViewController.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
54AD90F02E30C48400160925 /* Base */,
|
||||
);
|
||||
name = PreviewViewController.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
54ACC28E21061B3C0020715F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 546BD1892EDE156000943942 /* Config-debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
@@ -641,6 +802,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -648,7 +810,9 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = UY657LKNHJ;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
@@ -662,7 +826,6 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
@@ -671,6 +834,7 @@
|
||||
};
|
||||
54ACC28F21061B3C0020715F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 546BD1882EDE156000943942 /* Config.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
@@ -696,14 +860,18 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
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;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = UY657LKNHJ;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
@@ -714,7 +882,6 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
@@ -736,16 +903,12 @@
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = baRSS/baRSS.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = UY657LKNHJ;
|
||||
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
"$(PROJECT_DIR)/Carthage/Build/Mac",
|
||||
);
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"APP_NAME=\"\\@\\\"$(PRODUCT_NAME)\\\"\"",
|
||||
@@ -768,9 +931,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"$(FRAMEWORK_SEARCH_PATHS)",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS.beta;
|
||||
PRODUCT_NAME = "$(TARGET_NAME) Beta";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
PRODUCT_NAME = "$(inherited) Beta";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -791,16 +952,12 @@
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = baRSS/baRSS.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = UY657LKNHJ;
|
||||
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
"$(PROJECT_DIR)/Carthage/Build/Mac",
|
||||
);
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"APP_NAME=\"\\@\\\"$(PRODUCT_NAME)\\\"\"",
|
||||
@@ -823,9 +980,47 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"$(FRAMEWORK_SEARCH_PATHS)",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
54AD90F92E30C48400160925 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = QLOPML/QLOPML.entitlements;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = QLOPML/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).QLOPML";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
54AD90FA2E30C48400160925 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = QLOPML/QLOPML.entitlements;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = QLOPML/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).QLOPML";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -850,6 +1045,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
54AD90F82E30C48400160925 /* Build configuration list for PBXNativeTarget "QLOPML" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
54AD90F92E30C48400160925 /* Debug */,
|
||||
54AD90FA2E30C48400160925 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1000"
|
||||
version = "1.3">
|
||||
LastUpgradeVersion = "2600"
|
||||
version = "1.8">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
@@ -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"
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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;
|
||||
@class BarStatusItem, Preferences;
|
||||
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "AppHook.h"
|
||||
#import "DrawImage.h"
|
||||
#import "UserPrefs.h"
|
||||
@@ -29,6 +7,7 @@
|
||||
#import "StoreCoordinator.h"
|
||||
#import "SettingsFeeds+DragDrop.h"
|
||||
#import "URLScheme.h"
|
||||
#import "NotifyEndpoint.h"
|
||||
#import "NSURL+Ext.h"
|
||||
#import "NSError+Ext.h"
|
||||
|
||||
@@ -59,12 +38,18 @@
|
||||
[_statusItem asyncReloadUnreadCount];
|
||||
[UpdateScheduler registerNetworkChangeNotification]; // will call update scheduler
|
||||
if ([StoreCoordinator isEmpty]) {
|
||||
[_statusItem showWelcomeMessage];
|
||||
// stupid macOS bugs ... status-bar-menu-item frame is zero without delay
|
||||
// [_statusItem showWelcomeMessage];
|
||||
[_statusItem performSelector:@selector(showWelcomeMessage) withObject:nil afterDelay:.2];
|
||||
[UpdateScheduler autoDownloadAndParseUpdateURL];
|
||||
} else {
|
||||
// mostly for version migration 0.9.4 ~> 1.0 (favicon storage)
|
||||
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];
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification *)aNotification {
|
||||
|
||||
Binary file not shown.
@@ -1,13 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
|
||||
<linearGradient id="orange" gradientUnits="userSpaceOnUse" x2="100" y2="100">
|
||||
<stop offset="0" style="stop-color:#FF8B00"/>
|
||||
<stop offset="0.5" style="stop-color:#FFAB48"/>
|
||||
<stop offset="1" style="stop-color:#FF8B00"/>
|
||||
<stop offset="0" style="stop-color:#FF8B00"/>
|
||||
<stop offset="0.5" style="stop-color:#FFAB48"/>
|
||||
<stop offset="1" style="stop-color:#FF8B00"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#orange)" d="M0,25v50q0,25,25,25h50q25,0,25,-25v-50q0,-25,-25,-25h-50q-25,0,-25,25z"/>
|
||||
<g fill="#FFFFFF" transform="matrix(-0.75 0 0 0.75 87.5 12.5)">
|
||||
<circle cx="13" cy="13" r="13"/>
|
||||
<path d="M0,45v20Q65,65,65,0h-20Q45,45,0,45z"/>
|
||||
<path d="M0,80v20Q100,100,100,0h-20Q80,80,0,80z"/>
|
||||
<!-- 3 = half stroke width, 28 = 25 + 3, 25 = radius, 44 = 100 - 2*r - 2*3 -->
|
||||
<!-- <path fill="url(#orange)" stroke="#FFF" stroke-width="6" d="M3,28v44q0,25,25,25h44q25,0,25,-25v-44q0,-25,-25,-25h-44q-25,0,-25,25z"/> -->
|
||||
<g transform="translate(10 10) scale(.8 .8)">
|
||||
<path fill="url(#orange)" d="M0,25v50q0,25,25,25h50q25,0,25,-25v-50q0,-25,-25,-25h-50q-25,0,-25,25z"/>
|
||||
<g fill="#FFF" transform="translate(12.5 12.5) scale(.75 .75)">
|
||||
<circle cx="87" cy="13" r="13"/>
|
||||
<path d="M35,0q0,65,65,65v-20q-45,0,-45,-45z"/>
|
||||
<path d="M0,0q0,100,100,100v-20q-80,0,-80,-80z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 663 B After Width: | Height: | Size: 941 B |
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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.
|
||||
|
||||
#ifndef Constants_h
|
||||
#define Constants_h
|
||||
|
||||
@@ -28,7 +6,6 @@
|
||||
// TODO: Add support for media player? image feed?
|
||||
// <enclosure url="https://url.mp3" length="63274022" type="audio/mpeg" />
|
||||
// TODO: Disable 'update all' menu item during update?
|
||||
// TODO: HTML to Feed Generator. https://github.com/RSS-Bridge/rss-bridge
|
||||
// TODO: SQlite instead of CoreData? https://www.objc.io/issues/4-core-data/SQLite-instead-of-core-data/
|
||||
|
||||
|
||||
@@ -57,6 +34,8 @@ static NSImageName const RSSImageMenuBarIconActive = @"RSSImageMenuBarIconActive
|
||||
static NSImageName const RSSImageMenuBarIconPaused = @"RSSImageMenuBarIconPaused";
|
||||
/// Menu item, unread state icon (blue dot)
|
||||
static NSImageName const RSSImageMenuItemUnread = @"RSSImageMenuItemUnread";
|
||||
/// Feed edit, regex editor icon @c "(.*)"
|
||||
static NSImageName const RSSImageRegexIcon = @"RSSImageRegexIcon";
|
||||
|
||||
|
||||
#pragma mark - NSNotificationName constants
|
||||
|
||||
@@ -1,40 +1,24 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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;
|
||||
#import "Feed+CoreDataClass.h"
|
||||
@class RSParsedFeed;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Feed (Ext)
|
||||
@property (readonly) BOOL hasIcon;
|
||||
@property (nonnull, readonly) NSImage* iconImage16;
|
||||
|
||||
// Generator methods / Feed update
|
||||
+ (instancetype)newFeedAndMetaInContext:(NSManagedObjectContext*)context;
|
||||
- (NSString*)notificationID;
|
||||
- (void)updateWithRSS:(RSParsedFeed*)obj postUnreadCountChange:(BOOL)flag;
|
||||
- (NSMenuItem*)newMenuItem;
|
||||
// Getter & Setter
|
||||
- (void)calculateAndSetIndexPathString;
|
||||
- (void)setNewIcon:(NSURL*)location;
|
||||
// Article properties
|
||||
- (NSArray<FeedArticle*>*)sortedArticles;
|
||||
- (nullable NSArray<FeedArticle*>*)sortedArticles;
|
||||
- (NSUInteger)countUnread;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 RSXML2;
|
||||
#import "Feed+Ext.h"
|
||||
#import "Constants.h"
|
||||
@@ -28,6 +6,7 @@
|
||||
#import "FeedGroup+Ext.h"
|
||||
#import "FeedArticle+Ext.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "NotifyEndpoint.h"
|
||||
#import "NSURL+Ext.h"
|
||||
|
||||
@implementation Feed (Ext)
|
||||
@@ -39,6 +18,11 @@
|
||||
return feed;
|
||||
}
|
||||
|
||||
/// unique ID used for notifications. returns @c objectID.URIRepresentation.absoluteString
|
||||
- (NSString*)notificationID {
|
||||
return self.objectID.URIRepresentation.absoluteString;
|
||||
}
|
||||
|
||||
/// Call @c indexPathString on @c .group and update @c .indexPath if current value is different.
|
||||
- (void)calculateAndSetIndexPathString {
|
||||
NSString *pthStr = [self.group indexPathString];
|
||||
@@ -50,7 +34,13 @@
|
||||
- (NSMenuItem*)newMenuItem {
|
||||
NSMenuItem *item = [NSMenuItem new];
|
||||
item.title = self.group.anyName;
|
||||
item.toolTip = self.subtitle;
|
||||
// Tooltip disabled (feed-group only) because it causes issues on macOS Ventura.
|
||||
// Menu opens invisibly (OrderNSWindow: unsupported window ordering op -1)
|
||||
// steps to reproduce:
|
||||
// 1. hover over a feed-group menu item until tooltip pops up
|
||||
// 2. hover over another feed with tooltip
|
||||
// 3. go back to previous feed.
|
||||
// item.toolTip = self.subtitle;
|
||||
item.enabled = (self.articles.count > 0);
|
||||
item.image = self.iconImage16;
|
||||
item.representedObject = self.indexPath;
|
||||
@@ -105,6 +95,8 @@
|
||||
[localSet removeObject:stored];
|
||||
if (stored.sortIndex != currentIndex)
|
||||
stored.sortIndex = currentIndex; // Ensures block of ascending indices
|
||||
// replace local values with remote changes (if any)
|
||||
[stored updateArticleIfChanged:article];
|
||||
} else {
|
||||
FeedArticle *newArticle = [FeedArticle newArticle:article inContext:self.managedObjectContext];
|
||||
newArticle.sortIndex = currentIndex;
|
||||
@@ -124,10 +116,12 @@
|
||||
- (NSUInteger)deleteArticles:(NSMutableSet<FeedArticle*>*)localSet withRemoteSet:(NSArray<RSParsedArticle*>*)remoteSet {
|
||||
NSUInteger c = 0;
|
||||
NSMutableSet<FeedArticle*> *deletingSet = [NSMutableSet setWithCapacity:localSet.count];
|
||||
NSMutableArray *dismissed = [NSMutableArray array];
|
||||
for (FeedArticle *fa in localSet) {
|
||||
if (![self findLocalArticle:fa inRemoteSet:remoteSet]) {
|
||||
if (fa.unread) ++c;
|
||||
// TODO: keep unread articles?
|
||||
[dismissed addObject:fa.notificationID];
|
||||
[self.managedObjectContext deleteObject:fa];
|
||||
[deletingSet addObject:fa];
|
||||
}
|
||||
@@ -135,6 +129,7 @@
|
||||
if (deletingSet.count > 0) {
|
||||
[localSet minusSet:deletingSet];
|
||||
[self removeArticles:deletingSet];
|
||||
[NotifyEndpoint dismiss:dismissed];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -146,7 +141,7 @@
|
||||
/**
|
||||
@return Articles sorted by attribute @c sortIndex with descending order (newest items first).
|
||||
*/
|
||||
- (NSArray<FeedArticle*>*)sortedArticles {
|
||||
- (nullable NSArray<FeedArticle*>*)sortedArticles {
|
||||
if (self.articles.count == 0)
|
||||
return nil;
|
||||
return [self.articles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:NO]]];
|
||||
@@ -158,11 +153,13 @@
|
||||
- (FeedArticle*)findRemoteArticle:(RSParsedArticle*)remote inLocalSet:(NSSet<FeedArticle*>*)localSet {
|
||||
NSString *searchLink = remote.link;
|
||||
NSString *searchGuid = remote.guid;
|
||||
BOOL linkIsNil = (searchLink == nil);
|
||||
BOOL guidIsNil = (searchGuid == nil);
|
||||
for (FeedArticle *art in localSet) {
|
||||
if ((linkIsNil && art.link == nil) || (!linkIsNil && [art.link isEqualToString:searchLink])) {
|
||||
if ((guidIsNil && art.guid == nil) || (!guidIsNil && [art.guid isEqualToString:searchGuid]))
|
||||
// assuming if a guid is set, it will always be unique
|
||||
if (searchGuid != nil) {
|
||||
if ([art.guid isEqualToString:searchGuid])
|
||||
return art;
|
||||
} else if (searchLink != nil) {
|
||||
if ([art.link isEqualToString:searchLink])
|
||||
return art;
|
||||
}
|
||||
}
|
||||
@@ -175,17 +172,29 @@
|
||||
- (RSParsedArticle*)findLocalArticle:(FeedArticle*)local inRemoteSet:(NSArray<RSParsedArticle*>*)remoteSet {
|
||||
NSString *searchLink = local.link;
|
||||
NSString *searchGuid = local.guid;
|
||||
BOOL linkIsNil = (searchLink == nil);
|
||||
BOOL guidIsNil = (searchGuid == nil);
|
||||
for (RSParsedArticle *art in remoteSet) {
|
||||
if ((linkIsNil && art.link == nil) || (!linkIsNil && [art.link isEqualToString:searchLink])) {
|
||||
if ((guidIsNil && art.guid == nil) || (!guidIsNil && [art.guid isEqualToString:searchGuid]))
|
||||
// assuming if a guid is set, it will always be unique
|
||||
if (searchGuid != nil) {
|
||||
if ([art.guid isEqualToString:searchGuid])
|
||||
return art;
|
||||
} else if (searchLink != nil) {
|
||||
if ([art.link isEqualToString:searchLink])
|
||||
return art;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
/// Number of unread articles
|
||||
- (NSUInteger)countUnread {
|
||||
NSUInteger count = 0;
|
||||
for (FeedArticle *article in self.articles) {
|
||||
if (article.unread)
|
||||
count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Icon -
|
||||
|
||||
@@ -196,7 +205,8 @@
|
||||
if (self.articles.count == 0) {
|
||||
img = [NSImage imageNamed:NSImageNameCaution];
|
||||
} else if (self.hasIcon) {
|
||||
img = [[NSImage alloc] initByReferencingURL:[self iconPath]];
|
||||
NSData* data = [[NSData alloc] initWithContentsOfURL:[self iconPath]];
|
||||
img = [[NSImage alloc] initWithData:data];
|
||||
} else {
|
||||
img = [NSImage imageNamed:RSSImageDefaultRSSIcon];
|
||||
}
|
||||
|
||||
@@ -1,30 +1,14 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
#import "FeedArticle+CoreDataClass.h"
|
||||
@class RSParsedArticle;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FeedArticle (Ext)
|
||||
+ (instancetype)newArticle:(RSParsedArticle*)entry inContext:(NSManagedObjectContext*)moc;
|
||||
- (NSString*)notificationID;
|
||||
- (void)updateArticleIfChanged:(RSParsedArticle*)entry;
|
||||
- (NSMenuItem*)newMenuItem;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,30 +1,10 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 RSXML2.RSParsedArticle;
|
||||
#import "FeedArticle+Ext.h"
|
||||
#import "Feed+Ext.h"
|
||||
#import "Constants.h"
|
||||
#import "UserPrefs.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "NotifyEndpoint.h"
|
||||
#import "NSString+Ext.h"
|
||||
|
||||
@implementation FeedArticle (Ext)
|
||||
@@ -47,6 +27,21 @@
|
||||
return fa;
|
||||
}
|
||||
|
||||
/// unique ID used for notifications. returns @c objectID.URIRepresentation.absoluteString
|
||||
- (NSString*)notificationID {
|
||||
return self.objectID.URIRepresentation.absoluteString;
|
||||
}
|
||||
|
||||
- (void)updateArticleIfChanged:(RSParsedArticle*)entry {
|
||||
[self setGuidIfChanged:entry.guid];
|
||||
[self setTitleIfChanged:entry.title];
|
||||
[self setAuthorIfChanged:entry.author];
|
||||
[self setAbstractIfChanged:(entry.abstract.length > 0) ? [entry.abstract htmlToPlainText] : nil];
|
||||
[self setBodyIfChanged:(entry.body.length > 0) ? [entry.body htmlToPlainText] : nil];
|
||||
[self setLinkIfChanged:(entry.link.length > 0) ? entry.link : entry.guid];
|
||||
[self setPublishedIfChanged:entry.datePublished ? entry.datePublished : entry.dateModified];
|
||||
}
|
||||
|
||||
/// @return Full or truncated article title, based on user preference in settings.
|
||||
- (NSString*)shortArticleName {
|
||||
NSString *title = self.title;
|
||||
@@ -89,8 +84,84 @@
|
||||
[StoreCoordinator saveContext:moc andParent:YES];
|
||||
NSNumber *num = (fa.unread ? @+1 : @-1);
|
||||
PostNotification(kNotificationTotalUnreadCountChanged, num);
|
||||
|
||||
[NotifyEndpoint dismiss:fa.feed.countUnread > 0 ? @[fa.notificationID] : @[fa.notificationID, fa.feed.notificationID]];
|
||||
}
|
||||
[moc reset];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Setter -
|
||||
|
||||
|
||||
/// Set @c guid attribute but only if value differs.
|
||||
- (void)setGuidIfChanged:(nullable NSString*)guid {
|
||||
if (guid.length == 0) {
|
||||
if (self.guid.length > 0)
|
||||
self.guid = nil; // nullify empty strings
|
||||
} else if (![self.guid isEqualToString: guid]) {
|
||||
self.guid = guid;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c link attribute but only if value differs.
|
||||
- (void)setLinkIfChanged:(nullable NSString*)link {
|
||||
if (link.length == 0) {
|
||||
if (self.link.length > 0)
|
||||
self.link = nil; // nullify empty strings
|
||||
} else if (![self.link isEqualToString: link]) {
|
||||
self.link = link;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c title attribute but only if value differs.
|
||||
- (void)setTitleIfChanged:(nullable NSString*)title {
|
||||
if (title.length == 0) {
|
||||
if (self.title.length > 0)
|
||||
self.title = nil; // nullify empty strings
|
||||
} else if (![self.title isEqualToString: title]) {
|
||||
self.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c abstract attribute but only if value differs.
|
||||
- (void)setAbstractIfChanged:(nullable NSString*)abstract {
|
||||
if (abstract.length == 0) {
|
||||
if (self.abstract.length > 0)
|
||||
self.abstract = nil; // nullify empty strings
|
||||
} else if (![self.abstract isEqualToString: abstract]) {
|
||||
self.abstract = abstract;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c body attribute but only if value differs.
|
||||
- (void)setBodyIfChanged:(nullable NSString*)body {
|
||||
if (body.length == 0) {
|
||||
if (self.body.length > 0)
|
||||
self.body = nil; // nullify empty strings
|
||||
} else if (![self.body isEqualToString: body]) {
|
||||
self.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c author attribute but only if value differs.
|
||||
- (void)setAuthorIfChanged:(nullable NSString*)author {
|
||||
if (author.length == 0) {
|
||||
if (self.author.length > 0)
|
||||
self.author = nil; // nullify empty strings
|
||||
} else if (![self.author isEqualToString: author]) {
|
||||
self.author = author;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c published attribute but only if value differs.
|
||||
- (void)setPublishedIfChanged:(nullable NSDate*)published {
|
||||
if (!published) {
|
||||
if (self.published)
|
||||
self.published = nil; // nullify empty date
|
||||
} else if (![self.published isEqualToDate: published]) {
|
||||
self.published = published;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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;
|
||||
#import "FeedGroup+CoreDataClass.h"
|
||||
|
||||
@@ -29,6 +7,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,14 +18,16 @@ 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;
|
||||
// Handle children and parents
|
||||
- (NSString*)indexPathString;
|
||||
- (NSArray<FeedGroup*>*)sortedChildren;
|
||||
- (nullable NSArray<FeedGroup*>*)sortedChildren;
|
||||
- (NSMutableArray<FeedGroup*>*)allParents;
|
||||
// Printing
|
||||
- (NSString*)readableDescription;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "FeedGroup+Ext.h"
|
||||
#import "Feed+Ext.h"
|
||||
#import "StoreCoordinator.h"
|
||||
@@ -78,7 +56,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)
|
||||
@@ -89,10 +67,11 @@
|
||||
- (void)setSortIndexIfChanged:(int32_t)sortIndex {
|
||||
if (self.sortIndex != sortIndex) {
|
||||
self.sortIndex = sortIndex;
|
||||
[self iterateSorted:NO overDescendantFeeds:^(Feed *feed, BOOL *cancel) {
|
||||
[feed calculateAndSetIndexPathString];
|
||||
}];
|
||||
}
|
||||
// Otherwise move from 0.0 -> 0 will not trigger index path update
|
||||
[self iterateSorted:NO overDescendantFeeds:^(Feed *feed, BOOL *cancel) {
|
||||
[feed calculateAndSetIndexPathString];
|
||||
}];
|
||||
}
|
||||
|
||||
/// Set @c name attribute but only if value differs.
|
||||
@@ -127,7 +106,7 @@
|
||||
}
|
||||
|
||||
/// @return Children sorted by attribute @c sortIndex (same order as in preferences).
|
||||
- (NSArray<FeedGroup*>*)sortedChildren {
|
||||
- (nullable NSArray<FeedGroup*>*)sortedChildren {
|
||||
if (self.children.count == 0)
|
||||
return nil;
|
||||
return [self.children sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:YES]]];
|
||||
|
||||
@@ -1,30 +1,10 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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;
|
||||
#import "FeedMeta+CoreDataClass.h"
|
||||
|
||||
static int32_t const kDefaultFeedRefreshInterval = 30 * 60;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FeedMeta (Ext)
|
||||
+ (instancetype)newMetaInContext:(NSManagedObjectContext*)moc;
|
||||
// HTTP response
|
||||
@@ -35,3 +15,5 @@ static int32_t const kDefaultFeedRefreshInterval = 30 * 60;
|
||||
- (void)setRefreshIfChanged:(int32_t)refresh;
|
||||
- (void)scheduleNow:(NSTimeInterval)future;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "FeedMeta+Ext.h"
|
||||
#import "Feed+Ext.h"
|
||||
#import "FeedGroup+Ext.h"
|
||||
|
||||
@@ -1,27 +1,7 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 NSFetchRequest<ResultType> (Ext)
|
||||
// Perform core data request and fetch data
|
||||
- (NSArray<ResultType>*)fetchAllRows:(NSManagedObjectContext*)moc;
|
||||
@@ -37,3 +17,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
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "NSFetchRequest+Ext.h"
|
||||
#import "NSError+Ext.h"
|
||||
|
||||
|
||||
16
baRSS/Core Data/RegexConverter+Ext.h
Normal file
16
baRSS/Core Data/RegexConverter+Ext.h
Normal file
@@ -0,0 +1,16 @@
|
||||
@import Cocoa;
|
||||
#import "RegexConverter+CoreDataClass.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RegexConverter (Ext)
|
||||
+ (instancetype)newInContext:(NSManagedObjectContext*)moc;
|
||||
- (void)setEntryIfChanged:(nullable NSString*)pattern;
|
||||
- (void)setHrefIfChanged:(nullable NSString*)pattern;
|
||||
- (void)setTitleIfChanged:(nullable NSString*)pattern;
|
||||
- (void)setDescIfChanged:(nullable NSString*)pattern;
|
||||
- (void)setDateIfChanged:(nullable NSString*)pattern;
|
||||
- (void)setDateFormatIfChanged:(nullable NSString*)pattern;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
70
baRSS/Core Data/RegexConverter+Ext.m
Normal file
70
baRSS/Core Data/RegexConverter+Ext.m
Normal file
@@ -0,0 +1,70 @@
|
||||
#import "RegexConverter+Ext.h"
|
||||
|
||||
@implementation RegexConverter (Ext)
|
||||
|
||||
/// Create new instance
|
||||
+ (instancetype)newInContext:(NSManagedObjectContext*)moc {
|
||||
return [[RegexConverter alloc] initWithEntity:[RegexConverter entity] insertIntoManagedObjectContext:moc];
|
||||
}
|
||||
|
||||
/// Set @c entry attribute but only if value differs.
|
||||
- (void)setEntryIfChanged:(nullable NSString*)pattern {
|
||||
if (pattern.length == 0) {
|
||||
if (self.entry.length > 0)
|
||||
self.entry = nil; // nullify empty strings
|
||||
} else if (![self.entry isEqualToString: pattern]) {
|
||||
self.entry = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c href attribute but only if value differs.
|
||||
- (void)setHrefIfChanged:(nullable NSString*)pattern {
|
||||
if (pattern.length == 0) {
|
||||
if (self.href.length > 0)
|
||||
self.href = nil; // nullify empty strings
|
||||
} else if (![self.href isEqualToString: pattern]) {
|
||||
self.href = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c title attribute but only if value differs.
|
||||
- (void)setTitleIfChanged:(nullable NSString*)pattern {
|
||||
if (pattern.length == 0) {
|
||||
if (self.title.length > 0)
|
||||
self.title = nil; // nullify empty strings
|
||||
} else if (![self.title isEqualToString: pattern]) {
|
||||
self.title = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c desc attribute but only if value differs.
|
||||
- (void)setDescIfChanged:(nullable NSString*)pattern {
|
||||
if (pattern.length == 0) {
|
||||
if (self.desc.length > 0)
|
||||
self.desc = nil; // nullify empty strings
|
||||
} else if (![self.desc isEqualToString: pattern]) {
|
||||
self.desc = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c date attribute but only if value differs.
|
||||
- (void)setDateIfChanged:(nullable NSString*)pattern {
|
||||
if (pattern.length == 0) {
|
||||
if (self.date.length > 0)
|
||||
self.date = nil; // nullify empty strings
|
||||
} else if (![self.date isEqualToString: pattern]) {
|
||||
self.date = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set @c dateFormat attribute but only if value differs.
|
||||
- (void)setDateFormatIfChanged:(nullable NSString*)pattern {
|
||||
if (pattern.length == 0) {
|
||||
if (self.dateFormat.length > 0)
|
||||
self.dateFormat = nil; // nullify empty strings
|
||||
} else if (![self.dateFormat isEqualToString: pattern]) {
|
||||
self.dateFormat = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,28 +1,8 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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;
|
||||
#import "DBv1+CoreDataModel.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface StoreCoordinator : NSObject
|
||||
// Managing contexts
|
||||
+ (NSManagedObjectContext*)getMainContext;
|
||||
@@ -44,14 +24,17 @@
|
||||
+ (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;
|
||||
|
||||
// Unread articles list & mark articled read
|
||||
+ (NSArray<FeedArticle*>*)articlesAtPath:(nullable NSString*)path isFeed:(BOOL)feedFlag sorted:(BOOL)sortFlag unread:(BOOL)readFlag inContext:(NSManagedObjectContext*)moc limit:(NSUInteger)limit;
|
||||
+ (nullable NSArray<NSString*>*)updateArticles:(NSArray<FeedArticle*>*)list markRead:(BOOL)markRead andOpen:(BOOL)openLinks inContext:(NSManagedObjectContext*)moc;
|
||||
|
||||
// Restore sound state
|
||||
+ (void)cleanupAndShowAlert:(BOOL)flag;
|
||||
+ (NSUInteger)cleanupFavicons;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,30 +1,10 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "StoreCoordinator.h"
|
||||
#import "AppHook.h"
|
||||
#import "Constants.h"
|
||||
#import "FaviconDownload.h"
|
||||
#import "UserPrefs.h"
|
||||
#import "Feed+Ext.h"
|
||||
#import "FeedArticle+Ext.h"
|
||||
#import "NSURL+Ext.h"
|
||||
#import "NSError+Ext.h"
|
||||
#import "NSFetchRequest+Ext.h"
|
||||
@@ -79,7 +59,9 @@
|
||||
opt = [[Options alloc] initWithEntity:Options.entity insertIntoManagedObjectContext:moc];
|
||||
opt.key = key;
|
||||
}
|
||||
opt.value = value;
|
||||
if (opt.value != value) {
|
||||
opt.value = value;
|
||||
}
|
||||
[self saveContext:moc andParent:YES];
|
||||
[moc reset];
|
||||
}
|
||||
@@ -144,7 +126,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]];
|
||||
}
|
||||
|
||||
@@ -222,6 +204,50 @@
|
||||
return [fr fetchAllRows:moc];
|
||||
}
|
||||
|
||||
/**
|
||||
For provided articles, pen link, mark read, and save changes.
|
||||
@warning Will invalidate context.
|
||||
|
||||
@param list Should only contain @c FeedArticle
|
||||
@param markRead Whether the articles should be marked read or unread.
|
||||
@param openLinks Whether to open the link or mark read without opening
|
||||
|
||||
@return @c notificationID for all articles that were opened (empty if @c openLinks=NO or open failed).
|
||||
*/
|
||||
+ (nullable NSArray<NSString*>*)updateArticles:(NSArray<FeedArticle*>*)list markRead:(BOOL)markRead andOpen:(BOOL)openLinks inContext:(NSManagedObjectContext*)moc {
|
||||
if (openLinks) {
|
||||
NSMutableArray<NSURL*> *urls = [NSMutableArray arrayWithCapacity:list.count];
|
||||
for (FeedArticle *fa in list) {
|
||||
if (fa.link.length > 0)
|
||||
[urls addObject:[NSURL URLWithString:fa.link]];
|
||||
}
|
||||
if (urls.count > 0 && !UserPrefsOpenURLs(urls))
|
||||
return nil; // if success == NO, do not modify unread state & exit
|
||||
}
|
||||
|
||||
NSInteger countChange = 0;
|
||||
for (FeedArticle *fa in list) {
|
||||
if (fa.unread == markRead) { // only if differs
|
||||
fa.unread = !markRead;
|
||||
countChange += markRead ? -1 : +1;
|
||||
}
|
||||
}
|
||||
[self saveContext:moc andParent:YES];
|
||||
|
||||
// gather uri-ids for notification dismiss
|
||||
NSMutableArray<NSString*> *dbRefs = [NSMutableArray array];
|
||||
if (markRead) {
|
||||
for (FeedArticle *fa in list) {
|
||||
[dbRefs addObject:fa.notificationID];
|
||||
[dbRefs addObject:fa.feed.notificationID];
|
||||
}
|
||||
}
|
||||
|
||||
[moc reset];
|
||||
PostNotification(kNotificationTotalUnreadCountChanged, @(countChange));
|
||||
return dbRefs;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Restore Sound State
|
||||
|
||||
|
||||
@@ -1,52 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14460.32" systemVersion="17G8030" minimumToolsVersion="Automatic" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="v1.0.0">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="19H2026" minimumToolsVersion="Automatic" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="v1.0.0">
|
||||
<entity name="Feed" representedClassName="Feed" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="indexPath" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="link" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="articles" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="FeedArticle" inverseName="feed" inverseEntity="FeedArticle" syncable="YES"/>
|
||||
<relationship name="group" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedGroup" inverseName="feed" inverseEntity="FeedGroup" syncable="YES"/>
|
||||
<relationship name="meta" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="FeedMeta" inverseName="feed" inverseEntity="FeedMeta" syncable="YES"/>
|
||||
<attribute name="indexPath" optional="YES" attributeType="String"/>
|
||||
<attribute name="link" optional="YES" attributeType="String"/>
|
||||
<attribute name="subtitle" optional="YES" attributeType="String"/>
|
||||
<attribute name="title" optional="YES" attributeType="String"/>
|
||||
<relationship name="articles" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="FeedArticle" inverseName="feed" inverseEntity="FeedArticle"/>
|
||||
<relationship name="group" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedGroup" inverseName="feed" inverseEntity="FeedGroup"/>
|
||||
<relationship name="meta" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="FeedMeta" inverseName="feed" inverseEntity="FeedMeta"/>
|
||||
<relationship name="regex" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="RegexConverter" inverseName="feed" inverseEntity="RegexConverter"/>
|
||||
</entity>
|
||||
<entity name="FeedArticle" representedClassName="FeedArticle" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="abstract" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="author" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="body" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="guid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="link" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="published" optional="YES" attributeType="Date" usesScalarValueType="NO" customClassName="NSArray" syncable="YES"/>
|
||||
<attribute name="sortIndex" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="unread" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Feed" inverseName="articles" inverseEntity="Feed" syncable="YES"/>
|
||||
<attribute name="abstract" optional="YES" attributeType="String"/>
|
||||
<attribute name="author" optional="YES" attributeType="String"/>
|
||||
<attribute name="body" optional="YES" attributeType="String"/>
|
||||
<attribute name="guid" optional="YES" attributeType="String"/>
|
||||
<attribute name="link" optional="YES" attributeType="String"/>
|
||||
<attribute name="published" optional="YES" attributeType="Date" usesScalarValueType="NO" customClassName="NSArray"/>
|
||||
<attribute name="sortIndex" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="title" optional="YES" attributeType="String"/>
|
||||
<attribute name="unread" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Feed" inverseName="articles" inverseEntity="Feed"/>
|
||||
</entity>
|
||||
<entity name="FeedGroup" representedClassName="FeedGroup" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="sortIndex" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="type" optional="YES" attributeType="Integer 16" defaultValueString="-1" usesScalarValueType="YES" syncable="YES"/>
|
||||
<relationship name="children" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="FeedGroup" inverseName="parent" inverseEntity="FeedGroup" syncable="YES"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Feed" inverseName="group" inverseEntity="Feed" syncable="YES"/>
|
||||
<relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedGroup" inverseName="children" inverseEntity="FeedGroup" syncable="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sortIndex" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="type" optional="YES" attributeType="Integer 16" defaultValueString="-1" usesScalarValueType="YES"/>
|
||||
<relationship name="children" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="FeedGroup" inverseName="parent" inverseEntity="FeedGroup"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Feed" inverseName="group" inverseEntity="Feed"/>
|
||||
<relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedGroup" inverseName="children" inverseEntity="FeedGroup"/>
|
||||
</entity>
|
||||
<entity name="FeedMeta" representedClassName="FeedMeta" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="errorCount" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="etag" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="modified" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="refresh" optional="YES" attributeType="Integer 32" defaultValueString="-1" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="scheduled" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="url" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Feed" inverseName="meta" inverseEntity="Feed" syncable="YES"/>
|
||||
<attribute name="errorCount" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="etag" optional="YES" attributeType="String"/>
|
||||
<attribute name="modified" optional="YES" attributeType="String"/>
|
||||
<attribute name="refresh" optional="YES" attributeType="Integer 32" defaultValueString="-1" usesScalarValueType="YES"/>
|
||||
<attribute name="scheduled" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Feed" inverseName="meta" inverseEntity="Feed"/>
|
||||
</entity>
|
||||
<entity name="Options" representedClassName="Options" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="key" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="value" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="key" optional="YES" attributeType="String"/>
|
||||
<attribute name="value" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="RegexConverter" representedClassName="RegexConverter" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="date" optional="YES" attributeType="String"/>
|
||||
<attribute name="dateFormat" optional="YES" attributeType="String"/>
|
||||
<attribute name="desc" optional="YES" attributeType="String"/>
|
||||
<attribute name="entry" optional="YES" attributeType="String"/>
|
||||
<attribute name="href" optional="YES" attributeType="String"/>
|
||||
<attribute name="title" optional="YES" attributeType="String"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Feed" inverseName="regex" inverseEntity="Feed"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Feed" positionX="-278.84765625" positionY="-112.953125" width="128" height="150"/>
|
||||
<element name="Feed" positionX="-278.84765625" positionY="-112.953125" width="128" height="163"/>
|
||||
<element name="FeedArticle" positionX="-96.77734375" positionY="-113.83984375" width="128" height="195"/>
|
||||
<element name="FeedGroup" positionX="-460.37890625" positionY="-111.62890625" width="130.52734375" height="135"/>
|
||||
<element name="FeedMeta" positionX="-348.02734375" positionY="136.89453125" width="128" height="150"/>
|
||||
<element name="Options" positionX="-279" positionY="36" width="128" height="75"/>
|
||||
<element name="FeedMeta" positionX="-456.265625" positionY="62.41015625" width="128" height="150"/>
|
||||
<element name="Options" positionX="-279.09375" positionY="91.4609375" width="128" height="75"/>
|
||||
<element name="RegexConverter" positionX="-115.984375" positionY="93.1796875" width="128" height="148"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,32 +1,14 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
#define ENV_LOG_YOUTUBE 1
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// TODO: Make plugins extensible? community extensions.
|
||||
@interface YouTubePlugin : NSObject
|
||||
+ (NSString*)feedURL:(NSURL*)url;
|
||||
+ (nullable NSString*)feedURL:(NSURL*)url data:(NSData*)html;
|
||||
+ (NSString*)videoImage:(NSString*)videoid;
|
||||
+ (NSString*)videoImageHQ:(NSString*)videoid;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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.
|
||||
|
||||
#include "Download3rdParty.h"
|
||||
|
||||
@implementation YouTubePlugin
|
||||
@@ -33,12 +11,13 @@
|
||||
|
||||
@return @c nil if @c url is not properly formatted, YouTube feed URL otherwise.
|
||||
*/
|
||||
+ (NSString*)feedURL:(NSURL*)url {
|
||||
+ (nullable NSString*)feedURL:(NSURL*)url data:(NSData*)html {
|
||||
if (![url.host hasSuffix:@"youtube.com"]) // 'youtu.be' & 'youtube-nocookie.com' will redirect
|
||||
return nil;
|
||||
// https://www.youtube.com/channel/[channel-id]
|
||||
// https://www.youtube.com/user/[user-name]
|
||||
// https://www.youtube.com/playlist?list=[playlist-id]
|
||||
// https://www.youtube.com/c/[channel-name]
|
||||
#if DEBUG && ENV_LOG_YOUTUBE
|
||||
printf("resolving YouTube url:\n");
|
||||
printf(" ↳ %s\n", url.absoluteString.UTF8String);
|
||||
@@ -62,6 +41,23 @@
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if ([type isEqualToString:@"c"]) {
|
||||
NSData *m_head = [@"<meta itemprop=\"channelId\" content=\"" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSRange tmp = [html rangeOfData:m_head options:0 range:NSMakeRange(0, html.length)];
|
||||
if (tmp.location == NSNotFound) {
|
||||
NSData *m_json = [@"\"channelId\":\"" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
tmp = [html rangeOfData:m_json options:0 range:NSMakeRange(0, html.length)];
|
||||
}
|
||||
NSUInteger start = tmp.location + tmp.length;
|
||||
NSUInteger end = html.length - start;
|
||||
if (end > 50) end = 50; // no need to search till the end
|
||||
NSString *substr = [[NSString alloc] initWithData:[html subdataWithRange:NSMakeRange(start, end)] encoding:NSUTF8StringEncoding];
|
||||
if (substr) {
|
||||
NSUInteger to = [substr rangeOfString:@"\""].location;
|
||||
if (to != NSNotFound) {
|
||||
found = [ytBase stringByAppendingFormat:@"?channel_id=%@", [substr substringToIndex:to]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#if DEBUG && ENV_LOG_YOUTUBE
|
||||
@@ -80,5 +76,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
|
||||
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
@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);
|
||||
@@ -36,7 +16,7 @@ typedef void(^FaviconDownloadBlock)(NSImage * _Nullable img, NSURL * _Nullable p
|
||||
- (instancetype)startWithBlock:(nonnull FaviconDownloadBlock)block;
|
||||
- (void)cancel;
|
||||
// Extract from HTML metadata
|
||||
+ (nullable NSString*)urlForMetadata:(RSHTMLMetadata*)meta;
|
||||
+ (nullable NSString*)urlForMetadata:(nullable RSHTMLMetadata*)meta;
|
||||
@end
|
||||
|
||||
|
||||
@@ -45,3 +25,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
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 RSXML2;
|
||||
#import "FaviconDownload.h"
|
||||
#import "Feed+Ext.h"
|
||||
@@ -147,7 +125,13 @@
|
||||
return;
|
||||
self.currentDownload = [[NSURLRequest requestWithURL:self.remoteURL] downloadTask:^(NSURL * _Nullable path, NSError * _Nullable error) {
|
||||
if (error) path = nil; // will also nullify img
|
||||
NSImage *img = path ? [[NSImage alloc] initByReferencingURL:path] : nil;
|
||||
NSImage *img;
|
||||
if (path) {
|
||||
NSData* data = [[NSData alloc] initWithContentsOfURL:path];
|
||||
img = [[NSImage alloc] initWithData:data];
|
||||
} else {
|
||||
img = nil;
|
||||
}
|
||||
if (img.valid) {
|
||||
// move image to temporary destination, otherwise dataTask: will delete it.
|
||||
NSString *tmpFile = NSProcessInfo.processInfo.globallyUniqueString;
|
||||
@@ -166,7 +150,8 @@
|
||||
if (self.canceled)
|
||||
return;
|
||||
NSURL *path = self.fileURL;
|
||||
NSImage *img = [[NSImage alloc] initByReferencingURL:path];
|
||||
NSData* data = [[NSData alloc] initWithContentsOfURL:path];
|
||||
NSImage* img = [[NSImage alloc] initWithData:data];
|
||||
if (!img.valid) { path = nil; img = nil; }
|
||||
#if DEBUG && ENV_LOG_DOWNLOAD
|
||||
printf("ICON %1.0fx%1.0f %s\n", img.size.width, img.size.height, self.remoteURL.absoluteString.UTF8String);
|
||||
@@ -183,7 +168,7 @@
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
/// Extract favicon URL from parsed HTML metadata.
|
||||
+ (nullable NSString*)urlForMetadata:(RSHTMLMetadata*)meta {
|
||||
+ (nullable NSString*)urlForMetadata:(nullable RSHTMLMetadata*)meta {
|
||||
if (!meta) return nil;
|
||||
|
||||
double bestScore = DBL_MAX;
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
@class RSParsedFeed, RSHTMLMetadataFeedLink, Feed, FaviconDownload;
|
||||
@class RSParsedFeed, RSHTMLMetadataFeedLink, Feed, FaviconDownload, RegexConverter;
|
||||
@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.
|
||||
@@ -34,6 +14,7 @@
|
||||
@property (readonly, nullable) RSParsedFeed *xmlfeed;
|
||||
@property (readonly, nullable) NSError *error;
|
||||
@property (readonly, nullable) NSString *faviconURL;
|
||||
@property (readonly, nullable) NSData *rawData;
|
||||
|
||||
typedef void (^FeedDownloadBlock)(FeedDownload *sender);
|
||||
|
||||
@@ -41,6 +22,7 @@ typedef void (^FeedDownloadBlock)(FeedDownload *sender);
|
||||
+ (instancetype)withURL:(NSString*)url;
|
||||
+ (instancetype)withFeed:(Feed*)feed forced:(BOOL)flag;
|
||||
// Actions
|
||||
- (instancetype)withRegex:(nullable RegexConverter *)converter enforce:(BOOL)flag;
|
||||
- (instancetype)startWithDelegate:(id<FeedDownloadDelegate>)delegate;
|
||||
- (instancetype)startWithBlock:(nonnull FeedDownloadBlock)block;
|
||||
- (void)cancel;
|
||||
@@ -55,9 +37,11 @@ typedef void (^FeedDownloadBlock)(FeedDownload *sender);
|
||||
@protocol FeedDownloadDelegate <NSObject>
|
||||
@optional
|
||||
/// Delegate must return chosen URL. If not implemented, the first URL will be used.
|
||||
- (NSString*)feedDownload:(FeedDownload*)sender selectFeedFromList:(NSArray<RSHTMLMetadataFeedLink*>*)list;
|
||||
- (nullable NSString*)feedDownload:(FeedDownload*)sender selectFeedFromList:(NSArray<RSHTMLMetadataFeedLink*>*)list;
|
||||
/// Only called if an URL redirect occured.
|
||||
- (void)feedDownload:(FeedDownload*)sender urlRedirected:(NSString*)newURL;
|
||||
/// 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
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 RSXML2;
|
||||
#import "FeedDownload.h"
|
||||
#import "FaviconDownload.h"
|
||||
@@ -28,6 +6,9 @@
|
||||
#import "FeedMeta+Ext.h"
|
||||
#import "NSError+Ext.h"
|
||||
#import "NSURLRequest+Ext.h"
|
||||
#import "RegexFeed.h"
|
||||
#import "RegexConverter+Ext.h"
|
||||
|
||||
|
||||
@interface FeedDownload()
|
||||
@property (nonatomic, assign) BOOL respondToSelectFeed, respondToRedirect, respondToEnd;
|
||||
@@ -42,6 +23,9 @@
|
||||
@property (nonatomic, strong) RSParsedFeed *xmlfeed;
|
||||
@property (nonatomic, strong) NSError *error;
|
||||
@property (nonatomic, strong) NSString *faviconURL;
|
||||
@property (nonatomic, strong) NSData *rawData;
|
||||
@property (nonatomic, strong) RegexConverter *regexConverter;
|
||||
@property (nonatomic, assign) BOOL regexEnforce;
|
||||
@end
|
||||
|
||||
@implementation FeedDownload
|
||||
@@ -73,13 +57,20 @@
|
||||
FeedDownload *this = [FeedDownload new];
|
||||
this.assertIsFeedURL = YES;
|
||||
this.request = req;
|
||||
return this;
|
||||
return [this withRegex:feed.regex enforce:false];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// | MARK: - Getter & Setter
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
/// Set @c .regexConverter for html-processed feeds.
|
||||
- (instancetype)withRegex:(RegexConverter *)converter enforce:(BOOL)flag {
|
||||
self.regexConverter = converter;
|
||||
self.regexEnforce = flag;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Set delegate and check what methods are implemented.
|
||||
- (void)setDelegate:(id<FeedDownloadDelegate>)observer {
|
||||
_delegate = observer;
|
||||
@@ -156,10 +147,16 @@
|
||||
self.currentDownload = [request dataTask:^(NSData * _Nullable data, NSError * _Nullable error, NSHTTPURLResponse *response) {
|
||||
self.error = error;
|
||||
self.response = response;
|
||||
self.rawData = data;
|
||||
if (!data) { // data = nil if (error || 304)
|
||||
[self performSelectorOnMainThread:@selector(finishAndNotify) withObject:nil waitUntilDone:NO];
|
||||
return;
|
||||
}
|
||||
// if regex is used, no further processing
|
||||
if (self.regexConverter || self.regexEnforce) {
|
||||
[self processWithRegexConverter:self.regexConverter data:data];
|
||||
return;
|
||||
}
|
||||
RSXMLData *xml = [[RSXMLData alloc] initWithData:data url:response.URL];
|
||||
if (!self.assertIsFeedURL && [xml.parserClass isHTMLParser])
|
||||
[self processXMLDataHTML:xml]; // HTML source handling
|
||||
@@ -168,6 +165,30 @@
|
||||
}];
|
||||
}
|
||||
|
||||
/// The downloaded source is HTML data and will be parsed with @c RegexConverter
|
||||
- (void)processWithRegexConverter:(RegexConverter *)converter data:(NSData *)rawData {
|
||||
NSError *err = nil;
|
||||
if (converter) {
|
||||
NSString *theData = [[NSString alloc] initWithData:rawData encoding:NSUTF8StringEncoding];
|
||||
NSArray<RegexFeedEntry*> *matches = [[RegexFeed from:converter] process:theData error:&err];
|
||||
|
||||
RSParsedFeed *feed = [[RSParsedFeed alloc] initWithURL:self.request.URL];
|
||||
feed.link = self.request.URL.absoluteString; // needed for group-menu-item-open
|
||||
for (RegexFeedEntry *rxEntry in matches) {
|
||||
RSParsedArticle *article = [feed appendNewArticle];
|
||||
article.link = rxEntry.href;
|
||||
article.title = rxEntry.title;
|
||||
article.body = rxEntry.desc;
|
||||
article.datePublished = rxEntry.date;
|
||||
}
|
||||
self.xmlfeed = feed;
|
||||
} else {
|
||||
self.xmlfeed = nil;
|
||||
}
|
||||
self.error = err;
|
||||
[self performSelectorOnMainThread:@selector(finishAndNotify) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
|
||||
/// The downloaded source seems to be HTML data, lets parse it with @c RSXML @c RSHTMLMetadataParser
|
||||
- (void)processXMLDataHTML:(RSXMLData*)xml {
|
||||
RSHTMLMetadataParser *parser = [RSHTMLMetadataParser parserWithXMLData:xml];
|
||||
@@ -178,7 +199,7 @@
|
||||
}
|
||||
else if (!meta || meta.feedLinks.count == 0) {
|
||||
if ([xml.url.host hasSuffix:@"youtube.com"])
|
||||
feedURL = [YouTubePlugin feedURL:xml.url];
|
||||
feedURL = [YouTubePlugin feedURL:xml.url data:xml.data];
|
||||
if (feedURL.length == 0)
|
||||
self.error = [NSError feedURLNotFound:xml.url];
|
||||
}
|
||||
@@ -190,7 +211,7 @@
|
||||
self.error = [NSError canceledByUser];
|
||||
}
|
||||
// finalize HTML parsing
|
||||
if (self.error) {
|
||||
if (self.error || !feedURL) {
|
||||
[self finishAndNotify];
|
||||
} else {
|
||||
self.assertIsFeedURL = YES;
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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;
|
||||
@class FeedGroup;
|
||||
|
||||
@@ -28,6 +6,8 @@ typedef NS_OPTIONS(NSUInteger, OpmlFileExportOptions) {
|
||||
OpmlFileExportOptionFullBackup = 1 << 2,
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Protocols
|
||||
|
||||
@protocol OpmlFileImportDelegate <NSObject>
|
||||
@@ -61,3 +41,5 @@ typedef NS_OPTIONS(NSUInteger, OpmlFileExportOptions) {
|
||||
- (void)showExportDialog:(NSWindow*)window;
|
||||
- (nullable NSError*)writeOPMLFile:(NSURL*)url withOptions:(OpmlFileExportOptions)opt;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,29 +1,8 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 RSXML2;
|
||||
#import "OpmlFile.h"
|
||||
#import "FeedMeta+Ext.h"
|
||||
#import "FeedGroup+Ext.h"
|
||||
#import "RegexConverter+Ext.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "Constants.h"
|
||||
#import "NSDate+Ext.h"
|
||||
@@ -142,6 +121,24 @@ static NSInteger RadioGroupSelection(NSView *view) {
|
||||
|
||||
newFeed.feed.meta.url = [item attributeForKey:OPMLXMLURLKey];
|
||||
newFeed.feed.meta.refresh = interval;
|
||||
|
||||
// baRSS specific
|
||||
NSString *rxEntry = [item attributeForKey:@"rxEntry"];
|
||||
NSString *rxHref = [item attributeForKey:@"rxHref"];
|
||||
NSString *rxTitle = [item attributeForKey:@"rxTitle"];
|
||||
NSString *rxDesc = [item attributeForKey:@"rxDesc"];
|
||||
NSString *rxDate = [item attributeForKey:@"rxDate"];
|
||||
NSString *rxDateFormat = [item attributeForKey:@"rxDateFormat"];
|
||||
if (rxEntry || rxHref || rxTitle || rxDesc || rxDate || rxDateFormat) {
|
||||
RegexConverter *rx = [RegexConverter newInContext:moc];
|
||||
rx.entry = rxEntry;
|
||||
rx.href = rxHref;
|
||||
rx.title = rxTitle;
|
||||
rx.desc = rxDesc;
|
||||
rx.date = rxDate;
|
||||
rx.dateFormat = rxDateFormat;
|
||||
newFeed.feed.regex = rx;
|
||||
}
|
||||
} else { // GROUP
|
||||
for (NSUInteger i = 0; i < item.children.count; i++) {
|
||||
[self importFeed:item.children[i] parent:newFeed index:(int32_t)i inContext:moc];
|
||||
@@ -301,6 +298,21 @@ static NSInteger RadioGroupSelection(NSView *view) {
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:OPMLTypeKey stringValue:@"rss"]];
|
||||
NSString *intervalStr = [NSString stringWithFormat:@"%d", item.feed.meta.refresh];
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:@"refreshInterval" stringValue:intervalStr]]; // baRSS specific
|
||||
RegexConverter *rx = item.feed.regex;
|
||||
if (rx) { // baRSS specific
|
||||
if (rx.entry)
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:@"rxEntry" stringValue:rx.entry]];
|
||||
if (rx.href)
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:@"rxHref" stringValue:rx.href]];
|
||||
if (rx.title)
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:@"rxTitle" stringValue:rx.title]];
|
||||
if (rx.desc)
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:@"rxDesc" stringValue:rx.desc]];
|
||||
if (rx.date)
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:@"rxDate" stringValue:rx.date]];
|
||||
if (rx.dateFormat)
|
||||
[outline addAttribute:[NSXMLNode attributeWithName:@"rxDateFormat" stringValue:rx.dateFormat]];
|
||||
}
|
||||
// TODO: option to export unread state?
|
||||
}
|
||||
parent = outline;
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
@class Feed;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UpdateScheduler : NSObject
|
||||
@property (class, readonly) NSUInteger feedsInQueue;
|
||||
@property (class, readonly) NSDate *dateScheduled;
|
||||
@@ -37,7 +17,7 @@
|
||||
// Scheduling
|
||||
+ (void)scheduleNextFeed;
|
||||
+ (void)forceUpdateAllFeeds;
|
||||
+ (void)downloadList:(NSArray<Feed*>*)list userInitiated:(BOOL)flag finally:(nullable os_block_t)block;
|
||||
+ (void)downloadList:(NSArray<Feed*>*)list userInitiated:(BOOL)flag notifications:(BOOL)notify finally:(nullable os_block_t)block;
|
||||
+ (void)updateAllFavicons;
|
||||
// Auto Download & Parse Feed URL
|
||||
+ (void)autoDownloadAndParseURL:(NSString*)url;
|
||||
@@ -46,3 +26,5 @@
|
||||
+ (void)registerNetworkChangeNotification;
|
||||
+ (void)unregisterNetworkChangeNotification;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,34 +1,14 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 SystemConfiguration;
|
||||
#import "UpdateScheduler.h"
|
||||
#import "Constants.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "NotifyEndpoint.h"
|
||||
#import "NSDate+Ext.h"
|
||||
|
||||
#import "FeedDownload.h"
|
||||
#import "FaviconDownload.h"
|
||||
#import "Feed+Ext.h"
|
||||
#import "FeedArticle+Ext.h"
|
||||
#import "FeedMeta+Ext.h"
|
||||
#import "FeedGroup+Ext.h"
|
||||
|
||||
@@ -151,7 +131,7 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
||||
NSArray<Feed*> *list = [StoreCoordinator listOfFeedsThatNeedUpdate:updateAll inContext:moc];
|
||||
//NSAssert(list.count > 0, @"ERROR: Something went wrong, timer fired too early.");
|
||||
|
||||
[self downloadList:list userInitiated:updateAll finally:^{
|
||||
[self downloadList:list userInitiated:updateAll notifications:YES finally:^{
|
||||
[StoreCoordinator saveContext:moc andParent:YES]; // save parents too ...
|
||||
[moc reset];
|
||||
[self scheduleNextFeed]; // always reset the timer
|
||||
@@ -169,7 +149,7 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
||||
}
|
||||
|
||||
/// Download list of feeds. Either silently in background or with alerts in foreground.
|
||||
+ (void)downloadList:(NSArray<Feed*>*)list userInitiated:(BOOL)flag finally:(nullable os_block_t)block {
|
||||
+ (void)downloadList:(NSArray<Feed*>*)list userInitiated:(BOOL)flag notifications:(BOOL)notify finally:(nullable os_block_t)block {
|
||||
if (![self allowNetworkConnection]) {
|
||||
if (block) block();
|
||||
return;
|
||||
@@ -180,7 +160,7 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
for (Feed *f in list) {
|
||||
dispatch_group_enter(group);
|
||||
[self updateFeed:f alert:flag isForced:flag finally:^{
|
||||
[self updateFeed:f alert:flag isForced:flag notifications:notify finally:^{
|
||||
atomic_fetch_sub_explicit(&_queueSize, 1, memory_order_relaxed);
|
||||
PostNotification(kNotificationBackgroundUpdateInProgress, @(_queueSize));
|
||||
dispatch_group_leave(group);
|
||||
@@ -192,7 +172,7 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
||||
/// Helper method to show modal error alert
|
||||
static inline void AlertDownloadError(NSError *err, NSString *url) {
|
||||
NSAlert *alertPopup = [NSAlert alertWithError:err];
|
||||
alertPopup.informativeText = [NSString stringWithFormat:@"Error loading source: %@", url];
|
||||
alertPopup.informativeText = [NSString stringWithFormat:NSLocalizedString(@"Error loading source: %@", nil), url];
|
||||
[alertPopup runModal];
|
||||
}
|
||||
|
||||
@@ -200,7 +180,7 @@ static inline void AlertDownloadError(NSError *err, NSString *url) {
|
||||
Start download request with existing @c Feed object. Reuses etag and modified headers (unless articles count is 0).
|
||||
@note Will post a @c kNotificationArticlesUpdated notification if download was successful and status code is @b not 304.
|
||||
*/
|
||||
+ (void)updateFeed:(Feed*)feed alert:(BOOL)alert isForced:(BOOL)forced finally:(nullable os_block_t)block {
|
||||
+ (void)updateFeed:(Feed*)feed alert:(BOOL)alert isForced:(BOOL)forced notifications:(BOOL)notify finally:(nullable os_block_t)block {
|
||||
NSManagedObjectContext *moc = feed.managedObjectContext;
|
||||
NSManagedObjectID *oid = feed.objectID;
|
||||
[[FeedDownload withFeed:feed forced:forced] startWithBlock:^(FeedDownload *mem) {
|
||||
@@ -210,7 +190,37 @@ static inline void AlertDownloadError(NSError *err, NSString *url) {
|
||||
BOOL recentlyAdded = (f.articles.count == 0); // before copy values
|
||||
BOOL downloadIcon = (!f.hasIcon && (recentlyAdded || forced));
|
||||
BOOL needsNotification = [mem copyValuesTo:f ignoreError:NO];
|
||||
|
||||
// need to gather object before save, because afterwards list will be empty
|
||||
NSArray *inserted = notify ? moc.insertedObjects.allObjects : nil;
|
||||
NSArray *deleted = moc.deletedObjects.allObjects;
|
||||
|
||||
[StoreCoordinator saveContext:moc andParent:YES];
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
[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;
|
||||
}
|
||||
}
|
||||
if (didAddAny)
|
||||
[NotifyEndpoint postFeed:f];
|
||||
}
|
||||
|
||||
if (needsNotification)
|
||||
PostNotification(kNotificationArticlesUpdated, oid);
|
||||
if (downloadIcon && !mem.error) {
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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;
|
||||
|
||||
/// Draw separator line in @c NSOutlineView
|
||||
|
||||
@@ -1,28 +1,7 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "DrawImage.h"
|
||||
#import "Constants.h"
|
||||
#import "UserPrefs.h"
|
||||
#import "NSColor+Ext.h"
|
||||
#import "TinySVG.h"
|
||||
|
||||
|
||||
@implementation DrawSeparator
|
||||
@@ -147,7 +126,6 @@ static inline void AddGroupIconPath(CGContextRef c, CGFloat size, BOOL showBackg
|
||||
CGPathRelease(lower);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Create @c CGPath for RSS icon; a circle in the lower left bottom and two radio waves going outwards.
|
||||
@param connection If @c NO, draw only one radio wave and a pause icon in the upper right
|
||||
@@ -168,22 +146,9 @@ static inline void AddRSSIconPath(CGContextRef c, CGFloat size, BOOL connection)
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Icon Background Generators
|
||||
#pragma mark - Icon Background
|
||||
|
||||
|
||||
/// Create @c CGPath with rounded corners (optional). @param roundness Value between @c 0.0 and @c 1.0
|
||||
static void AddRoundedBackgroundPath(CGContextRef c, CGRect r, CGFloat roundness) {
|
||||
const CGFloat corner = ShorterSide(r.size) * (roundness / 2.0);
|
||||
if (corner > 0) {
|
||||
CGMutablePathRef pth = CGPathCreateMutable();
|
||||
CGPathAddRoundedRect(pth, NULL, r, corner, corner);
|
||||
CGContextAddPath(c, pth);
|
||||
CGPathRelease(pth);
|
||||
} else {
|
||||
CGContextAddRect(c, r);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert and draw linear gradient with @c color saturation @c ±0.3
|
||||
static void DrawGradient(CGContextRef c, CGFloat size, NSColor *color) {
|
||||
CGFloat h = 0, s = 1, b = 1, a = 1;
|
||||
@@ -212,6 +177,12 @@ static void DrawGradient(CGContextRef c, CGFloat size, NSColor *color) {
|
||||
#pragma mark - CGContext Drawing & Manipulation
|
||||
|
||||
|
||||
/// Flip coordinate system
|
||||
static void FlipCoordinateSystem(CGContextRef c, CGFloat height) {
|
||||
CGContextTranslateCTM(c, 0, height);
|
||||
CGContextScaleCTM(c, 1, -1);
|
||||
}
|
||||
|
||||
/// Scale and translate context to the center with respect to the new scale. If @c width @c != @c length align top left.
|
||||
static void SetContentScale(CGContextRef c, CGSize size, CGFloat scale) {
|
||||
const CGFloat s = ShorterSide(size);
|
||||
@@ -226,7 +197,7 @@ static void DrawRoundedFrame(CGContextRef c, CGRect r, CGColorRef color, BOOL ba
|
||||
CGContextSetStrokeColorWithColor(c, color);
|
||||
CGFloat contentScale = defaultScale;
|
||||
if (background) {
|
||||
AddRoundedBackgroundPath(c, r, corner);
|
||||
svgAddRect(c, 1, r, ShorterSide(r.size) * corner/2);
|
||||
if (scaling != 0.0)
|
||||
contentScale *= scaling;
|
||||
}
|
||||
@@ -299,6 +270,31 @@ static void DrawUnreadIcon(CGRect r, NSColor *color) {
|
||||
CGPathRelease(path);
|
||||
}
|
||||
|
||||
/// Draw "(.*)" as vector path
|
||||
static void DrawRegexIcon(CGRect r) {
|
||||
const CGFloat size = ShorterSide(r.size);
|
||||
CGContextRef c = NSGraphicsContext.currentContext.CGContext;
|
||||
|
||||
svgAddRect(c, 1, r, .2 * size);
|
||||
CGContextSetFillColorWithColor(c, NSColor.redColor.CGColor);
|
||||
CGContextFillPath(c);
|
||||
|
||||
// SVG files use bottom-left corner coordinate system. Quartz uses top-left.
|
||||
FlipCoordinateSystem(c, r.size.height);
|
||||
SetContentScale(c, r.size, 0.8);
|
||||
// "("
|
||||
svgAddPath(c, size/1000, "m184 187c-140 205-134 432-1 622l-66 44c-159-221-151-499 0-708z");
|
||||
// "."
|
||||
svgAddCircle(c, size/1000, 315, 675, 70, NO);
|
||||
// "*"
|
||||
svgAddPath(c, size/1000, "m652 277 107-35 21 63-109 36 68 92-54 39-68-93-66 91-52-41 67-88-109-37 21-63 108 37v-113h66v112z");
|
||||
// ")"
|
||||
svgAddPath(c, size/1000, "m816 813c140-205 134-430 1-621l66-45c159 221 151 499 0 708z");
|
||||
|
||||
CGContextSetFillColorWithColor(c, NSColor.whiteColor.CGColor);
|
||||
CGContextFillPath(c);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSImage Name Registration
|
||||
|
||||
@@ -312,18 +308,12 @@ 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; });
|
||||
Register(32, RSSImageRegexIcon, NSLocalizedString(@"Regex icon", nil), ^(NSRect r) { DrawRegexIcon(r); return YES; });
|
||||
}
|
||||
|
||||
5
baRSS/Helper/TinySVG.h
Normal file
5
baRSS/Helper/TinySVG.h
Normal file
@@ -0,0 +1,5 @@
|
||||
@import Cocoa;
|
||||
|
||||
void svgAddPath(CGContextRef context, CGFloat scale, const char * path);
|
||||
void svgAddCircle(CGContextRef context, CGFloat scale, CGFloat x, CGFloat y, CGFloat radius, bool clockwise);
|
||||
void svgAddRect(CGContextRef context, CGFloat scale, CGRect rect, CGFloat cornerRadius);
|
||||
171
baRSS/Helper/TinySVG.m
Normal file
171
baRSS/Helper/TinySVG.m
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "TinySVG.h"
|
||||
|
||||
|
||||
struct SVGState {
|
||||
CGFloat scale; // technically not part of parser but easier to pass along
|
||||
|
||||
char op;
|
||||
float x, y;
|
||||
bool prevDot;
|
||||
|
||||
float num[6];
|
||||
uint8 iNum;
|
||||
|
||||
char buf[15];
|
||||
uint8 iBuf;
|
||||
};
|
||||
|
||||
|
||||
# pragma mark - Helper
|
||||
|
||||
/// if number buffer contains anything, write it to num array and start new buffer
|
||||
static void finishNum(struct SVGState *state) {
|
||||
if (state->iBuf > 0) {
|
||||
state->buf[state->iBuf] = '\0';
|
||||
state->num[state->iNum++] = (float)atof(state->buf);
|
||||
state->iBuf = 0;
|
||||
state->prevDot = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// All numbers stored in num array, finalize SVG path operation and add path to @c CGContext
|
||||
static void finishOp(CGMutablePathRef path, struct SVGState *state) {
|
||||
char op = state->op;
|
||||
if (op >= 'a' && op <= 'z') {
|
||||
// convert relative to absolute coordinates
|
||||
for (uint8 t = 0; t < state->iNum; t++) {
|
||||
state->num[t] += (t % 2 || op == 'v') ? state->y : state->x;
|
||||
}
|
||||
// convert to upper-case
|
||||
op = op - 'a' + 'A';
|
||||
}
|
||||
|
||||
if (op == 'Z') {
|
||||
CGPathCloseSubpath(path);
|
||||
|
||||
} else if (op == 'V' && state->iNum == 1) {
|
||||
state->y = state->num[0];
|
||||
CGPathAddLineToPoint(path, NULL, state->x * state->scale, state->y * state->scale);
|
||||
|
||||
} else if (op == 'H' && state->iNum == 1) {
|
||||
state->x = state->num[0];
|
||||
CGPathAddLineToPoint(path, NULL, state->x * state->scale, state->y * state->scale);
|
||||
|
||||
} else if (op == 'M' && state->iNum == 2) {
|
||||
state->x = state->num[0];
|
||||
state->y = state->num[1];
|
||||
CGPathMoveToPoint(path, NULL, state->x * state->scale, state->y * state->scale);
|
||||
// Edge-case: "M 1 2 3 4 5 6" is valid SVG after move 1,2 all remaining points are lines (3,4 and 5,6)
|
||||
// For this case we overwrite op here. It will be overwritten again if a new op starts. Else, assume line-op.
|
||||
state->op = (state->op == 'm') ? 'l' : 'L';
|
||||
|
||||
} else if (op == 'L' && state->iNum == 2) {
|
||||
state->x = state->num[0];
|
||||
state->y = state->num[1];
|
||||
CGPathAddLineToPoint(path, NULL, state->x * state->scale, state->y * state->scale);
|
||||
|
||||
} else if (op == 'C' && state->iNum == 6) {
|
||||
state->x = state->num[4];
|
||||
state->y = state->num[5];
|
||||
CGPathAddCurveToPoint(path, NULL, state->num[0] * state->scale, state->num[1] * state->scale, state->num[2] * state->scale, state->num[3] * state->scale, state->x * state->scale, state->y * state->scale);
|
||||
} else {
|
||||
NSLog(@"Unsupported SVG operation %c %d", state->op, state->iNum);
|
||||
}
|
||||
state->iNum = 0;
|
||||
}
|
||||
|
||||
/// current number not finished yet. Append another char to internal buffer
|
||||
inline static void continueNum(char chr, struct SVGState *state) {
|
||||
state->buf[state->iBuf++] = chr;
|
||||
}
|
||||
|
||||
|
||||
# pragma mark - Parser
|
||||
|
||||
/// very basic svg path parser.
|
||||
static void tinySVG_parse(const char * code, CGFloat scale, CGMutablePathRef path) {
|
||||
struct SVGState state = {
|
||||
.scale = scale,
|
||||
.op = '_',
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.prevDot = false,
|
||||
|
||||
//.num = {0, 0, 0, 0, 0, 0},
|
||||
.iNum = 0,
|
||||
//.buf = " ",
|
||||
.iBuf = 0,
|
||||
};
|
||||
|
||||
unsigned long len = strlen(code);
|
||||
for (unsigned long i = 0; i < len; i++) {
|
||||
char chr = code[i];
|
||||
if ((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z')) {
|
||||
if (state.op != '_') {
|
||||
finishNum(&state);
|
||||
finishOp(path, &state);
|
||||
}
|
||||
state.op = chr;
|
||||
} else if (chr >= '0' && chr <= '9') {
|
||||
continueNum(chr, &state);
|
||||
} else if (chr == '-' && state.iBuf == 0) {
|
||||
continueNum(chr, &state);
|
||||
} else if (chr == '.' && !state.prevDot) {
|
||||
continueNum(chr, &state);
|
||||
state.prevDot = true;
|
||||
} else { // any number-separating character
|
||||
finishNum(&state);
|
||||
|
||||
// Edge-Case: SVG can reuse the previous operation without declaration
|
||||
// e.g. you can draw four lines with "L1 2 3 4 5 6 7 8"
|
||||
// or two curves with "c1 2 3 4 5 6 -1 -2 -3 -4 -5 -6"
|
||||
// Therefore we need to complete the operation if the number of arguments is reached
|
||||
if (state.iNum == 1 && strchr("HhVv", state.op) != NULL) {
|
||||
finishOp(path, &state);
|
||||
} else if (state.iNum == 2 && strchr("MmLl", state.op) != NULL) {
|
||||
finishOp(path, &state);
|
||||
} else if (state.iNum == 6 && strchr("Cc", state.op) != NULL) {
|
||||
finishOp(path, &state);
|
||||
}
|
||||
|
||||
if (chr == '-') {
|
||||
continueNum(chr, &state);
|
||||
} else if (chr == '.') {
|
||||
continueNum(chr, &state);
|
||||
state.prevDot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# pragma mark - External API
|
||||
|
||||
/// calls @c tinySVG_path and handles @c CGPath creation and release.
|
||||
void svgAddPath(CGContextRef context, CGFloat scale, const char * code) {
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
tinySVG_parse(code, scale, path);
|
||||
CGContextAddPath(context, path);
|
||||
CGPathRelease(path);
|
||||
}
|
||||
|
||||
/// calls @c CGPathAddArc with full circle
|
||||
void svgAddCircle(CGContextRef context, CGFloat scale, CGFloat x, CGFloat y, CGFloat radius, bool clockwise) {
|
||||
CGMutablePathRef tmp = CGPathCreateMutable();
|
||||
CGPathAddArc(tmp, NULL, x * scale, y * scale, radius * scale, 0, M_PI * 2, clockwise);
|
||||
CGContextAddPath(context, tmp);
|
||||
CGPathRelease(tmp);
|
||||
}
|
||||
|
||||
/// Calls @c CGContextAddRect or @c CGPathAddRoundedRect (optional).
|
||||
/// @param cornerRadius Use @c <=0 for no corners. Use half of @c min(w,h) for a full circle.
|
||||
void svgAddRect(CGContextRef context, CGFloat scale, CGRect rect, CGFloat cornerRadius) {
|
||||
if (cornerRadius > 0) {
|
||||
CGMutablePathRef tmp = CGPathCreateMutable();
|
||||
CGPathAddRoundedRect(tmp, NULL, rect, cornerRadius * scale, cornerRadius * scale);
|
||||
CGContextAddPath(context, tmp);
|
||||
CGPathRelease(tmp);
|
||||
} else {
|
||||
CGContextAddRect(context, rect);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
@interface URLScheme : NSObject
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "URLScheme.h"
|
||||
#import "AppHook.h" // barss:open/preferences
|
||||
#import "Preferences.h" // barss:open/preferences
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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.
|
||||
|
||||
#ifndef UserPrefs_h
|
||||
#define UserPrefs_h
|
||||
|
||||
@@ -35,9 +13,11 @@
|
||||
/** default: @c nil */ static NSString* const Pref_modalSheetWidth = @"modalSheetWidth";
|
||||
// ------ General settings ------ (Preferences > General Tab) ------
|
||||
/** default: @c nil */ static NSString* const Pref_defaultHttpApplication = @"defaultHttpApplication";
|
||||
/** default: @c nil */ static NSString* const Pref_notificationType = @"notificationType";
|
||||
// ------ Appearance matrix ------ (Preferences > Appearance Tab) ------
|
||||
/** default: @c YES */ static NSString* const Pref_globalTintMenuIcon = @"globalTintMenuBarIcon";
|
||||
/** 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_globalMarkRead = @"globalMarkRead";
|
||||
/** default: @c YES */ static NSString* const Pref_globalMarkUnread = @"globalMarkUnread";
|
||||
@@ -45,11 +25,13 @@
|
||||
/** default: @c YES */ static NSString* const Pref_groupOpenUnread = @"groupOpenUnread";
|
||||
/** default: @c YES */ static NSString* const Pref_groupMarkRead = @"groupMarkRead";
|
||||
/** default: @c YES */ static NSString* const Pref_groupMarkUnread = @"groupMarkUnread";
|
||||
/** default: @c NO */ static NSString* const Pref_groupUnreadOnly = @"groupUnreadOnly";
|
||||
/** default: @c YES */ static NSString* const Pref_groupUnreadCount = @"groupUnreadCount";
|
||||
/** default: @c NO */ static NSString* const Pref_groupUnreadIndicator = @"groupUnreadIndicator";
|
||||
/** default: @c YES */ static NSString* const Pref_feedOpenUnread = @"feedOpenUnread";
|
||||
/** default: @c YES */ static NSString* const Pref_feedMarkRead = @"feedMarkRead";
|
||||
/** default: @c YES */ static NSString* const Pref_feedMarkUnread = @"feedMarkUnread";
|
||||
/** default: @c NO */ static NSString* const Pref_feedUnreadOnly = @"feedUnreadOnly";
|
||||
/** default: @c YES */ static NSString* const Pref_feedUnreadCount = @"feedUnreadCount";
|
||||
/** default: @c YES */ static NSString* const Pref_feedUnreadIndicator = @"feedUnreadIndicator";
|
||||
/** default: @c NO */ static NSString* const Pref_feedTruncateTitle = @"feedTruncateTitle";
|
||||
@@ -68,6 +50,16 @@
|
||||
|
||||
void UserPrefsInit(void);
|
||||
NSColor* UserPrefsColor(NSString *key, NSColor *defaultColor); // Change with: defaults write de.relikd.baRSS {KEY} -string "#FBA33A"
|
||||
|
||||
typedef NS_ENUM(NSInteger, NotificationType) {
|
||||
NotificationTypeDisabled,
|
||||
NotificationTypePerArticle,
|
||||
NotificationTypePerFeed,
|
||||
NotificationTypeGlobal,
|
||||
};
|
||||
NotificationType UserPrefsNotificationType(void);
|
||||
NSString* NotificationTypeToString(NotificationType typ);
|
||||
|
||||
// ------ Getter ------
|
||||
/// Helper method calls @c (standardUserDefaults)boolForKey:
|
||||
static inline BOOL UserPrefsBool(NSString* const key) { return [[NSUserDefaults standardUserDefaults] boolForKey:key]; }
|
||||
@@ -90,7 +82,7 @@ static inline void UserPrefsSetBool(NSString* const key, BOOL value) { [[NSUserD
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
/// Helper method calls @c (mainBundle)CFBundleShortVersionString
|
||||
static inline NSString* UserPrefsAppVersion() { return [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; }
|
||||
static inline NSString* UserPrefsAppVersion(void) { return [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; }
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// | MARK: - Open URLs
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "UserPrefs.h"
|
||||
#import "NSString+Ext.h" // hexColor
|
||||
|
||||
@@ -32,16 +10,22 @@ static inline void defaultsAppend(NSMutableDictionary *defs, id value, NSArray<N
|
||||
/// Helper method calls @c (standardUserDefaults)registerDefaults:
|
||||
void UserPrefsInit(void) {
|
||||
NSMutableDictionary *defs = [NSMutableDictionary dictionary];
|
||||
defaultsAppend(defs, @YES, @[Pref_globalTintMenuIcon,
|
||||
Pref_globalUpdateAll,
|
||||
Pref_globalOpenUnread, Pref_groupOpenUnread, Pref_feedOpenUnread,
|
||||
Pref_globalMarkRead, Pref_groupMarkRead, Pref_feedMarkRead,
|
||||
Pref_globalMarkUnread, Pref_groupMarkUnread, Pref_feedMarkUnread,
|
||||
Pref_globalUnreadCount, Pref_groupUnreadCount, Pref_feedUnreadCount,
|
||||
Pref_feedUnreadIndicator]);
|
||||
defaultsAppend(defs, @NO, @[Pref_groupUnreadIndicator,
|
||||
Pref_feedTruncateTitle,
|
||||
Pref_feedLimitArticles]);
|
||||
defaultsAppend(defs, @YES, @[
|
||||
Pref_globalTintMenuIcon,
|
||||
Pref_globalUpdateAll,
|
||||
Pref_globalOpenUnread, Pref_groupOpenUnread, Pref_feedOpenUnread,
|
||||
Pref_globalMarkRead, Pref_groupMarkRead, Pref_feedMarkRead,
|
||||
Pref_globalMarkUnread, Pref_groupMarkUnread, Pref_feedMarkUnread,
|
||||
Pref_globalUnreadCount, Pref_groupUnreadCount, Pref_feedUnreadCount,
|
||||
Pref_feedUnreadIndicator
|
||||
]);
|
||||
defaultsAppend(defs, @NO, @[
|
||||
Pref_globalToggleHidden,
|
||||
Pref_groupUnreadOnly, Pref_feedUnreadOnly,
|
||||
Pref_groupUnreadIndicator,
|
||||
Pref_feedTruncateTitle,
|
||||
Pref_feedLimitArticles
|
||||
]);
|
||||
// Display limits & truncation ( defaults write de.relikd.baRSS {KEY} -int 10 )
|
||||
[defs setObject:[NSNumber numberWithUnsignedInteger:10] forKey:Pref_openFewLinksLimit];
|
||||
[defs setObject:[NSNumber numberWithUnsignedInteger:60] forKey:Pref_shortArticleNamesLimit];
|
||||
@@ -61,3 +45,22 @@ NSColor* UserPrefsColor(NSString *key, NSColor *defaultColor) {
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
/// Convert stored notification type string into enum
|
||||
NotificationType UserPrefsNotificationType(void) {
|
||||
NSString *typ = UserPrefsString(Pref_notificationType);
|
||||
if ([typ isEqualToString:@"article"]) return NotificationTypePerArticle;
|
||||
if ([typ isEqualToString:@"feed"]) return NotificationTypePerFeed;
|
||||
if ([typ isEqualToString:@"global"]) return NotificationTypeGlobal;
|
||||
return NotificationTypeDisabled;
|
||||
}
|
||||
|
||||
/// Convert enum type to storable string
|
||||
NSString* NotificationTypeToString(NotificationType typ) {
|
||||
switch (typ) {
|
||||
case NotificationTypeDisabled: return nil;
|
||||
case NotificationTypePerArticle: return @"article";
|
||||
case NotificationTypePerFeed: return @"feed";
|
||||
case NotificationTypeGlobal: return @"global";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -70,7 +70,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>14360</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.news</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
@@ -83,7 +83,7 @@
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2019 relikd. Public Domain.</string>
|
||||
<string>Copyright © 2025 relikd.</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>AppHook</string>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
|
||||
14
baRSS/NSCategories/NSColor+Ext.h
Normal file
14
baRSS/NSCategories/NSColor+Ext.h
Normal file
@@ -0,0 +1,14 @@
|
||||
@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
|
||||
41
baRSS/NSCategories/NSColor+Ext.m
Normal file
41
baRSS/NSCategories/NSColor+Ext.m
Normal file
@@ -0,0 +1,41 @@
|
||||
#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_colorUnreadIndicator, [NSColor controlAccentColor]);
|
||||
} else {
|
||||
color = UserPrefsColor(Pref_colorUnreadIndicator, [NSColor systemBlueColor]);
|
||||
}
|
||||
});
|
||||
return color;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
typedef int32_t Interval;
|
||||
@@ -32,6 +10,8 @@ typedef NS_ENUM(int32_t, TimeUnitType) {
|
||||
TimeUnitYears = 365 * 24 * 60 * 60
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSDate (Ext)
|
||||
+ (NSString*)timeStringISO8601;
|
||||
+ (NSString*)dayStringISO8601;
|
||||
@@ -55,5 +35,7 @@ typedef NS_ENUM(int32_t, TimeUnitType) {
|
||||
|
||||
|
||||
@interface NSDate (Statistics)
|
||||
+ (NSDictionary*)refreshIntervalStatistics:(NSArray<NSDate*> *)list;
|
||||
+ (nullable NSDictionary*)refreshIntervalStatistics:(NSArray<NSDate*> *)list;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 QuartzCore;
|
||||
#import "NSDate+Ext.h"
|
||||
|
||||
@@ -162,7 +140,7 @@ static TimeUnitType const _values[] = {
|
||||
/**
|
||||
@return @c nil if list contains less than 2 entries. Otherwise: @{min, max, avg, median, earliest, latest}
|
||||
*/
|
||||
+ (NSDictionary*)refreshIntervalStatistics:(NSArray<NSDate*> *)list {
|
||||
+ (nullable NSDictionary*)refreshIntervalStatistics:(NSArray<NSDate*> *)list {
|
||||
if (!list || list.count == 0)
|
||||
return nil;
|
||||
|
||||
|
||||
@@ -1,30 +1,10 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
/// 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 +15,5 @@
|
||||
- (BOOL)inCaseLog:(nullable const char*)title;
|
||||
- (BOOL)inCasePresent:(NSApplication*)app;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 RSXML2.RSXMLError;
|
||||
#import "NSError+Ext.h"
|
||||
|
||||
|
||||
@@ -1,27 +1,7 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 NSString (PlainHTML)
|
||||
+ (NSString*)plainTextFromHTMLData:(NSData*)data;
|
||||
- (nonnull NSString*)htmlToPlainText;
|
||||
@@ -30,3 +10,5 @@
|
||||
@interface NSString (HexColor)
|
||||
- (nullable NSColor*)hexColor;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "NSString+Ext.h"
|
||||
|
||||
@implementation NSString (PlainHTML)
|
||||
@@ -73,8 +51,8 @@
|
||||
if (last != '\n') {
|
||||
[result appendString:@"\n"];
|
||||
}
|
||||
if (order > 0) [result appendFormat:@" %d. ", order++];
|
||||
else [result appendString:@" • "];
|
||||
if (order > 0) [result appendFormat:@" %d. ", order++];
|
||||
else [result appendString:@" • "];
|
||||
}
|
||||
} else {
|
||||
// append text inbetween tags
|
||||
@@ -96,7 +74,10 @@
|
||||
// collapsing multiple horizontal whitespaces (\h) into one (the first one)
|
||||
[[NSRegularExpression regularExpressionWithPattern:@"(\\h)[\\h]+" options:0 error:nil]
|
||||
replaceMatchesInString:result options:0 range:NSMakeRange(0, result.length) withTemplate:@"$1"];
|
||||
return [result stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
|
||||
|
||||
NSMutableCharacterSet *cs = NSMutableCharacterSet.whitespaceAndNewlineCharacterSet;
|
||||
[cs removeCharactersInString:@" "]; // used for "li"
|
||||
return [result stringByTrimmingCharactersInSet:cs];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
#define ENV_LOG_FILES 0
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSURL (Ext)
|
||||
// Generators
|
||||
+ (NSURL*)applicationSupportURL;
|
||||
@@ -38,3 +18,5 @@
|
||||
- (void)remove;
|
||||
- (void)moveTo:(NSURL*)destination;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "NSURL+Ext.h"
|
||||
#import "NSError+Ext.h"
|
||||
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
#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
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "NSURLRequest+Ext.h"
|
||||
#import "NSString+Ext.h"
|
||||
#import "NSError+Ext.h"
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
/***/ static CGFloat const PAD_WIN = 20; // window padding
|
||||
@@ -40,6 +18,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,11 +45,11 @@ 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;
|
||||
+ (NSView*)radioGroup:(NSArray<NSString*>*)entries;
|
||||
+ (nullable NSView*)radioGroup:(NSArray<NSString*>*)entries target:(id)target action:(nonnull SEL)action;
|
||||
+ (nullable NSView*)radioGroup:(NSArray<NSString*>*)entries;
|
||||
// UI: Enclosing Container
|
||||
+ (NSPopover*)popover:(NSSize)size;
|
||||
- (NSScrollView*)wrapContent:(NSView*)content inScrollView:(NSRect)rect;
|
||||
@@ -91,7 +71,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 +86,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
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "NSView+Ext.h"
|
||||
|
||||
@implementation NSView (Ext)
|
||||
@@ -91,6 +69,11 @@
|
||||
btn.bezelStyle = NSBezelStyleRounded;
|
||||
btn.bordered = NO;
|
||||
btn.image = [NSImage imageNamed:name];
|
||||
NSSize s = btn.image.size;
|
||||
if (s.width > s.height)
|
||||
[btn.image setSize:NSMakeSize(size, size * (s.height / s.width))];
|
||||
else
|
||||
[btn.image setSize:NSMakeSize(size * (s.width / s.height), size)];
|
||||
return btn;
|
||||
}
|
||||
|
||||
@@ -122,7 +105,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,13 +125,13 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/// Create grouping view with vertically, left-aligned radio buttons. Action is identical for all buttons (grouping).
|
||||
+ (NSView*)radioGroup:(NSArray<NSString*>*)entries target:(id)target action:(nonnull SEL)action {
|
||||
+ (nullable NSView*)radioGroup:(NSArray<NSString*>*)entries target:(id)target action:(nonnull SEL)action {
|
||||
if (entries.count == 0)
|
||||
return nil;
|
||||
CGFloat w = 0, h = 0;
|
||||
@@ -167,7 +150,7 @@
|
||||
}
|
||||
|
||||
/// Same as @c radioGroup:target:action: but using dummy action to ignore radio button click events.
|
||||
+ (NSView*)radioGroup:(NSArray<NSString*>*)entries {
|
||||
+ (nullable NSView*)radioGroup:(NSArray<NSString*>*)entries {
|
||||
return [self radioGroup:entries target:self action:@selector(donothing)];
|
||||
}
|
||||
|
||||
@@ -328,7 +311,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;
|
||||
|
||||
18
baRSS/Notifications/NotifyEndpoint.h
Normal file
18
baRSS/Notifications/NotifyEndpoint.h
Normal file
@@ -0,0 +1,18 @@
|
||||
@import Cocoa;
|
||||
@import UserNotifications;
|
||||
|
||||
@class Feed, FeedArticle;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NotifyEndpoint : NSObject <UNUserNotificationCenterDelegate>
|
||||
+ (void)activate;
|
||||
|
||||
+ (void)setGlobalCount:(NSInteger)count previousCount:(NSInteger)count;
|
||||
+ (void)postFeed:(Feed*)feed;
|
||||
+ (void)postArticle:(FeedArticle*)article;
|
||||
|
||||
+ (void)dismiss:(nullable NSArray<NSString*>*)list;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
192
baRSS/Notifications/NotifyEndpoint.m
Normal file
192
baRSS/Notifications/NotifyEndpoint.m
Normal file
@@ -0,0 +1,192 @@
|
||||
#import "NotifyEndpoint.h"
|
||||
#import "UserPrefs.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "Feed+Ext.h"
|
||||
#import "FeedGroup+Ext.h"
|
||||
#import "FeedArticle+Ext.h"
|
||||
|
||||
/**
|
||||
Sent for global unread count notification alert (Notification Center)
|
||||
*/
|
||||
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
|
||||
|
||||
static NotifyEndpoint *singleton = nil;
|
||||
static NotificationType notifyType;
|
||||
|
||||
/// 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
|
||||
+ (void)activate {
|
||||
UNUserNotificationCenter *center = UNUserNotificationCenter.currentNotificationCenter;
|
||||
notifyType = UserPrefsNotificationType();
|
||||
|
||||
// even if disabled, register delegate. This allows to open previously sent notifications
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
singleton = [NotifyEndpoint new];
|
||||
center.delegate = singleton;
|
||||
});
|
||||
|
||||
if (notifyType == NotificationTypeDisabled) {
|
||||
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]];
|
||||
|
||||
[center requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = NSLocalizedString(@"Notifications Disabled", nil);
|
||||
alert.informativeText = NSLocalizedString(@"Either enable notifications in System Settings, or disable notifications in baRSS settings.", nil);
|
||||
alert.alertStyle = NSAlertStyleInformational;
|
||||
[alert runModal];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/// Set (or update) global "X unread articles"
|
||||
+ (void)setGlobalCount:(NSInteger)newCount previousCount:(NSInteger)oldCount {
|
||||
if (newCount > 0) {
|
||||
if (notifyType != NotificationTypeGlobal) {
|
||||
return;
|
||||
}
|
||||
// TODO: how to handle global count updates?
|
||||
// ignore and keep old count until 0?
|
||||
// or update count and show a new notification banner?
|
||||
if (newCount > oldCount) { // only notify if new feeds (quirk: will also trigger for option-click menu to mark unread)
|
||||
[self send:kNotifyIdGlobal
|
||||
title:APP_NAME
|
||||
body:[NSString stringWithFormat:NSLocalizedString(@"%ld unread articles", nil), newCount]];
|
||||
}
|
||||
} else {
|
||||
[self dismiss:@[kNotifyIdGlobal]];
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers feed notifications (if enabled)
|
||||
+ (void)postFeed:(Feed*)feed {
|
||||
if (notifyType != NotificationTypePerFeed) {
|
||||
return;
|
||||
}
|
||||
NSUInteger count = feed.countUnread;
|
||||
if (count > 0) {
|
||||
[feed.managedObjectContext obtainPermanentIDsForObjects:@[feed] error:nil];
|
||||
[self send:feed.notificationID
|
||||
title:feed.group.anyName
|
||||
body:[NSString stringWithFormat:NSLocalizedString(@"%ld unread articles", nil), count]];
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers article notifications (if enabled)
|
||||
+ (void)postArticle:(FeedArticle*)article {
|
||||
if (notifyType != NotificationTypePerArticle) {
|
||||
return;
|
||||
}
|
||||
[article.managedObjectContext obtainPermanentIDsForObjects:@[article] error:nil];
|
||||
[self send:article.notificationID
|
||||
title:article.feed.group.anyName
|
||||
body:article.title];
|
||||
}
|
||||
|
||||
/// Close already posted notifications because they were opened via menu
|
||||
+ (void)dismiss:(nullable NSArray<NSString*>*)list {
|
||||
if (list.count > 0) {
|
||||
[UNUserNotificationCenter.currentNotificationCenter removeDeliveredNotificationsWithIdentifiers:list];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Helper methods
|
||||
|
||||
/// Post notification (immediatelly).
|
||||
/// @param identifier Used to identify a specific instance (and dismiss a previously shown notification).
|
||||
+ (void)send:(NSString *)identifier title:(nullable NSString *)title body:(nullable NSString *)body {
|
||||
UNMutableNotificationContent *msg = [UNMutableNotificationContent new];
|
||||
if (title != nil) msg.title = title;
|
||||
if (body != nil) msg.body = body;
|
||||
// common settings:
|
||||
msg.categoryIdentifier = kCategoryDismissable;
|
||||
// TODO: make sound configurable?
|
||||
msg.sound = [UNNotificationSound defaultSound];
|
||||
[self send:identifier content: msg];
|
||||
}
|
||||
|
||||
/// Internal method for queueing a new notification.
|
||||
+ (void)send:(NSString *)identifier content:(UNMutableNotificationContent*)msg {
|
||||
UNUserNotificationCenter *center = UNUserNotificationCenter.currentNotificationCenter;
|
||||
|
||||
[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
|
||||
if (settings.authorizationStatus != UNAuthorizationStatusAuthorized) {
|
||||
return;
|
||||
}
|
||||
|
||||
UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:identifier content:msg trigger:nil];
|
||||
[center addNotificationRequest:req withCompletionHandler:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"Could not send notification: %@", error);
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Delegate
|
||||
|
||||
/// Must be implemented to show notifications while the app is in foreground
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
|
||||
// all the options
|
||||
UNNotificationPresentationOptions common = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionBadge;
|
||||
if (@available(macOS 11.0, *)) {
|
||||
completionHandler(common | UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionList);
|
||||
} else {
|
||||
completionHandler(common | UNNotificationPresentationOptionAlert);
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback method when user clicks on alert banner
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
|
||||
NSArray<FeedArticle*> *articles;
|
||||
|
||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
||||
NSString *theId = response.notification.request.identifier;
|
||||
if ([theId isEqualToString:kNotifyIdGlobal]) {
|
||||
// global notification
|
||||
articles = [StoreCoordinator articlesAtPath:nil isFeed:NO sorted:YES unread:YES inContext:moc limit:0];
|
||||
} else {
|
||||
NSURL *uri = [NSURL URLWithString:theId];
|
||||
NSManagedObjectID *oid = [moc.persistentStoreCoordinator managedObjectIDForURIRepresentation:uri];
|
||||
NSManagedObject *obj = [moc objectWithID:oid];
|
||||
if ([obj isKindOfClass:[FeedArticle class]]) {
|
||||
// per-article notification
|
||||
articles = @[(FeedArticle*)obj];
|
||||
} else if ([obj isKindOfClass:[Feed class]]) {
|
||||
// per-feed notification
|
||||
articles = [[[(Feed*)obj articles]
|
||||
filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"unread = 1"]]
|
||||
sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:NO]]];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
@interface SettingsAbout : NSViewController
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "SettingsAbout.h"
|
||||
#import "SettingsAboutView.h"
|
||||
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
|
||||
@interface SettingsAboutView : NSView
|
||||
|
||||
@@ -1,32 +1,10 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "SettingsAboutView.h"
|
||||
#import "NSView+Ext.h"
|
||||
|
||||
@implementation SettingsAboutView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super initWithFrame: NSZeroRect];
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 320, 327)];
|
||||
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
|
||||
NSString *version = [NSString stringWithFormat:NSLocalizedString(@"Version %@", nil), info[@"CFBundleShortVersionString"]];
|
||||
#if DEBUG // append build number, e.g., '0.9.4 (9906)'
|
||||
@@ -55,16 +33,16 @@
|
||||
NSMutableAttributedString *mas = [NSMutableAttributedString new];
|
||||
[mas beginEditing];
|
||||
[self str:mas add:@"Programming\n" bold:YES];
|
||||
[self str:mas add:@"Oleg Geier\n\n" bold:NO];
|
||||
[self str:mas add:@"Source Code Available\n" bold:YES];
|
||||
[self str:mas add:@"Oleg Geier\n" bold:NO];
|
||||
[self str:mas add:@"\nSource Code Available\n" bold:YES];
|
||||
[self str:mas add:@"github.com" link:@"https://github.com/relikd/baRSS"];
|
||||
[self str:mas add:@" (MIT License)\nor " bold:NO];
|
||||
[self str:mas add:@"gitlab.com" link:@"https://gitlab.com/relikd/baRSS"];
|
||||
[self str:mas add:@" (MIT License)\n\n" bold:NO];
|
||||
[self str:mas add:@"3rd-Party Libraries\n" bold:YES];
|
||||
[self str:mas add:@" (MIT License)\n" bold:NO];
|
||||
[self str:mas add:@"\nLibraries\n" bold:YES];
|
||||
[self str:mas add:@"RSXML2" link:@"https://github.com/relikd/RSXML2"];
|
||||
[self str:mas add:@" (MIT License)" bold:NO];
|
||||
[self str:mas add:@"\n\n\n\nOptions\n" bold:YES];
|
||||
[self str:mas add:@" (MIT License)\n" bold:NO];
|
||||
[self str:mas add:@"QLOPML" link:@"https://github.com/relikd/QLOPML"];
|
||||
[self str:mas add:@" (MIT License)\n" bold:NO];
|
||||
[self str:mas add:@"\n\n\nOptions\n" bold:YES];
|
||||
[self str:mas add:@"Fix Cache\n" link:@"barss:config/fixcache"];
|
||||
[self str:mas add:@"Backup now\n" link:@"barss:backup/show"];
|
||||
[mas endEditing];
|
||||
@@ -74,7 +52,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
|
||||
|
||||
@@ -1,27 +1,9 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 SettingsAppearance : NSViewController
|
||||
- (void)didSelectCheckbox:(NSButton*)sender;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "SettingsAppearance.h"
|
||||
#import "SettingsAppearanceView.h"
|
||||
#import "AppHook.h"
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
@class SettingsAppearance;
|
||||
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "SettingsAppearanceView.h"
|
||||
#import "NSView+Ext.h"
|
||||
#import "Constants.h" // column icons
|
||||
@@ -38,30 +16,99 @@
|
||||
@implementation SettingsAppearanceView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super initWithFrame: NSZeroRect];
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 320, 327)];
|
||||
// Insert matrix header (icons above checkbox matrix)
|
||||
ColumnIcon(self, X__, RSSImageSettingsGlobal, NSLocalizedString(@"Show in menu bar", nil));
|
||||
ColumnIcon(self, _X_, RSSImageSettingsGroup, NSLocalizedString(@"Show in group menu", nil));
|
||||
ColumnIcon(self, __X, RSSImageSettingsFeed, NSLocalizedString(@"Show in feed menu", nil));
|
||||
ColumnIcon(self, X__, RSSImageSettingsGlobal);
|
||||
ColumnIcon(self, _X_, RSSImageSettingsGroup);
|
||||
ColumnIcon(self, __X, RSSImageSettingsFeed);
|
||||
// Generate checkbox matrix
|
||||
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(@"Update all feeds", nil) c1:Pref_globalUpdateAll c2:nil c3:nil];
|
||||
[self entry:NSLocalizedString(@"Open all unread", nil) c1:Pref_globalOpenUnread c2:Pref_groupOpenUnread c3:Pref_feedOpenUnread];
|
||||
[self entry:NSLocalizedString(@"Mark all read", nil) c1:Pref_globalMarkRead c2:Pref_groupMarkRead c3:Pref_feedMarkRead];
|
||||
[self entry:NSLocalizedString(@"Mark all unread", nil) c1:Pref_globalMarkUnread c2:Pref_groupMarkUnread c3:Pref_feedMarkUnread];
|
||||
[self entry:NSLocalizedString(@"Number of unread articles", nil) c1:Pref_globalUnreadCount c2:Pref_groupUnreadCount c3:Pref_feedUnreadCount];
|
||||
[self entry:NSLocalizedString(@"Indicator for unread articles", nil) c1:nil c2:Pref_groupUnreadIndicator c3:Pref_feedUnreadIndicator];
|
||||
[[self entry:NSLocalizedString(@"Truncate article title", nil) c1:nil c2:nil c3:Pref_feedTruncateTitle]
|
||||
tooltip:NSLocalizedString(@"Truncate article title after 60 characters", nil)];
|
||||
[[self entry:NSLocalizedString(@"Limit number of articles", nil) c1:nil c2:nil c3:Pref_feedLimitArticles]
|
||||
tooltip:NSLocalizedString(@"Display at most 40 articles in feed menu", nil)];
|
||||
[self entry:NSLocalizedString(@"Tint menu bar icon on unread", nil)
|
||||
help:NSLocalizedString(@"If active, a color will indicate if there are unread articles.", nil)
|
||||
tip:nil
|
||||
c1:Pref_globalTintMenuIcon c1tt:NSLocalizedString(@"menu bar icon", nil)
|
||||
c2:nil c2tt:nil
|
||||
c3:nil c3tt:nil];
|
||||
|
||||
[self entry:NSLocalizedString(@"Update all feeds", nil)
|
||||
help:NSLocalizedString(@"Show button in main menu to reload all feeds. This will force fetch new online content regardless of next-update timer.", nil)
|
||||
tip:nil
|
||||
c1:Pref_globalUpdateAll c1tt:NSLocalizedString(@"in main 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;
|
||||
}
|
||||
|
||||
/// Helper method for matrix table header icons
|
||||
static inline void ColumnIcon(id this, CGFloat x, const NSImageName img, NSString *ttip) {
|
||||
[[[NSView imageView:img size:IconSize] placeIn:this x:x yTop:PAD_WIN] tooltip:ttip];
|
||||
static inline void ColumnIcon(id this, CGFloat x, const NSImageName img) {
|
||||
[[NSView imageView:img size:IconSize] placeIn:this x:x yTop:PAD_WIN];
|
||||
}
|
||||
|
||||
/// Helper method for generating a checkbox
|
||||
@@ -72,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
|
||||
- (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;
|
||||
self.y += (PAD_S + HEIGHT_LABEL);
|
||||
// TODO: localize: global, group, feed
|
||||
if (pref1) Checkbox(self, X__ + 2, y + 2, pref1).accessibilityLabel = [label stringByAppendingString:@" (global)"];
|
||||
if (pref2) Checkbox(self, _X_ + 2, y + 2, pref2).accessibilityLabel = [label stringByAppendingString:@" (group)"];
|
||||
if (pref3) Checkbox(self, __X + 2, y + 2, pref3).accessibilityLabel = [label stringByAppendingString:@" (feed)"];
|
||||
return [[[NSView label:label] placeIn:self x:PAD_WIN + 3 * colWidth yTop:y] sizeToRight:PAD_WIN];
|
||||
if (pref1) [Checkbox(self, X__ + 2, y + 2, pref1) tooltip:ttip1].accessibilityLabel = [label stringByAppendingString:@" (global)"];
|
||||
if (pref2) [Checkbox(self, _X_ + 2, y + 2, pref2) tooltip:ttip2].accessibilityLabel = [label stringByAppendingString:@" (group)"];
|
||||
if (pref3) [Checkbox(self, __X + 2, y + 2, pref3) tooltip:ttip3].accessibilityLabel = [label stringByAppendingString:@" (feed)"];
|
||||
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
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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;
|
||||
#import "ModalSheet.h"
|
||||
@class FeedGroup, ModalSheet;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ModalEditDialog : NSViewController
|
||||
+ (instancetype)modalWith:(FeedGroup*)group;
|
||||
- (ModalSheet*)getModalSheet;
|
||||
@@ -33,8 +13,10 @@
|
||||
|
||||
@interface ModalFeedEdit : ModalEditDialog <NSTextFieldDelegate>
|
||||
- (void)didClickWarningButton:(NSButton*)sender;
|
||||
- (void)openRegexConverter;
|
||||
@end
|
||||
|
||||
@interface ModalGroupEdit : ModalEditDialog
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 RSXML2;
|
||||
#import "ModalFeedEdit.h"
|
||||
#import "ModalFeedEditView.h"
|
||||
@@ -33,6 +11,9 @@
|
||||
#import "NSView+Ext.h"
|
||||
#import "NSDate+Ext.h"
|
||||
#import "NSURL+Ext.h"
|
||||
#import "RegexConverterController.h"
|
||||
#import "RegexConverterModal.h"
|
||||
#import "RegexConverter+Ext.h"
|
||||
|
||||
// ################################################################
|
||||
// #
|
||||
@@ -81,6 +62,9 @@
|
||||
@property (strong) FeedDownload *memFeed;
|
||||
@property (weak) FaviconDownload *memIcon;
|
||||
@property (strong) RefreshStatisticsView *statisticsView;
|
||||
@property (nonatomic, assign) BOOL skipIconDownload;
|
||||
@property (nonatomic, assign) BOOL openRegexAfterDownload;
|
||||
@property (weak) id eventMonitor;
|
||||
@end
|
||||
|
||||
@implementation ModalFeedEdit
|
||||
@@ -93,6 +77,13 @@
|
||||
self.view.refreshNum.intValue = 30;
|
||||
[NSDate populateUnitsMenu:self.view.refreshUnit selected:TimeUnitMinutes];
|
||||
[self populateTextFields:self.feedGroup];
|
||||
|
||||
// removed in windowShouldClose:
|
||||
self.eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged handler:^(NSEvent *event) {
|
||||
BOOL optionKeyActive = ((event.modifierFlags & NSEventModifierFlagOption) != 0);
|
||||
self.view.regexConverterButton.hidden = !self.feedGroup.feed.regex && !optionKeyActive;
|
||||
return event;
|
||||
}];
|
||||
}
|
||||
|
||||
/// Pre-fill UI control field values with @c FeedGroup properties.
|
||||
@@ -103,6 +94,7 @@
|
||||
self.view.url.objectValue = fg.feed.meta.url;
|
||||
self.previousURL = self.view.url.stringValue;
|
||||
self.view.favicon.image = [fg.feed iconImage16];
|
||||
self.view.regexConverterButton.hidden = !fg.feed.regex;
|
||||
[NSDate setInterval:fg.feed.meta.refresh forPopup:self.view.refreshUnit andField:self.view.refreshNum animate:NO];
|
||||
[self statsForCoreDataObject];
|
||||
}
|
||||
@@ -124,7 +116,8 @@
|
||||
[f.meta setRefreshIfChanged:intv];
|
||||
if (self.memFeed) {
|
||||
[self.memFeed copyValuesTo:f ignoreError:YES];
|
||||
[f setNewIcon:self.faviconFile]; // only if downloaded anything (nil deletes icon!)
|
||||
if (self.faviconFile) // only if downloaded anything (nil deletes icon!)
|
||||
[f setNewIcon:self.faviconFile];
|
||||
self.faviconFile = nil;
|
||||
}
|
||||
}
|
||||
@@ -143,9 +136,11 @@
|
||||
- (void)downloadRSS {
|
||||
[self cancelDownloads];
|
||||
[self.modalSheet setDoneEnabled:NO]; // prevent user from closing the dialog during download
|
||||
[self.view.spinnerURL startAnimation:nil];
|
||||
[self.view.spinnerName startAnimation:nil];
|
||||
self.view.favicon.image = nil;
|
||||
if (!self.skipIconDownload) {
|
||||
[self.view.spinnerURL startAnimation:nil];
|
||||
self.view.favicon.image = nil;
|
||||
}
|
||||
self.view.warningButton.hidden = YES;
|
||||
// User didn't change title since last fetch. Will be pre-filled with new title after download
|
||||
if ([self.view.name.stringValue isEqualToString:self.view.name.placeholderString]) {
|
||||
@@ -153,7 +148,9 @@
|
||||
self.view.name.placeholderString = NSLocalizedString(@"Loading …", nil);
|
||||
}
|
||||
self.previousURL = self.view.url.stringValue;
|
||||
self.memFeed = [[FeedDownload withURL:self.previousURL] startWithDelegate:self];
|
||||
self.memFeed = [[[FeedDownload withURL:self.previousURL]
|
||||
withRegex:self.feedGroup.feed.regex enforce:self.openRegexAfterDownload]
|
||||
startWithDelegate:self];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +159,7 @@
|
||||
|
||||
@return Either URL string or @c nil if user canceled the selection.
|
||||
*/
|
||||
- (NSString*)feedDownload:(FeedDownload*)sender selectFeedFromList:(NSArray<RSHTMLMetadataFeedLink*>*)list {
|
||||
- (nullable NSString*)feedDownload:(FeedDownload*)sender selectFeedFromList:(NSArray<RSHTMLMetadataFeedLink*>*)list {
|
||||
NSMenu *menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Choose feed menu", nil)];
|
||||
menu.autoenablesItems = NO;
|
||||
for (RSHTMLMetadataFeedLink *fl in list) {
|
||||
@@ -204,7 +201,7 @@
|
||||
self.view.favicon.hidden = hasError;
|
||||
self.view.warningButton.hidden = !hasError;
|
||||
// Start favicon download
|
||||
if (hasError)
|
||||
if (hasError || self.skipIconDownload)
|
||||
[self downloadComplete];
|
||||
else
|
||||
self.memIcon = [[sender faviconDownload] startWithDelegate:self];
|
||||
@@ -216,7 +213,13 @@
|
||||
*/
|
||||
- (void)faviconDownload:(FaviconDownload*)sender didFinish:(nullable NSURL*)path {
|
||||
// Create image from favicon temporary file location or default icon if no favicon exists.
|
||||
NSImage *img = path ? [[NSImage alloc] initByReferencingURL:path] : [NSImage imageNamed:RSSImageDefaultRSSIcon];
|
||||
NSImage *img;
|
||||
if (path) {
|
||||
NSData* data = [[NSData alloc] initWithContentsOfURL:path];
|
||||
img = [[NSImage alloc] initWithData:data];
|
||||
} else {
|
||||
img = [NSImage imageNamed:RSSImageDefaultRSSIcon];
|
||||
}
|
||||
self.view.favicon.image = img;
|
||||
self.faviconFile = path;
|
||||
[self downloadComplete];
|
||||
@@ -226,8 +229,47 @@
|
||||
- (void)downloadComplete {
|
||||
[self.view.spinnerURL stopAnimation:nil];
|
||||
[self.modalSheet setDoneEnabled:YES];
|
||||
self.skipIconDownload = NO;
|
||||
|
||||
if (self.openRegexAfterDownload) {
|
||||
[self openRegexConverter];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Regex Converter
|
||||
|
||||
- (void)openRegexConverter {
|
||||
if (!self.openRegexAfterDownload) {
|
||||
self.openRegexAfterDownload = YES;
|
||||
self.skipIconDownload = self.feedGroup.feed.hasIcon;
|
||||
[self downloadRSS];
|
||||
return;
|
||||
}
|
||||
self.openRegexAfterDownload = NO;
|
||||
|
||||
// shrink FeedEdit modal size to effectively hide it behind new modal
|
||||
NSRect previous = self.modalSheet.frame;
|
||||
CGFloat minWidthDiff = previous.size.width - self.modalSheet.minSize.width;
|
||||
[self.modalSheet setFrame:NSInsetRect(previous, minWidthDiff / 2.0, 0) display:NO];
|
||||
|
||||
RegexConverterController *c = [RegexConverterController withData:self.memFeed.rawData andConverter:self.feedGroup.feed.regex];
|
||||
[self.modalSheet.sheetParent beginCriticalSheet:[c getModalSheet] completionHandler:^(NSModalResponse returnCode) {
|
||||
// reset previous size
|
||||
[self.modalSheet setFrame:previous display:NO];
|
||||
|
||||
if (returnCode == NSModalResponseOK) {
|
||||
[c applyChanges:self.feedGroup.feed];
|
||||
self.skipIconDownload = self.feedGroup.feed.hasIcon;
|
||||
self.view.regexConverterButton.hidden = !self.feedGroup.feed.regex;
|
||||
[self downloadRSS];
|
||||
} else {
|
||||
[self populateTextFields:self.feedGroup];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Feed Statistics
|
||||
|
||||
/// Perform statistics on newly downloaded feed item
|
||||
@@ -280,6 +322,7 @@
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:NSControlTextDidEndEditingNotification object:self.view.url];
|
||||
return NO;
|
||||
}
|
||||
[NSEvent removeMonitor:self.eventMonitor];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +1,28 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
@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;
|
||||
@property (strong) IBOutlet NSButton *regexConverterButton;
|
||||
|
||||
- (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
|
||||
|
||||
@@ -1,28 +1,7 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "ModalFeedEditView.h"
|
||||
#import "ModalFeedEdit.h"
|
||||
#import "NSView+Ext.h"
|
||||
#import "Constants.h"
|
||||
|
||||
@interface StrictUIntFormatter : NSFormatter
|
||||
@end
|
||||
@@ -36,7 +15,7 @@
|
||||
NSView *labels = [NSView labelColumn:lbls rowHeight:HEIGHT_INPUTFIELD padding:PAD_S];
|
||||
|
||||
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 0, NSHeight(labels.frame))];
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 320, NSHeight(labels.frame))];
|
||||
self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
|
||||
CGFloat x = NSWidth(labels.frame) + PAD_S;
|
||||
@@ -47,7 +26,8 @@
|
||||
self.url = [[[NSView inputField:@"https://example.org/feed.rss" width:0] placeIn:self x:x yTop:0] sizeToRight:PAD_S + 18];
|
||||
self.spinnerURL = [[NSView activitySpinner] placeIn:self xRight:1 yTop:2.5];
|
||||
self.favicon = [[[NSView imageView:nil size:18] tooltip:NSLocalizedString(@"Favicon", nil)] placeIn:self xRight:0 yTop:1.5];
|
||||
self.warningButton = [[[[NSView buttonIcon:NSImageNameCaution size:18] action:@selector(didClickWarningButton:) target:nil] // up the responder chain
|
||||
self.warningButton = [[[[NSView buttonIcon:NSImageNameCaution size:18]
|
||||
action:@selector(didClickWarningButton:) target:nil] // up the responder chain
|
||||
tooltip:NSLocalizedString(@"Click here to show failure reason", nil)]
|
||||
placeIn:self xRight:0 yTop:1.5];
|
||||
// 2. row
|
||||
@@ -56,6 +36,10 @@
|
||||
// 3. row
|
||||
self.refreshNum = [[NSView inputField:@"30" width:85] placeIn:self x:x yTop:2*rowHeight];
|
||||
self.refreshUnit = [[NSView popupButton:120] placeIn:self x:NSMaxX(self.refreshNum.frame) + PAD_M yTop:2*rowHeight];
|
||||
self.regexConverterButton = [[[[NSView buttonIcon:RSSImageRegexIcon size:19]
|
||||
action:@selector(openRegexConverter) target:controller]
|
||||
tooltip:NSLocalizedString(@"Regex converter", nil)]
|
||||
placeIn:self xRight:0 yTop:2*rowHeight + 1];
|
||||
|
||||
// initial state
|
||||
self.url.accessibilityLabel = lbls[0];
|
||||
@@ -63,6 +47,7 @@
|
||||
self.refreshNum.accessibilityLabel = NSLocalizedString(@"Refresh interval", nil);
|
||||
self.url.delegate = controller;
|
||||
self.warningButton.hidden = YES;
|
||||
self.regexConverterButton.hidden = YES;
|
||||
self.refreshNum.formatter = [StrictUIntFormatter new]; // see below ...
|
||||
[self prepareWarningPopover];
|
||||
return self;
|
||||
|
||||
@@ -1,27 +1,7 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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
|
||||
|
||||
@protocol RefreshIntervalButtonDelegate <NSObject>
|
||||
@required
|
||||
/// @c sender.tag is refresh interval in seconds
|
||||
@@ -34,3 +14,5 @@
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "RefreshStatisticsView.h"
|
||||
#import "NSDate+Ext.h"
|
||||
#import "NSView+Ext.h"
|
||||
@@ -36,7 +14,7 @@
|
||||
@return Centered view without autoresizing.
|
||||
*/
|
||||
- (instancetype)initWithRefreshInterval:(NSDictionary*)info articleCount:(NSUInteger)count callback:(nullable id<RefreshIntervalButtonDelegate>)callback {
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 320, 327)];
|
||||
self.autoresizesSubviews = NO;
|
||||
|
||||
NSTextField *dateView = [self viewForArticlesCount:count latest:info];
|
||||
|
||||
@@ -1,29 +1,11 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "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
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "SettingsFeeds+DragDrop.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "Constants.h"
|
||||
@@ -160,7 +138,7 @@ const NSPasteboardType dragReorder = @"de.relikd.baRSS.drag-reorder";
|
||||
if (selection.count > 0)
|
||||
[self.dataStore setSelectionIndexPaths:[selection sortedArrayUsingSelector:@selector(compare:)]];
|
||||
|
||||
[UpdateScheduler downloadList:feedsList userInitiated:YES finally:^{
|
||||
[UpdateScheduler downloadList:feedsList userInitiated:YES notifications:NO finally:^{
|
||||
[self endCoreDataChangeUndoEmpty:NO forceUndo:NO];
|
||||
for (Feed *f in feedsList)
|
||||
[moc refreshObject:f.group mergeChanges:NO]; // fixes blank icon if imported with no inet conn
|
||||
|
||||
@@ -1,31 +1,11 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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
|
||||
|
||||
/** 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 +20,5 @@
|
||||
- (BOOL)endCoreDataChangeUndoEmpty:(BOOL)undoEmpty forceUndo:(BOOL)force;
|
||||
- (void)restoreOrderingAndIndexPathStr:(NSArray<NSTreeNode*>*)parentsList;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "SettingsFeeds+DragDrop.h"
|
||||
#import "Constants.h"
|
||||
#import "StoreCoordinator.h"
|
||||
|
||||
@@ -1,32 +1,12 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
@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 +25,5 @@ extern NSUserInterfaceItemIdentifier const CustomCellRefresh;
|
||||
@interface SeparatorColumnCell : NSTableCellView
|
||||
extern NSUserInterfaceItemIdentifier const CustomCellSeparator;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "SettingsFeedsView.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "FeedGroup+Ext.h"
|
||||
@@ -35,7 +13,7 @@
|
||||
@implementation SettingsFeedsView
|
||||
|
||||
- (instancetype)initWithController:(SettingsFeeds*)delegate {
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 201, 327)];
|
||||
if (self) {
|
||||
self.controller = delegate; // make sure its first
|
||||
self.outline = [self generateOutlineView]; // uses self.controller
|
||||
@@ -82,7 +60,7 @@
|
||||
- (void)setOutlineColumns:(NSOutlineView*)outline {
|
||||
NSTableColumn *colName = [[NSTableColumn alloc] initWithIdentifier:CustomCellName];
|
||||
colName.title = NSLocalizedString(@"Name", nil);
|
||||
colName.width = 10000;
|
||||
colName.width = 201;
|
||||
colName.maxWidth = 10000;
|
||||
colName.resizingMask = NSTableColumnAutoresizingMask;
|
||||
[outline addTableColumn:colName];
|
||||
@@ -192,7 +170,7 @@
|
||||
NSUserInterfaceItemIdentifier const CustomCellName = @"NameColumnCell";
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect {
|
||||
self = [super initWithFrame:frameRect];
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 100, 0)];
|
||||
self.identifier = CustomCellName;
|
||||
self.imageView = [[NSView imageView:nil size:16] placeIn:self x:1 yTop:1];
|
||||
self.imageView.accessibilityLabel = NSLocalizedString(@"Feed icon", nil);
|
||||
@@ -217,7 +195,7 @@ NSUserInterfaceItemIdentifier const CustomCellName = @"NameColumnCell";
|
||||
NSUserInterfaceItemIdentifier const CustomCellRefresh = @"RefreshColumnCell";
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect {
|
||||
self = [super initWithFrame:frameRect];
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 100, 0)];
|
||||
self.identifier = CustomCellRefresh;
|
||||
self.textField = [[[[NSView label:@""] textRight] placeIn:self x:0 yTop:0] sizeToRight:0];
|
||||
self.textField.accessibilityTitle = @" "; // otherwise groups and separators will say 'text'
|
||||
@@ -246,7 +224,7 @@ NSUserInterfaceItemIdentifier const CustomCellRefresh = @"RefreshColumnCell";
|
||||
NSUserInterfaceItemIdentifier const CustomCellSeparator = @"SeparatorColumnCell";
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect {
|
||||
self = [super initWithFrame:frameRect];
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 100, 0)];
|
||||
self.identifier = CustomCellSeparator;
|
||||
[[[[DrawSeparator alloc] initWithFrame:self.frame] placeIn:self x:0 y:0] sizableWidthAndHeight];
|
||||
return self;
|
||||
|
||||
@@ -1,28 +1,11 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 SettingsGeneral : NSViewController
|
||||
- (void)changeHttpApplication:(NSPopUpButton *)sender;
|
||||
- (void)clickHowToDefaults:(NSButton *)sender;
|
||||
- (void)changeHttpApplication:(NSPopUpButton *)sender;
|
||||
- (void)changeNotificationType:(NSPopUpButton *)sender;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,30 +1,9 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "SettingsGeneral.h"
|
||||
#import "UserPrefs.h"
|
||||
#import "StoreCoordinator.h"
|
||||
#import "Constants.h"
|
||||
#import "SettingsGeneralView.h"
|
||||
#import "NotifyEndpoint.h"
|
||||
|
||||
@interface SettingsGeneral()
|
||||
@property (strong) IBOutlet SettingsGeneralView *view; // override
|
||||
@@ -35,19 +14,38 @@
|
||||
|
||||
- (void)loadView {
|
||||
self.view = [[SettingsGeneralView alloc] initWithController:self];
|
||||
// Default http application for opening the feed urls
|
||||
NSPopUpButton *pop = self.view.popupHttpApplication;
|
||||
[pop removeAllItems];
|
||||
[pop addItemWithTitle:NSLocalizedString(@"System Default", @"Default web browser application")];
|
||||
NSArray<NSString*> *browsers = CFBridgingRelease(LSCopyAllHandlersForURLScheme(CFSTR("https")));
|
||||
for (NSString *bundleID in browsers) {
|
||||
[pop addItemWithTitle: [self applicationNameForBundleId:bundleID]];
|
||||
pop.lastItem.representedObject = bundleID;
|
||||
}
|
||||
[pop selectItemAtIndex:[pop indexOfItemWithRepresentedObject:UserPrefsString(Pref_defaultHttpApplication)]];
|
||||
|
||||
// Default RSS Reader application
|
||||
NSString *feedBundleId = CFBridgingRelease(LSCopyDefaultHandlerForURLScheme(CFSTR("feed")));
|
||||
self.view.defaultReader.objectValue = [self applicationNameForBundleId:feedBundleId];
|
||||
|
||||
// Default http application for opening the feed urls
|
||||
NSPopUpButton *defaultApp = self.view.popupHttpApplication;
|
||||
[defaultApp removeAllItems];
|
||||
[defaultApp addItemWithTitle:NSLocalizedString(@"System Default", @"Default web browser application")];
|
||||
NSArray<NSString*> *browsers = CFBridgingRelease(LSCopyAllHandlersForURLScheme(CFSTR("https")));
|
||||
for (NSString *bundleID in browsers) {
|
||||
[defaultApp addItemWithTitle: [self applicationNameForBundleId:bundleID]];
|
||||
defaultApp.lastItem.representedObject = bundleID;
|
||||
}
|
||||
[defaultApp selectItemAtIndex:[defaultApp indexOfItemWithRepresentedObject:UserPrefsString(Pref_defaultHttpApplication)]];
|
||||
|
||||
// Notification settings (disabled, per article, per feed, total)
|
||||
NSPopUpButton *notify = self.view.popupNotificationType;
|
||||
[notify removeAllItems];
|
||||
[notify addItemsWithTitles:@[
|
||||
NSLocalizedString(@"Disabled", @"No notifications"),
|
||||
NSLocalizedString(@"Per Article", nil),
|
||||
NSLocalizedString(@"Per Feed", nil),
|
||||
NSLocalizedString(@"Global “X unread articles”", nil),
|
||||
]];
|
||||
notify.itemArray[0].representedObject = NotificationTypeToString(NotificationTypeDisabled);
|
||||
notify.itemArray[1].representedObject = NotificationTypeToString(NotificationTypePerArticle);
|
||||
notify.itemArray[2].representedObject = NotificationTypeToString(NotificationTypePerFeed);
|
||||
notify.itemArray[3].representedObject = NotificationTypeToString(NotificationTypeGlobal);
|
||||
NotificationType savedType = UserPrefsNotificationType();
|
||||
[notify selectItemAtIndex:[notify indexOfItemWithRepresentedObject:NotificationTypeToString(savedType)]];
|
||||
self.view.notificationHelp.stringValue = [self notificationHelpString:savedType];
|
||||
}
|
||||
|
||||
/// Get human readable application name such as 'Safari' or 'baRSS'
|
||||
@@ -63,11 +61,6 @@
|
||||
|
||||
#pragma mark - User interaction
|
||||
|
||||
// Callback method fired when user selects a different item from popup list
|
||||
- (void)changeHttpApplication:(NSPopUpButton *)sender {
|
||||
UserPrefsSet(Pref_defaultHttpApplication, sender.selectedItem.representedObject);
|
||||
}
|
||||
|
||||
// Callback method from round help button right of default feed reader text
|
||||
- (void)clickHowToDefaults:(NSButton *)sender {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
@@ -85,4 +78,29 @@
|
||||
|
||||
// x-apple.systempreferences:com.apple.preferences.users?startupItemsPref
|
||||
|
||||
// Callback method fired when user selects a different item from popup list
|
||||
- (void)changeHttpApplication:(NSPopUpButton *)sender {
|
||||
UserPrefsSet(Pref_defaultHttpApplication, sender.selectedItem.representedObject);
|
||||
}
|
||||
|
||||
- (void)changeNotificationType:(NSPopUpButton *)sender {
|
||||
UserPrefsSet(Pref_notificationType, sender.selectedItem.representedObject);
|
||||
self.view.notificationHelp.stringValue = [self notificationHelpString:UserPrefsNotificationType()];
|
||||
[NotifyEndpoint activate];
|
||||
}
|
||||
|
||||
/// Help string explaining the different notification settings (for the current configuration)
|
||||
- (NSString*)notificationHelpString:(NotificationType)typ {
|
||||
switch (typ) {
|
||||
case NotificationTypeDisabled:
|
||||
return NSLocalizedString(@"Notifications are disabled. You will not get any notifications even if you enable them in System Settings.", nil);
|
||||
case NotificationTypePerArticle:
|
||||
return NSLocalizedString(@"You will get a notification for each article (“Feed Title: Article Title”). A click on the notification banner opens the article link and marks the item as read.", nil);
|
||||
case NotificationTypePerFeed:
|
||||
return NSLocalizedString(@"You will get a notification for each feed whenever one or more new articles are published (“Feed Title: X unread articles”). A click on the notification banner will open all unread articles of that feed.", nil);
|
||||
case NotificationTypeGlobal:
|
||||
return NSLocalizedString(@"You will get a single notification for all feeds combined (“baRSS: X unread articles”). A click on the notification banner will open all unread articles of all feeds.", nil);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,34 +1,17 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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;
|
||||
@class SettingsGeneral;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsGeneralView : NSView
|
||||
@property (weak) IBOutlet NSPopUpButton* popupHttpApplication;
|
||||
@property (weak) IBOutlet NSTextField *defaultReader;
|
||||
@property (strong) IBOutlet NSTextField *defaultReader;
|
||||
@property (strong) IBOutlet NSPopUpButton* popupHttpApplication;
|
||||
@property (strong) IBOutlet NSPopUpButton* popupNotificationType;
|
||||
@property (strong) IBOutlet NSTextField* notificationHelp;
|
||||
|
||||
- (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
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 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 "SettingsGeneralView.h"
|
||||
#import "SettingsGeneral.h"
|
||||
#import "NSView+Ext.h"
|
||||
@@ -27,16 +5,30 @@
|
||||
@implementation SettingsGeneralView
|
||||
|
||||
- (instancetype)initWithController:(SettingsGeneral*)controller {
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
self = [super initWithFrame:NSMakeRect(0, 0, 320, 327)];
|
||||
|
||||
// Change default feed reader application
|
||||
NSTextField *l1 = [[NSView label:NSLocalizedString(@"Default feed reader:", nil)] placeIn:self x:PAD_WIN yTop:PAD_WIN + 3];
|
||||
NSButton *help = [[[NSView helpButton] action:@selector(clickHowToDefaults:) target:controller] placeIn:self xRight:PAD_WIN yTop:PAD_WIN];
|
||||
self.defaultReader = [[[[NSView label:@""] bold] placeIn:self x:NSMaxX(l1.frame) + PAD_S yTop:PAD_WIN + 3] sizeToRight:NSWidth(help.frame) + PAD_WIN];
|
||||
|
||||
// Popup button 'Open URLs with:'
|
||||
CGFloat y = YFromTop(help) + PAD_M;
|
||||
NSTextField *l2 = [[NSView label:NSLocalizedString(@"Open URLs with:", nil)] placeIn:self x:PAD_WIN yTop:y + 1];
|
||||
self.popupHttpApplication = [[[[NSView popupButton:0] placeIn:self x:NSMaxX(l2.frame) + PAD_S yTop:y] sizeToRight:PAD_WIN]
|
||||
action:@selector(changeHttpApplication:) target:controller];
|
||||
|
||||
// Notification type
|
||||
y = YFromTop(self.popupHttpApplication) + PAD_M;
|
||||
NSTextField *l3 = [[NSView label:NSLocalizedString(@"Notifications:", nil)] placeIn:self x:PAD_WIN yTop:y + 1];
|
||||
self.popupNotificationType = [[[[NSView popupButton:0] placeIn:self x:NSMaxX(l3.frame) + PAD_S yTop:y] sizeToRight:PAD_WIN]
|
||||
action:@selector(changeNotificationType:) target:controller];
|
||||
|
||||
// Notification help text
|
||||
y = YFromTop(self.popupNotificationType) + PAD_M;
|
||||
self.notificationHelp = [[[[[NSView label:@""] gray]
|
||||
multiline:NSMakeSize(320 - 2*PAD_WIN, HEIGHT_LABEL * 5)]
|
||||
placeIn:self x:PAD_WIN yTop:y] sizeToRight:PAD_WIN];
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,7 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 ModalSheet : NSPanel
|
||||
@property (readonly) BOOL didTapCancel;
|
||||
|
||||
@@ -31,3 +11,5 @@
|
||||
- (void)setDoneEnabled:(BOOL)accept;
|
||||
- (void)extendContentViewBy:(CGFloat)dy;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "ModalSheet.h"
|
||||
#import "UserPrefs.h"
|
||||
#import "NSView+Ext.h"
|
||||
@@ -44,7 +22,7 @@
|
||||
[content setFrameSize: NSMakeSize(w, h)];
|
||||
|
||||
// after content size, increase to window size
|
||||
w += 2 * PAD_WIN;
|
||||
w += 2 * (NSInteger)PAD_WIN;
|
||||
h += PAD_WIN + contentOffsetY; // the second PAD_WIN is already in contentOffsetY
|
||||
|
||||
NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView;
|
||||
|
||||
@@ -1,29 +1,11 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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;
|
||||
@class SettingsFeeds;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Preferences : NSWindow <NSWindowDelegate>
|
||||
+ (instancetype)window;
|
||||
- (__kindof NSViewController*)selectTab:(NSUInteger)index;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2018 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 "Preferences.h"
|
||||
#import "SettingsGeneral.h"
|
||||
#import "SettingsFeeds.h"
|
||||
@@ -38,18 +16,13 @@
|
||||
if (self) {
|
||||
self.tabStyle = NSTabViewControllerTabStyleToolbar;
|
||||
self.transitionOptions = NSViewControllerTransitionNone;
|
||||
|
||||
NSTabViewItem *flexibleWidth = [[NSTabViewItem alloc] initWithIdentifier:NSToolbarFlexibleSpaceItemIdentifier];
|
||||
flexibleWidth.viewController = [NSViewController new];
|
||||
|
||||
self.tabViewItems = @[
|
||||
TabItem(NSImageNamePreferencesGeneral, NSLocalizedString(@"General", nil), [SettingsGeneral class]),
|
||||
TabItem(NSImageNameUserAccounts, NSLocalizedString(@"Feeds", nil), [SettingsFeeds class]),
|
||||
TabItem(NSImageNameFontPanel, NSLocalizedString(@"Appearance", nil), [SettingsAppearance class]),
|
||||
flexibleWidth,
|
||||
TabItem(NSImageNameInfo, NSLocalizedString(@"About", nil), [SettingsAbout class]),
|
||||
];
|
||||
[self switchToTab: UserPrefsUInt(Pref_prefSelectedTab)];
|
||||
self.selectedTabViewItemIndex = -1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -65,19 +38,16 @@ static inline NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Clas
|
||||
/// Safely set selected index without out of bounds exception
|
||||
- (__kindof NSViewController*)switchToTab:(NSUInteger)index {
|
||||
if (index < 0 || index >= self.tabViewItems.count)
|
||||
return nil;
|
||||
NSTabViewItem *tab = self.tabViewItems[index];
|
||||
if (tab.identifier == NSToolbarFlexibleSpaceItemIdentifier)
|
||||
return nil;
|
||||
index = 0;
|
||||
self.selectedTabViewItemIndex = (NSInteger)index;
|
||||
return [tab viewController];
|
||||
return [self.tabViewItems[index] viewController];
|
||||
}
|
||||
|
||||
/// Delegate method, store last selected tab to user preferences
|
||||
- (void)tabView:(NSTabView*)tabView didSelectTabViewItem:(nullable NSTabViewItem*)tabViewItem {
|
||||
[super tabView:tabView didSelectTabViewItem:tabViewItem];
|
||||
NSInteger newIndex = self.selectedTabViewItemIndex;
|
||||
if (UserPrefsInt(Pref_prefSelectedTab) != newIndex)
|
||||
if (newIndex != -1 && UserPrefsInt(Pref_prefSelectedTab) != newIndex)
|
||||
UserPrefsSetInt(Pref_prefSelectedTab, newIndex);
|
||||
}
|
||||
|
||||
@@ -92,7 +62,10 @@ static inline NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Clas
|
||||
w.contentMinSize = NSMakeSize(320, 327);
|
||||
w.windowController.shouldCascadeWindows = YES;
|
||||
w.title = [NSString stringWithFormat:NSLocalizedString(@"%@ Preferences", nil), NSProcessInfo.processInfo.processName];
|
||||
w.contentViewController = [PrefTabs new];
|
||||
PrefTabs *tabController = [PrefTabs new];
|
||||
w.contentViewController = tabController;
|
||||
[w.toolbar insertItemWithItemIdentifier:NSToolbarSpaceItemIdentifier atIndex:3];
|
||||
[w.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:4];
|
||||
w.delegate = w;
|
||||
NSWindowPersistableFrameDescriptor prevFrame = UserPrefsString(Pref_prefWindowFrame);
|
||||
if (!prevFrame) {
|
||||
@@ -101,6 +74,7 @@ static inline NSTabViewItem* TabItem(NSImageName imageName, NSString *text, Clas
|
||||
} else {
|
||||
[w setFrameFromString:prevFrame];
|
||||
}
|
||||
[tabController switchToTab:UserPrefsUInt(Pref_prefSelectedTab)];
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user