├── .gitignore
├── AppIcon.sketch
├── LICENSE
├── README.md
├── RSS-Button-MacOS
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── AppIcon-128pt@1x.png
│ │ ├── AppIcon-128pt@2x.png
│ │ ├── AppIcon-16pt@1x.png
│ │ ├── AppIcon-16pt@2x.png
│ │ ├── AppIcon-256pt@1x.png
│ │ ├── AppIcon-256pt@2x.png
│ │ ├── AppIcon-32pt@1x.png
│ │ ├── AppIcon-32pt@2x.png
│ │ ├── AppIcon-512pt@1x.png
│ │ ├── AppIcon-512pt@2x.png
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── Main.storyboard
├── FeedHandlerModel.swift
├── Info.plist
├── RSS_Button_for_Safari.entitlements
├── SettingsManager.swift
└── ViewController.swift
├── RSS-Button-Safari-Extension
├── Base.lproj
│ └── SafariExtensionViewController.xib
├── FeedModel.swift
├── Info.plist
├── RSS_Button.entitlements
├── SafariExtensionHandler.swift
├── SafariExtensionStateManager.swift
├── SafariExtensionViewController.swift
├── ToolbarItemIcon.pdf
└── script.js
├── RSS-Button-for-Safari.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── ToolbarItemIcon.sketch
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
--------------------------------------------------------------------------------
/AppIcon.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/AppIcon.sketch
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2015 Reda Lemeden
4 | Copyright (c) 2018 BitPiston Studios
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RSS Button for Safari
2 | A native app extension written in Swift for Safari 12+ adding feed discovery via toolbar button.
3 |
4 | Inspired by Syndicate by Reda Lemeden:
5 | https://github.com/kaishin/syndicate/
6 |
7 | Motiviation thanks to Apple depreciating Safari legacy extensions in Safari 12:
8 | https://developer.apple.com/documentation/safariextensions
9 |
10 |
11 | ## Installation
12 |
13 | RSS Button for Safari can be purchased from the Mac App Store:
14 |
15 |
16 |
17 | Why isn't it free? To cover the cost of the Apple Developer Program fee required to sign and distribute the extension.
18 |
19 | Alternatively you can checkout the source and build the application and extension yourself allowing unsigned extensions from the develop menu in Safari.
20 |
21 | To install this extension after purchasing on the App Store or compiling from source:
22 |
23 | 1) Open RSS Button for Safari from Applications.
24 |
25 | 3) Choose your preferred news reader:
26 |
27 | 
28 |
29 | 4) Enable the extension from Safari Preferences under the extensions tab:
30 |
31 | 
32 |
33 | 6) If the toolbar button does not appear automatically in Safari go to View > Customize Toolbar and drag the RSS Button to your toolbar.
34 |
35 |
36 | ## Requirements
37 |
38 | Requires macOS 10.12 or newer and Safari 12 or newer.
39 |
40 | RSS Button for Safari requires either a desktop news reader supporting RSS, Atom or JSON feeds or an account with an online news reader. If your preferred application or online news reader isn't one the below services feel free to contact me or open an issue on GitHub.
41 |
42 |
43 | ### Compatible news reader applications
44 |
45 | Compatible news reader applications include:
46 | - Cappuccino
47 | - Feedy (not to be confused with Feedly)
48 | - Leaf
49 | - Newsflow
50 | - News Explorer
51 | - News Menu
52 | - NetNewsWire
53 | - ReadKit
54 | - Reeder 4 or 5
55 | - Stripes
56 |
57 | News reader applications that are not compatible or have known issues opening feed URLs automatically:
58 | - Feedly
59 | - Pulp
60 | - Mozilla Thunderbird
61 | - NewsBar
62 | - Reeder 3 or older
63 | - RSS Reader
64 | - An Otter RSS Reader
65 |
66 |
67 | ### Supported news reader services
68 |
69 | - Feedbin
70 | - Feedly
71 | - FeedHQ
72 | - Feed Wrangler
73 | - Inoreader
74 | - NewsBlur
75 | - The Old Reader
76 | - BazQuz Reader
77 |
78 | Custom URLs are also supported for self-hosted web services.
79 |
80 |
81 | ## Usage
82 |
83 | 
84 |
85 | 
86 |
87 | 
88 |
89 | 
90 |
91 |
92 | ## Known Issues
93 |
94 | - Some pages do not publish the alternate links for auto-discovery of their RSS feeds and the extension cannot pick up feeds without them.
95 | - When installing from the Mac App Store rarely the extension will fail to load in Safari. Quitting Safari and relaunching tends to resolve the issue.
96 |
97 | ## Privacy
98 |
99 | RSS Button for Safari does not collect or retain any data from users. Absolutely no requests to external or third party services are made from the application or extension at any time.
100 |
--------------------------------------------------------------------------------
/RSS-Button-MacOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // RSS Button for Safari
4 | //
5 | // Created by Jan Pingel on 2018-09-20.
6 | // Copyright © 2018 BitPiston Studios. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 | @IBAction func showHelpMenuItemClicked(_ sender: NSMenuItem) {
15 | NSWorkspace.shared.open(URL(string: Bundle.main.infoDictionary!["Help URL"] as! String)!)
16 | }
17 |
18 | @IBAction func showPrivacyMenuItemClicked(_ sender: NSMenuItem) {
19 | NSWorkspace.shared.open(URL(string: Bundle.main.infoDictionary!["Privacy URL"] as! String)!)
20 | }
21 |
22 | func applicationDidFinishLaunching(_ aNotification: Notification) {}
23 | func applicationWillTerminate(_ aNotification: Notification) {}
24 |
25 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
26 | return true
27 | }
28 |
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-128pt@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-128pt@1x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-128pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-128pt@2x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-16pt@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-16pt@1x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-16pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-16pt@2x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-256pt@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-256pt@1x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-256pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-256pt@2x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-32pt@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-32pt@1x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-32pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-32pt@2x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-512pt@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-512pt@1x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-512pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/AppIcon-512pt@2x.png
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "AppIcon-16pt@1x.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "AppIcon-16pt@2x.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "AppIcon-32pt@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "AppIcon-32pt@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "AppIcon-128pt@1x.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "AppIcon-128pt@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "AppIcon-256pt@1x.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "AppIcon-256pt@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "AppIcon-512pt@1x.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "AppIcon-512pt@2x.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
--------------------------------------------------------------------------------
/RSS-Button-MacOS/FeedHandlerModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeedHandlerModel.swift
3 | // RSS Button for Safari
4 | //
5 | // Created by Jan Pingel on 2018-09-28.
6 | // Copyright © 2018 BitPiston Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | @objc enum FeedHandlerType: Int, CaseIterable {
12 | case none = 0
13 | case app = 1
14 | case web = 2
15 | case custom = 4
16 | case copy = 3
17 | }
18 |
19 | @objc(FeedHandlerModel)
20 | class FeedHandlerModel: NSObject, NSCoding {
21 | let title: String
22 | let type : FeedHandlerType
23 | let url : String?
24 | let appId: String?
25 |
26 | init(title: String, type: FeedHandlerType, url: String?, appId: String?) {
27 | if (type == FeedHandlerType.app && appId == nil) ||
28 | ((type == FeedHandlerType.web || type == FeedHandlerType.custom) && url == nil) {
29 | NSLog("Error: Invalid FeedHandlerModel (\(title))")
30 | }
31 |
32 | self.title = title
33 | self.type = type
34 | self.url = url
35 | self.appId = appId
36 |
37 | super.init()
38 | }
39 |
40 | required init(coder aDecoder: NSCoder) {
41 | self.title = aDecoder.decodeObject(forKey: "title") as! String
42 | self.type = FeedHandlerType(rawValue: aDecoder.decodeInteger(forKey: "type") as Int)!
43 | self.url = aDecoder.decodeObject(forKey: "url") as? String
44 | self.appId = aDecoder.decodeObject(forKey: "appId") as? String
45 | }
46 |
47 | func encode(with aCoder: NSCoder) {
48 | aCoder.encode(self.title, forKey: "title")
49 | aCoder.encode(self.type.rawValue, forKey: "type")
50 |
51 | if let url = self.url {
52 | aCoder.encode(url, forKey: "url")
53 | }
54 | if let appId = self.appId {
55 | aCoder.encode(appId, forKey: "appId")
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/RSS-Button-MacOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | App group
6 | $(TeamIdentifierPrefix)group.com.bitpiston.rss-button
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIconFile
12 |
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(MARKETING_VERSION)
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | Extension bundle identifier
26 | $(PRODUCT_BUNDLE_IDENTIFIER).SafariExtension
27 | Help URL
28 | https://rss-extension.bitpiston.com/#contact
29 | ITSAppUsesNonExemptEncryption
30 |
31 | LSApplicationCategoryType
32 | public.app-category.news
33 | LSMinimumSystemVersion
34 | $(MACOSX_DEPLOYMENT_TARGET)
35 | NSHumanReadableCopyright
36 | Copyright © 2018-2021 BitPiston Studios
37 |
38 | Safari is a trademark of Apple Inc.
Inspired by Syndicate by Reda Lemeden.
39 | NSMainStoryboardFile
40 | Main
41 | NSPrincipalClass
42 | NSApplication
43 | Privacy URL
44 | https://rss-extension.bitpiston.com/#privacy
45 |
46 |
47 |
--------------------------------------------------------------------------------
/RSS-Button-MacOS/RSS_Button_for_Safari.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | $(TeamIdentifierPrefix)group.com.bitpiston.rss-button
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/RSS-Button-MacOS/SettingsManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsManager.swift
3 | // RSS Button for Safari
4 | //
5 | // Created by Jan Pingel on 2018-09-29.
6 | // Copyright © 2018 BitPiston Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | class SettingsManager {
13 |
14 | static let shared = SettingsManager()
15 |
16 | let sharedUserDefaults = UserDefaults(suiteName: Bundle.main.infoDictionary!["App group"] as? String)!
17 | let feedHandlerKey = "feedHandler"
18 | let defaultFeedHandlers: [FeedHandlerModel]
19 | let badgeButtonKey = "badgeButtonState"
20 | let unsupportedHandlers = [
21 | "com.apple.news", // Apple News
22 | "com.apple.mail", // Apple Mail
23 | "com.apple.safari", // Apple Safari
24 | "com.newsbar-app", // Newsbar
25 | "org.mozilla.thunderbird", // Mozilla Thunderbird
26 | "com.reederapp.rkit2.mac", // Reeder v3
27 | "com.froggyproggy.rss-reader", // RSS Reader
28 | "com.devhd.feedly.osx", // Feedly
29 | "com.acrylic.pulp", // Pulp
30 | "com.joshholtz.AnOtterRSS", // An Otter RSS Reader
31 | "com.synsion.Newsify", // Newsify
32 | ]
33 |
34 | init() {
35 | self.defaultFeedHandlers = [
36 | FeedHandlerModel(title: "None", // Previously "Default"
37 | type: FeedHandlerType.none,
38 | url: nil,
39 | appId: nil),
40 | FeedHandlerModel(title: "Copy to Clipboard",
41 | type: FeedHandlerType.copy,
42 | url: "%@",
43 | appId: nil),
44 | FeedHandlerModel(title: "Feedbin",
45 | type: FeedHandlerType.web,
46 | url: "https://feedbin.com/?subscribe=%@",
47 | appId: nil),
48 | FeedHandlerModel(title: "Feedly",
49 | type: FeedHandlerType.web,
50 | url: "https://feedly.com/i/subscription/feed/%@",
51 | appId: nil),
52 | FeedHandlerModel(title: "Feeder",
53 | type: FeedHandlerType.web,
54 | url: "https://feeder.co/settings/feeds/new?q=%@",
55 | appId: nil),
56 | FeedHandlerModel(title: "Feed HQ",
57 | type: FeedHandlerType.web,
58 | url: "https://feedhq.org/feed/add/?feed=%@",
59 | appId: nil),
60 | FeedHandlerModel(title: "Feed Wrangler",
61 | type: FeedHandlerType.web,
62 | url: "https://feedwrangler.net/feeds/bookmarklet?feed_url=%@",
63 | appId: nil),
64 | FeedHandlerModel(title: "NewsBlur",
65 | type: FeedHandlerType.web,
66 | url: "https://www.newsblur.com/?url=%@",
67 | appId: nil),
68 | FeedHandlerModel(title: "The Old Reader",
69 | type: FeedHandlerType.web,
70 | url: "https://theoldreader.com/feeds/subscribe?url=%@",
71 | appId: nil),
72 | FeedHandlerModel(title: "Inoreader",
73 | type: FeedHandlerType.web,
74 | url: "https://www.inoreader.com/?add_feed=%@",
75 | appId: nil),
76 | FeedHandlerModel(title: "BazQuz Reader",
77 | type: FeedHandlerType.web,
78 | url: "https://bazqux.com/add?url=%@",
79 | appId: nil)
80 | ]
81 |
82 | //#if DEBUG
83 | //sharedUserDefaults.removeObject(forKey: feedHandlerKey)
84 | //sharedUserDefaults.synchronize()
85 | //#endif
86 | }
87 |
88 | var feedHandler: FeedHandlerModel {
89 | get {
90 | if let data = sharedUserDefaults.value(forKey: feedHandlerKey) as? Data {
91 | return NSKeyedUnarchiver.unarchiveObject(with: data) as! FeedHandlerModel
92 | } else {
93 | return defaultFeedHandlers[0]
94 | }
95 | }
96 | set(value) {
97 | let data = NSKeyedArchiver.archivedData(withRootObject: value)
98 | sharedUserDefaults.set(data, forKey: feedHandlerKey)
99 | sharedUserDefaults.synchronize()
100 | }
101 | }
102 |
103 | var badgeButtonState: Bool {
104 | get {
105 | return sharedUserDefaults.value(forKey: badgeButtonKey) as? Bool ?? false
106 | }
107 | set(value) {
108 | sharedUserDefaults.set(value, forKey: badgeButtonKey)
109 | }
110 | }
111 |
112 | func setFeedHandler(_ feedHandler: FeedHandlerModel) -> Void {
113 | self.feedHandler = feedHandler
114 |
115 | #if DEBUG
116 | NSLog("Info: feedHandler set (\(feedHandler.title))")
117 | #endif
118 | }
119 |
120 | func getFeedHandler() -> FeedHandlerModel {
121 | return self.feedHandler
122 | }
123 |
124 | func setBadgeButtonState(_ enabled: Bool) -> Void {
125 | self.badgeButtonState = enabled
126 |
127 | #if DEBUG
128 | NSLog("Info: badgeButtonState set (\(enabled))")
129 | #endif
130 | }
131 |
132 | func getBadgeButtonState() -> Bool {
133 | return self.badgeButtonState
134 | }
135 |
136 | func isFeedHandlerSet() -> Bool {
137 | let title = self.feedHandler.title
138 | let type = self.feedHandler.type
139 | let appId = self.feedHandler.appId
140 |
141 | return type == FeedHandlerType.none ||
142 | type == FeedHandlerType.app && appId == "com.apple.news" ||
143 | type == FeedHandlerType.web && (title == "None" || title == "Default") ? false : true
144 | }
145 |
146 | func isSupportedFeedHandler() -> Bool {
147 | let type = self.feedHandler.type
148 | let appId = self.feedHandler.appId
149 |
150 | return type == FeedHandlerType.app && self.unsupportedHandlers.contains(appId!) ? false : true
151 | }
152 |
153 | @objc func launchApplication(bundleIdentifier: String = "com.bitpiston.RSSButton4Safari") -> Void {
154 | if #available(OSX 10.15, *) {
155 | guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleIdentifier) else { return }
156 | NSWorkspace.shared.openApplication(at: url,
157 | configuration: NSWorkspace.OpenConfiguration(),
158 | completionHandler: nil)
159 | } else {
160 | NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier,
161 | options: NSWorkspace.LaunchOptions.default,
162 | additionalEventParamDescriptor: nil,
163 | launchIdentifier: nil)
164 | }
165 | }
166 |
167 | @objc func noFeedHandlerConfiguredAlert(fromExtension: Bool = false) -> Void {
168 | let alert = NSAlert()
169 | alert.messageText = "No news reader configured"
170 | if fromExtension {
171 | alert.informativeText = "You must choose a news reader from within the RSS Button for Safari application to subscribe to feeds."
172 | } else {
173 | alert.informativeText = "You must choose a news reader to subscribe to feeds."
174 | }
175 | alert.alertStyle = .warning
176 | alert.addButton(withTitle: "OK")
177 | alert.runModal()
178 |
179 | NSLog("Error: No news reader configured")
180 |
181 | if fromExtension {
182 | self.launchApplication()
183 | }
184 | }
185 |
186 | @objc func noFeedHandlersAlert(fromExtension: Bool = false) -> Void {
187 | let alert = NSAlert()
188 | alert.messageText = "No news reader available"
189 | if fromExtension {
190 | alert.informativeText = "Subscribing to feeds requires a news reader with RSS support for MacOS. Please install one or if you prefer choose a web news service within the RSS Button for Safari application."
191 | } else {
192 | alert.informativeText = "Subscribing to feeds requires a news reader with RSS support for MacOS. Please install one or if you prefer choose a web based news service."
193 | }
194 | alert.alertStyle = .warning
195 | alert.addButton(withTitle: "OK")
196 | alert.runModal()
197 |
198 | NSLog("Error: No news reader avaiable")
199 |
200 | if fromExtension {
201 | self.launchApplication()
202 | }
203 | }
204 |
205 | @objc func unsupportedFeedHandlerAlert(withFeedUrl feedUrl: String?) -> Void {
206 | let appName = self.feedHandler.title
207 | let alert = NSAlert()
208 | var message = "\(appName) currently does not support opening feeds automatically. You will need to choose the copy to clipboard option in the RSS Button for Safari application and manually subscribe to feeds from within \(appName)."
209 | if feedUrl != nil {
210 | message = message + "\n\nYou can also copy and paste the URL below:\n\n\(feedUrl!)"
211 | alert.messageText = "\(appName) is unable to open the feed"
212 | } else {
213 | alert.messageText = "\(appName) does not support opening feeds"
214 | }
215 | alert.informativeText = message
216 | alert.alertStyle = .warning
217 | alert.addButton(withTitle: "OK")
218 | alert.runModal()
219 |
220 | NSLog("Error: Attempted to open a feed with \(appName) which is bugged")
221 |
222 | if feedUrl != nil {
223 | self.launchApplication()
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/RSS-Button-MacOS/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // RSS Button for Safari
4 | //
5 | // Created by Jan Pingel on 2018-09-20.
6 | // Copyright © 2018 BitPiston Studios. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SafariServices
11 |
12 | class ViewController: NSViewController, NSWindowDelegate, NSTextFieldDelegate {
13 |
14 | @IBOutlet weak var statusTextField: NSTextField!
15 | @IBOutlet weak var informationTextField: NSTextField!
16 | @IBOutlet weak var enableButton: NSButton!
17 | @IBOutlet weak var readerPopUpButton: NSPopUpButton!
18 | @IBOutlet weak var customUrlTextField: NSTextField!
19 | @IBOutlet weak var badgeButtonToggle: NSButton!
20 | @IBOutlet weak var customUrlTextFieldHeightConstraint: NSLayoutConstraint!
21 | @IBOutlet weak var customUrlTextFieldPaddingConstraint: NSLayoutConstraint!
22 |
23 | var feedHandlers = [FeedHandlerModel]()
24 | var previousCustomUrl: String?
25 | let customUrlTitle = "Custom URL"
26 |
27 | let extensionId = (Bundle.main.infoDictionary!["Extension bundle identifier"] as? String)!
28 | let settingsManager = SettingsManager.shared
29 |
30 | //static let shared = ViewController()
31 |
32 | override func viewDidLoad() {
33 | super.viewDidLoad()
34 |
35 | self.customUrlTextField.delegate = self
36 |
37 | self.checkExtensionState()
38 | self.updateFeedHandlers()
39 | self.updateSettings()
40 | }
41 |
42 | override func viewWillAppear() {
43 | super.viewWillAppear()
44 |
45 | self.checkExtensionState()
46 | }
47 |
48 | override func viewDidAppear() {
49 | super.viewDidAppear()
50 |
51 | view.window!.delegate = self
52 | view.window!.styleMask.remove(.resizable)
53 |
54 | Timer.scheduledTimer(timeInterval: 1.0,
55 | target: self,
56 | selector: #selector(self.checkExtensionState),
57 | userInfo: nil,
58 | repeats: true)
59 | }
60 |
61 | func windowShouldClose(_ sender: NSWindow) -> Bool {
62 | NSApplication.shared.terminate(self)
63 | return true
64 | }
65 |
66 | override var representedObject: Any? {
67 | didSet {
68 | // Update the view, if already loaded.
69 | }
70 | }
71 |
72 | @objc func checkExtensionState() -> Void {
73 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionId) { [weak self] (state, error) in
74 | DispatchQueue.main.async {
75 | if let status = state?.isEnabled {
76 | self?.statusTextField.textColor = status ? .systemGreen : .systemRed
77 | self?.statusTextField.stringValue = status ? "● Enabled" : "● Disabled"
78 | self?.informationTextField.stringValue = status ? "The extension is enabled. You can add the RSS Button to the Safari toolbar by right clicking and choosing Customize Toolbar." : "The extension is currently disabled. Please enable it from Safari preferences under the extensions tab."
79 | //self?.enableButton.isHidden = status
80 | } else {
81 | // Error message due to failure to install?
82 | self?.statusTextField.textColor = .systemRed
83 | self?.statusTextField.stringValue = "● Not Installed"
84 | self?.informationTextField.stringValue = "The extension is not installed. Please quit Safari, quit and move RSS Button for Safari to the trash and reinstall from the Mac App Store."
85 | }
86 | }
87 | }
88 | }
89 |
90 | func updateFeedHandlers() -> Void {
91 | self.feedHandlers = self.settingsManager.defaultFeedHandlers
92 |
93 | let defaultFeedHandler = LSCopyDefaultHandlerForURLScheme("feed" as CFString)?.takeRetainedValue()
94 | let feedHandler = self.settingsManager.getFeedHandler()
95 |
96 | #if DEBUG
97 | NSLog("Info: default feed handler from launch services (\(String(describing: defaultFeedHandler)))")
98 | NSLog("Info: retrieved feed handler from preferences (\(String(describing: feedHandler.title)), \(String(describing: feedHandler.appId)))")
99 | #endif
100 |
101 | // Get all the applications registered as handlers for feed URLs
102 | if let foundFeedHandlers = LSCopyAllHandlersForURLScheme("feed" as CFString)?.takeRetainedValue() {
103 | let identifiers = foundFeedHandlers as! [String]
104 |
105 | for (index, id) in identifiers.enumerated() {
106 | if id == "com.apple.news" { continue }
107 |
108 | // Make sure the application exists as old cruft can remain in launch services
109 | guard let path = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: id) else {
110 | #if DEBUG
111 | NSLog("Info: bad feed handler with no path detected and skipped (\(id))")
112 | #endif
113 | continue
114 | }
115 |
116 | if FileManager.default.fileExists(atPath: path) {
117 | let name = FileManager.default.displayName(atPath: path)
118 |
119 | self.feedHandlers.insert(FeedHandlerModel(title: name,
120 | type: FeedHandlerType.app,
121 | url: "feed:%@",
122 | appId: id), at: 0 + index)
123 | #if DEBUG
124 | NSLog("Info: found valid feed handler (\(id))")
125 | #endif
126 | }
127 |
128 | #if DEBUG
129 | if !FileManager.default.fileExists(atPath: path) {
130 | NSLog("Info: bad feed handler that does not exist detected and skipped (\(id))")
131 | }
132 | #endif
133 | }
134 | }
135 |
136 | // Custom URLs
137 | if feedHandler.type == FeedHandlerType.custom {
138 | self.feedHandlers.append(feedHandler)
139 | } else {
140 | self.feedHandlers.append(FeedHandlerModel(title: self.customUrlTitle,
141 | type: FeedHandlerType.custom,
142 | url: "https://example.com/?url=%@",
143 | appId: nil))
144 | }
145 |
146 | // Create the menu of feed handlers
147 | let readerMenu = NSMenu()
148 | readerMenu.addItem(withTitle: "None Selected", action: nil, keyEquivalent: "")
149 |
150 | for type in FeedHandlerType.allCases {
151 | readerMenu.addItem(NSMenuItem.separator())
152 |
153 | for handler in self.feedHandlers.filter({$0.type == type}) {
154 | if handler.type == FeedHandlerType.none { continue }
155 |
156 | readerMenu.addItem(withTitle: handler.title, action: nil, keyEquivalent: "")
157 | }
158 | }
159 |
160 | self.readerPopUpButton.menu = readerMenu
161 |
162 | // Set the feed handler if configured or alert
163 | if self.settingsManager.isFeedHandlerSet() {
164 | if (self.feedHandlers.first(where: {$0.title == feedHandler.title}) != nil) {
165 | self.readerPopUpButton.selectItem(withTitle: feedHandler.title)
166 | } else {
167 | self.settingsManager.noFeedHandlerConfiguredAlert()
168 | }
169 | } else {
170 | if self.feedHandlers.filter({$0.type == FeedHandlerType.app}).count > 0 {
171 | self.settingsManager.noFeedHandlerConfiguredAlert()
172 | } else {
173 | self.settingsManager.noFeedHandlersAlert()
174 | }
175 | }
176 | }
177 |
178 | func updateSettings() -> Void {
179 | let feedHandler = self.settingsManager.getFeedHandler()
180 |
181 | if feedHandler.type == FeedHandlerType.custom {
182 | if let url = feedHandler.url {
183 | self.customUrlTextField.stringValue = url
184 | }
185 |
186 | self.setCustomUrlFieldVisibility(true)
187 | } else {
188 | self.setCustomUrlFieldVisibility(false)
189 | }
190 |
191 | if self.settingsManager.getBadgeButtonState() {
192 | self.badgeButtonToggle.state = NSControl.StateValue.on
193 | } else {
194 | self.badgeButtonToggle.state = NSControl.StateValue.off
195 | }
196 | }
197 |
198 | func controlTextDidChange(_ obj: Notification) {
199 | guard let object = obj.object as? NSTextField else { return }
200 | let value = object.stringValue
201 |
202 | if value.contains("%@") {
203 | self.settingsManager.setFeedHandler(FeedHandlerModel(title: self.customUrlTitle,
204 | type: FeedHandlerType.custom,
205 | url: value,
206 | appId: nil))
207 | }
208 | }
209 |
210 | func setCustomUrlFieldVisibility(_ visible: Bool, animated: Bool = true) -> Void {
211 | if visible {
212 | self.customUrlTextField.isEnabled = true
213 | self.customUrlTextField.isHidden = false
214 | NSAnimationContext.runAnimationGroup({ (context) in
215 | context.allowsImplicitAnimation = true
216 | context.duration = 0.2
217 | self.customUrlTextFieldHeightConstraint.animator().constant = CGFloat(21)
218 | self.customUrlTextFieldPaddingConstraint.animator().constant = CGFloat(16)
219 | }, completionHandler: { () -> Void in
220 | NSAnimationContext.runAnimationGroup({ (context) in
221 | context.allowsImplicitAnimation = true
222 | context.duration = 0.1
223 | self.customUrlTextField.animator().alphaValue = 1
224 | })
225 | })
226 | } else {
227 | NSAnimationContext.runAnimationGroup({ (context) in
228 | context.allowsImplicitAnimation = true
229 | context.duration = 0.1
230 | self.customUrlTextField.animator().alphaValue = 0
231 | }, completionHandler: { () -> Void in
232 | NSAnimationContext.runAnimationGroup({ (context) in
233 | context.allowsImplicitAnimation = true
234 | context.duration = 0.2
235 | self.customUrlTextFieldHeightConstraint.animator().constant = 0
236 | self.customUrlTextFieldPaddingConstraint.animator().constant = 0
237 | }, completionHandler: { () -> Void in
238 | self.customUrlTextField.isEnabled = false
239 | self.customUrlTextField.isHidden = true
240 | })
241 | })
242 | }
243 | }
244 |
245 | @IBAction func readerPopUpButtonSelected(_ sender: NSMenuItem) -> Void {
246 | // Set the handler and warn if unsupported
247 | if let feedHandler = self.feedHandlers.first(where: {$0.title == sender.title}) {
248 | // Toggle and populate the text field for custom URLs
249 | if feedHandler.type == FeedHandlerType.custom {
250 | self.customUrlTextField.stringValue = self.previousCustomUrl ?? feedHandler.url!
251 | self.setCustomUrlFieldVisibility(true)
252 | //self.customUrlTextField.window?.makeFirstResponder(self.customUrlTextField)
253 | } else {
254 | let previousFeedHandler = self.settingsManager.getFeedHandler()
255 |
256 | if previousFeedHandler.type == FeedHandlerType.custom {
257 | self.previousCustomUrl = self.customUrlTextField.stringValue
258 | }
259 |
260 | self.setCustomUrlFieldVisibility(false)
261 | }
262 |
263 | self.settingsManager.setFeedHandler(feedHandler)
264 |
265 | if !self.settingsManager.isSupportedFeedHandler() {
266 | self.settingsManager.unsupportedFeedHandlerAlert(withFeedUrl: nil)
267 | }
268 | } else {
269 | self.settingsManager.setFeedHandler(self.settingsManager.defaultFeedHandlers[0])
270 | }
271 | }
272 |
273 | @IBAction func enableButtonClicked(_ sender: NSButton) -> Void {
274 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionId)
275 | }
276 |
277 | @IBAction func badgeButtonToggleClicked(_ sender: NSButton) -> Void {
278 | let value = sender.state.rawValue == 0 ? false : true
279 |
280 | self.settingsManager.setBadgeButtonState(value)
281 | }
282 | }
283 |
284 | extension NSTextField {
285 |
286 | func controlTextDidChange(obj: NSNotification) {}
287 | }
288 |
--------------------------------------------------------------------------------
/RSS-Button-Safari-Extension/Base.lproj/SafariExtensionViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/RSS-Button-Safari-Extension/FeedModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeedModel.swift
3 | // RSS Button
4 | //
5 | // Created by Jan Pingel on 2018-09-23.
6 | // Copyright © 2018 BitPiston Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | @objc(FeedModel)
13 | class FeedModel: NSObject {
14 | let title: String
15 | let type : String
16 | let url : String
17 |
18 | init(title: String, type: String, url: String) {
19 | self.title = title
20 | self.type = type
21 | self.url = url
22 |
23 | super.init()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/RSS-Button-Safari-Extension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | App group
6 | $(TeamIdentifierPrefix)group.com.bitpiston.rss-button
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | RSS Button
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | XPC!
21 | CFBundleShortVersionString
22 | $(MARKETING_VERSION)
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | ITSAppUsesNonExemptEncryption
26 |
27 | LSApplicationCategoryType
28 | public.app-category.news
29 | LSMinimumSystemVersion
30 | $(MACOSX_DEPLOYMENT_TARGET)
31 | NSExtension
32 |
33 | NSExtensionMainNibFile
34 | SafariExtensionViewController
35 | NSExtensionPointIdentifier
36 | com.apple.Safari.extension
37 | NSExtensionPrincipalClass
38 | $(PRODUCT_MODULE_NAME).SafariExtensionHandler
39 | SFSafariContentScript
40 |
41 |
42 | Script
43 | script.js
44 |
45 |
46 | SFSafariToolbarItem
47 |
48 | Action
49 | Popover
50 | Identifier
51 | menuButton
52 | Image
53 | ToolbarItemIcon.pdf
54 | Label
55 | RSS Feeds
56 |
57 | SFSafariWebsiteAccess
58 |
59 | Allowed Domains
60 |
61 | *.*
62 |
63 | Level
64 | All
65 |
66 |
67 | NSHumanReadableCopyright
68 | Copyright © 2018-2021 BitPiston Studios
69 |
70 | Safari is a trademark of Apple Inc.
Inspired by Syndicate by Reda Lemeden.
71 | NSHumanReadableDescription
72 | A button in the Safari toolbar for discovering and subscribing to RSS feeds
73 |
74 |
75 |
--------------------------------------------------------------------------------
/RSS-Button-Safari-Extension/RSS_Button.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | $(TeamIdentifierPrefix)group.com.bitpiston.rss-button
10 |
11 | com.apple.security.cs.allow-jit
12 |
13 | com.apple.security.files.user-selected.read-only
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/RSS-Button-Safari-Extension/SafariExtensionHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafariExtensionHandler.swift
3 | // RSS Button
4 | //
5 | // Created by Jan Pingel on 2018-09-20.
6 | // Copyright © 2018 BitPiston Studios. All rights reserved.
7 | //
8 |
9 | import SafariServices
10 |
11 | class SafariExtensionHandler: SFSafariExtensionHandler {
12 | typealias FeedDictionary = [String: Any]
13 |
14 | let stateManager = SafariExtensionStateManager.shared
15 | let viewController = SafariExtensionViewController.shared
16 | let settingsManager = SettingsManager.shared
17 |
18 | override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String: Any]?) {
19 | page.getPropertiesWithCompletionHandler { properties in
20 | #if DEBUG
21 | NSLog("Info: The extension received a message (\(messageName)) from a script injected into (\(String(describing: properties?.url))) with userInfo (\(userInfo ?? [:]))")
22 | #endif
23 |
24 | switch messageName {
25 | case "extractedFeeds":
26 | guard let url: URL = properties?.url else {
27 | #if DEBUG
28 | NSLog("Info: Failed to get valid url from page properties in \(page.description) during extractedFeeds message")
29 | #endif
30 | return
31 | }
32 |
33 | guard userInfo?["feeds"] != nil else {
34 | #if DEBUG
35 | NSLog("Info: userInfo feed data nil \(page.description) during extractedFeeds message")
36 | #endif
37 | return
38 | }
39 |
40 | let feeds = self.decodeJSONFeeds(data: userInfo!["feeds"] as? [FeedDictionary])
41 |
42 | if !feeds.isEmpty {
43 | self.stateManager.setFeeds(url: url, feeds: feeds)
44 | SFSafariApplication.setToolbarItemsNeedUpdate()
45 | }
46 |
47 | default:
48 | NSLog("Error: Unhandled message received in \(page.description)")
49 | }
50 | }
51 | }
52 |
53 | override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) {
54 | window.getActiveTab { tab in
55 | guard let tab = tab else {
56 | #if DEBUG
57 | NSLog("Info: Failed to get active tab \(window.description)")
58 | #endif
59 | validationHandler(false, "")
60 | return
61 | }
62 |
63 | tab.getActivePage(completionHandler: { page in
64 | guard let page = page else {
65 | #if DEBUG
66 | NSLog("Info: Failed to get active page \(tab.description)")
67 | #endif
68 | validationHandler(false, "")
69 | return
70 | }
71 |
72 | page.getPropertiesWithCompletionHandler { properties in
73 | guard let properties = properties else {
74 | #if DEBUG
75 | NSLog("Info: Failed to get page properties in \(page.description)")
76 | #endif
77 | validationHandler(false, "")
78 | return
79 | }
80 |
81 | guard let url = properties.url,
82 | url.scheme == "http" || url.scheme == "https" else {
83 | #if DEBUG
84 | NSLog("Info: Failed to get valid url from page properties in \(page.description)")
85 | #endif
86 | validationHandler(false, "")
87 | return
88 | }
89 |
90 | let feedCount = self.stateManager.countFeeds(url: url)
91 | let feedsFound = feedCount > 0 ? true : false
92 | let badgeText = self.settingsManager.getBadgeButtonState() && feedsFound ? String(feedCount) : ""
93 |
94 | #if DEBUG
95 | NSLog("Info: validateToolbarItem (\(url)) with feedsFound (\(feedsFound))")
96 | #endif
97 |
98 | validationHandler(feedsFound, badgeText)
99 | }
100 | })
101 | }
102 | }
103 |
104 | override func popoverViewController() -> SFSafariExtensionViewController {
105 | return viewController
106 | }
107 |
108 | override func popoverWillShow(in window: SFSafariWindow) {
109 | window.getActiveTab { tab in
110 | guard let tab = tab else {
111 | #if DEBUG
112 | NSLog("Info: Failed to get active tab \(window.description)")
113 | #endif
114 | return
115 | }
116 |
117 | tab.getActivePage(completionHandler: { page in
118 | guard let page = page else {
119 | #if DEBUG
120 | NSLog("Info: Failed to get active page \(tab.description)")
121 | #endif
122 | return
123 | }
124 |
125 | page.getPropertiesWithCompletionHandler { properties in
126 | guard let properties = properties else {
127 | #if DEBUG
128 | NSLog("Info: Failed to get page properties in \(page.description)")
129 | #endif
130 | return
131 | }
132 |
133 | guard let url = properties.url,
134 | url.scheme == "http" || url.scheme == "https" else {
135 | #if DEBUG
136 | NSLog("Info: Failed to get valid url from page properties in \(page.description)")
137 | #endif
138 | return
139 | }
140 |
141 | let feeds = self.stateManager.getFeeds(url: url)
142 |
143 | #if DEBUG
144 | NSLog("Info: popoverWillShow (\(url)) with \(feeds.count) feeds (\(feeds))")
145 | #endif
146 |
147 | self.viewController.updateFeeds(with: feeds)
148 | }
149 | })
150 | }
151 | }
152 |
153 | func decodeJSONFeeds(data: [FeedDictionary]?) -> [FeedModel] {
154 | var feeds = [FeedModel]()
155 |
156 | if data != nil {
157 | for (index, values) in data!.enumerated() {
158 | if values["title"] == nil || values["type"] == nil || values["url"] == nil { continue }
159 |
160 | let feed = FeedModel(title: values["title"] as! String,
161 | type : values["type"] as! String,
162 | url : values["url"] as! String)
163 |
164 | feeds.insert(feed, at: index)
165 | }
166 | }
167 |
168 | return feeds
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/RSS-Button-Safari-Extension/SafariExtensionStateManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafariExtensionStateManager.swift
3 | // RSS Button
4 | //
5 | // Created by Jan Pingel on 2018-09-20.
6 | // Copyright © 2018 BitPiston Studios. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SafariServices
11 |
12 | class SafariExtensionStateManager {
13 | private let queue = DispatchQueue(label: "com.bitpiston.RSSButton4Safari.feedStore",
14 | attributes: .concurrent)
15 |
16 | static let shared = SafariExtensionStateManager()
17 |
18 | private var feeds: [URL: [FeedModel]] = [:]
19 |
20 | func setFeeds(url: URL, feeds: [FeedModel]) -> Void {
21 | self.queue.async(flags: .barrier) {
22 | self.feeds[url] = feeds
23 | }
24 | }
25 |
26 | func getFeeds(url: URL) -> [FeedModel] {
27 | var result: [FeedModel]?
28 |
29 | self.queue.sync {
30 | result = self.feeds[url]
31 | }
32 |
33 | return result ?? [FeedModel]()
34 | }
35 |
36 | func hasFeeds(url: URL) -> Bool {
37 | var result: Bool?
38 |
39 | self.queue.sync {
40 | result = self.feeds[url]?.isEmpty
41 | }
42 |
43 | return result ?? true ? false : true
44 | }
45 |
46 | func countFeeds(url: URL) -> Int {
47 | var result: Int?
48 |
49 | self.queue.sync {
50 | result = self.feeds[url]?.count
51 | }
52 |
53 | return result ?? 0
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/RSS-Button-Safari-Extension/SafariExtensionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafariExtensionViewController.swift
3 | // RSS Button
4 | //
5 | // Created by Jan Pingel on 2018-09-20.
6 | // Copyright © 2018 BitPiston Studios. All rights reserved.
7 | //
8 |
9 | import SafariServices
10 | import Cocoa
11 |
12 | class SafariExtensionViewController: SFSafariExtensionViewController {
13 |
14 | @IBOutlet weak var textField: NSTextField!
15 | @IBOutlet weak var tableView: NSTableView!
16 |
17 | var feeds = [FeedModel]()
18 | var showFeedType: Bool = false
19 |
20 | let settingsManager = SettingsManager.shared
21 |
22 | static let shared = SafariExtensionViewController()
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 |
27 | tableView.delegate = self
28 | tableView.dataSource = self
29 |
30 | preferredContentSize = CGSize(width: 420, height: 101)
31 |
32 | let menu = NSMenu()
33 | menu.addItem(NSMenuItem(title: "Subscribe to Feed", action: #selector(subscribeMenuClicked(_:)), keyEquivalent: ""))
34 | menu.addItem(NSMenuItem.separator())
35 | menu.addItem(NSMenuItem(title: "Copy Feed Address", action: #selector(copyMenuClicked(_:)), keyEquivalent: ""))
36 | menu.addItem(NSMenuItem.separator())
37 | menu.addItem(NSMenuItem(title: "Open Feed in New Tab", action: #selector(openTabMenuClicked(_:)), keyEquivalent: ""))
38 | menu.addItem(NSMenuItem(title: "Open Feed in New Window", action: #selector(openWindowMenuClicked(_:)), keyEquivalent: ""))
39 | tableView.menu = menu
40 | }
41 |
42 | @objc func subscribeMenuClicked(_ sender: NSMenuItem) {
43 | guard tableView.clickedRow >= 0 else { return }
44 |
45 | self.subscribeToFeed(feeds[tableView.clickedRow])
46 | }
47 |
48 | @objc func copyMenuClicked(_ sender: NSMenuItem) {
49 | guard tableView.clickedRow >= 0 else { return }
50 |
51 | if let url = URL(string: feeds[tableView.clickedRow].url) {
52 | self.copyToClipboard(url.absoluteString)
53 | }
54 |
55 | self.dismissPopover()
56 | }
57 |
58 | @objc func openTabMenuClicked(_ sender: NSMenuItem) {
59 | guard tableView.clickedRow >= 0 else { return }
60 |
61 | if let url = URL(string: feeds[tableView.clickedRow].url) {
62 | SFSafariApplication.getActiveWindow { (window) in
63 | window?.openTab(with: url, makeActiveIfPossible: true, completionHandler: { (tab) in
64 | self.dismissPopover()
65 | })
66 | }
67 | }
68 | }
69 |
70 | @objc func openWindowMenuClicked(_ sender: NSMenuItem) {
71 | guard tableView.clickedRow >= 0 else { return }
72 |
73 | if let url = URL(string: feeds[tableView.clickedRow].url) {
74 | SFSafariApplication.openWindow(with: url, completionHandler: { (tab) in
75 | self.dismissPopover()
76 | })
77 | }
78 | }
79 |
80 | func updatePreferredContentSize() -> Void {
81 | let width = CGFloat(420)
82 | let height = CGFloat((self.feeds.count * 55) + 46)
83 |
84 | preferredContentSize = CGSize(width: width, height: height)
85 | tableView.sizeToFit()
86 | }
87 |
88 | func updateFeeds(with feeds: [FeedModel]) -> Void {
89 | let rssFeed: Bool = feeds.contains(where: { $0.type == "RSS" })
90 | let atomFeed: Bool = feeds.contains(where: { $0.type == "Atom" })
91 | let jsonFeed: Bool = feeds.contains(where: { $0.type == "JSON" })
92 | let unknownFeed: Bool = feeds.contains(where: { $0.type == "Unknown" })
93 |
94 | DispatchQueue.main.async { [weak self] in
95 | guard let self = self else { return }
96 | self.feeds = feeds
97 |
98 | if (rssFeed == true && atomFeed == true) || (rssFeed == true && jsonFeed == true)
99 | || (atomFeed == true && jsonFeed == true) || unknownFeed == true {
100 | self.showFeedType = true
101 | } else {
102 | self.showFeedType = false
103 | }
104 |
105 | self.updatePreferredContentSize()
106 | self.tableView.reloadData()
107 | }
108 | }
109 |
110 | @objc func subscribeButtonClicked(_ sender: NSButton) {
111 | self.subscribeToFeed(feeds[tableView.row(for: sender)])
112 | }
113 |
114 | func subscribeToFeed(_ feed: FeedModel) -> Void {
115 | let feedHandler = settingsManager.getFeedHandler()
116 |
117 | #if DEBUG
118 | NSLog("Info: Subscribe button clicked for feed (\(feed.url)) with feed handler (\(String(describing: feedHandler.appId)))")
119 | #endif
120 |
121 | // Warn of known unsupported or bugged readers
122 | if !self.settingsManager.isFeedHandlerSet() {
123 | self.settingsManager.noFeedHandlerConfiguredAlert(fromExtension: true)
124 | } else if !self.settingsManager.isSupportedFeedHandler() {
125 | self.settingsManager.unsupportedFeedHandlerAlert(withFeedUrl: feed.url)
126 | } else if let url = URL(string: String(format: feedHandler.url!, feed.url)) {
127 | if feedHandler.type == FeedHandlerType.copy {
128 | self.copyToClipboard(url.absoluteString)
129 | #if DEBUG
130 | NSLog("Info: Copying feed (\(url)) to clipboard")
131 | #endif
132 | } else if feedHandler.type == FeedHandlerType.app {
133 | if #available(OSX 10.15, *) {
134 | guard let appUrl = NSWorkspace.shared.urlForApplication(withBundleIdentifier: feedHandler.appId!) else { return }
135 | NSWorkspace.shared.open([url], withApplicationAt: appUrl,
136 | configuration: NSWorkspace.OpenConfiguration(),
137 | completionHandler: nil)
138 | } else {
139 | NSWorkspace.shared.open([url], withAppBundleIdentifier: feedHandler.appId,
140 | options: NSWorkspace.LaunchOptions.default,
141 | additionalEventParamDescriptor: nil,
142 | launchIdentifiers: nil)
143 | }
144 | #if DEBUG
145 | NSLog("Info: Opening feed (\(url)) in \(feedHandler.title)")
146 | #endif
147 | } else {
148 | if url.scheme == "https" || url.scheme == "http" {
149 | SFSafariApplication.getActiveWindow { (window) in
150 | window?.openTab(with: url, makeActiveIfPossible: true, completionHandler: { (tab) in
151 | self.dismissPopover()
152 | })
153 | }
154 | #if DEBUG
155 | NSLog("Info: Opening feed (\(url)) in Safari")
156 | #endif
157 | } else {
158 | NSWorkspace.shared.open(url)
159 | #if DEBUG
160 | NSLog("Info: Opening feed (\(url)) in default application")
161 | #endif
162 | }
163 | }
164 | } else {
165 | NSLog("Error: Invalid URL for feed")
166 | }
167 |
168 | self.dismissPopover()
169 | }
170 |
171 | func copyToClipboard(_ string: String) -> Void {
172 | let pasteBoard = NSPasteboard.general
173 | pasteBoard.clearContents()
174 | pasteBoard.setString(string, forType: .string)
175 | }
176 | }
177 |
178 | extension SafariExtensionViewController: NSTableViewDataSource, NSTableViewDelegate {
179 |
180 | func numberOfRows(in tableView: NSTableView) -> Int {
181 | return self.feeds.count
182 | }
183 |
184 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
185 | guard self.feeds.count > row else {
186 | return nil
187 | }
188 |
189 | let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "cellIdentifier")
190 |
191 | if let cellView = tableView.makeView(withIdentifier: cellIdentifier, owner: self) as? FeedTableCellView {
192 | cellView.titleTextField.stringValue = {
193 | if showFeedType == true {
194 | return "(\(self.feeds[row].type)) " + self.feeds[row].title
195 | } else {
196 | return self.feeds[row].title
197 | }
198 | }()
199 | cellView.detailsTextField.stringValue = self.feeds[row].url
200 | cellView.subscribeButton.target = self
201 | cellView.subscribeButton.action = #selector(self.subscribeButtonClicked(_:))
202 |
203 | cellView.layoutSubtreeIfNeeded()
204 |
205 | return cellView
206 | } else {
207 | return nil
208 | }
209 | }
210 |
211 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
212 | return false
213 | }
214 |
215 | }
216 |
217 | class FeedTableCellView: NSTableCellView {
218 |
219 | @IBOutlet weak var titleTextField: NSTextField!
220 | @IBOutlet weak var detailsTextField: NSTextField!
221 | @IBOutlet weak var subscribeButton: NSButton!
222 |
223 | }
224 |
--------------------------------------------------------------------------------
/RSS-Button-Safari-Extension/ToolbarItemIcon.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/RSS-Button-Safari-Extension/ToolbarItemIcon.pdf
--------------------------------------------------------------------------------
/RSS-Button-Safari-Extension/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Based on Syndicate: https://github.com/kaishin/syndicate/
3 | *
4 | * Copyright (c) 2015 Reda Lemeden
5 | * Copyright (c) 2018 BitPiston Studios
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | 'use strict';
12 |
13 | var feeds = [],
14 | parsed = false,
15 | href = window.location.href,
16 | timer = 0;
17 |
18 | document.addEventListener("DOMContentLoaded", function(event) {
19 | if (isValidPage()) {
20 | extractFeeds();
21 |
22 | // To handle document / url changes requires polling
23 | startPolling();
24 |
25 | // Don't poll if the tab is inactive
26 | document.addEventListener('visibilitychange', function(event) {
27 | if (document.hidden && timer != 0) {
28 | stopPolling();
29 | console.info('RSS Button for Safari stopped polling for changes.');
30 | } else {
31 | startPolling();
32 | console.info('RSS Button for Safari started polling for changes.');
33 | }
34 | });
35 | }
36 | });
37 |
38 | function isValidPage() {
39 | return (window.top === window &&
40 | typeof safari != "undefined" &&
41 | (document.domain !== "undefined" || document.location != null) &&
42 | window.location.href !== "favorites://");
43 | }
44 |
45 | function pollForChanges() {
46 | if (parsed === true && href != window.location.href) {
47 | stopPolling();
48 |
49 | href = window.location.href;
50 | parsed = false;
51 |
52 | extractFeeds();
53 |
54 | startPolling();
55 | } else {
56 | startPolling();
57 | }
58 | }
59 |
60 | function startPolling(seconds = 1) {
61 | timer = setTimeout(pollForChanges, seconds * 1000);
62 | }
63 |
64 | function stopPolling() {
65 | if (timer != 0) {
66 | clearTimeout(timer);
67 | timer = 0;
68 | }
69 | }
70 |
71 | function extractFeeds(setParsed = true) {
72 | if (parsed === true) { return };
73 |
74 | if (!feeds.length > 0) {
75 | var headLinks = document.querySelectorAll("link[rel='alternate']");
76 | // this should be "head > link[rel='alternate']" but many sites including slashdot have it in the body
77 |
78 | for (var i = 0; i < headLinks.length; i++) {
79 | var link = headLinks[i];
80 |
81 | if (link.attributes.getNamedItem("rel") !== null &&
82 | link.attributes.getNamedItem("rel").value == "alternate" &&
83 | link.attributes.getNamedItem("type") !== null &&
84 | link.attributes.getNamedItem("href") !== null) {
85 | var type = link.attributes.getNamedItem("type").value;
86 |
87 | if (type == "application/rss+xml" ||
88 | type == "application/atom+xml" ||
89 | type == "application/rdf+xml" ||
90 | type == "application/xml" ||
91 | type == "text/xml" ||
92 | type == "application/feed+json" ||
93 | type == "application/json") {
94 | var href = link.attributes.getNamedItem("href").value,
95 | type = typeFromString(type),
96 | title;
97 |
98 | if (link.attributes.getNamedItem("title") !== null) {
99 | title = link.attributes.getNamedItem("title").value;
100 | }
101 |
102 | if (!title) {
103 | title = titleFromType(type);
104 | }
105 |
106 | if (href) {
107 | try {
108 | feeds.push({url: getUrl(href), title: title, type: type});
109 | } catch (error) {
110 | // Invalid URL or base URI when constructing URL() in getUrl()
111 | continue;
112 | }
113 | }
114 | }
115 | }
116 | }
117 | }
118 |
119 | if (setParsed === true) {
120 | parsed = true;
121 | }
122 |
123 | if (feeds.length > 0) {
124 | safari.extension.dispatchMessage("extractedFeeds", {feeds: feeds});
125 | console.info('RSS Button for Safari detected ' + feeds.length + ' feed(s).');
126 | }
127 | }
128 |
129 | function typeFromString(string) {
130 | var type,
131 | types = {
132 | "rss" : "RSS",
133 | "atom": "Atom",
134 | "json": "JSON",
135 | "rdf" : "RDF",
136 | };
137 |
138 | for (var key in types) {
139 | if (string.indexOf(key) != -1) {
140 | type = types[key];
141 | }
142 | }
143 |
144 | if (!type) {
145 | type = "Unknown";
146 | }
147 |
148 | return type;
149 | }
150 |
151 | function titleFromType(type) {
152 | var title,
153 | types = {
154 | "RSS" : "RSS Feed",
155 | "Atom": "Atom Feed",
156 | "JSON": "JSON Feed",
157 | };
158 |
159 | for (var key in types) {
160 | if (type.indexOf(key) != -1) {
161 | title = types[key];
162 | }
163 | }
164 |
165 | if (!title) {
166 | title = "Unknown Feed";
167 | }
168 |
169 | return title;
170 | }
171 |
172 | function getUrl(href) {
173 | var base = document.baseURI;
174 | var url = new URL(href, base);
175 |
176 | return url.href;
177 | }
178 |
--------------------------------------------------------------------------------
/RSS-Button-for-Safari.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | E4643371216764E5000B213E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E4BC96682154452900E65EA5 /* Assets.xcassets */; };
11 | E4BC96652154452800E65EA5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BC96642154452800E65EA5 /* AppDelegate.swift */; };
12 | E4BC96672154452800E65EA5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BC96662154452800E65EA5 /* ViewController.swift */; };
13 | E4BC96692154452900E65EA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E4BC96682154452900E65EA5 /* Assets.xcassets */; };
14 | E4BC966C2154452900E65EA5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E4BC966A2154452900E65EA5 /* Main.storyboard */; };
15 | E4BC96972154455500E65EA5 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4BC96962154455500E65EA5 /* Cocoa.framework */; };
16 | E4BC969A2154455600E65EA5 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BC96992154455600E65EA5 /* SafariExtensionHandler.swift */; };
17 | E4BC969C2154455600E65EA5 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BC969B2154455600E65EA5 /* SafariExtensionViewController.swift */; };
18 | E4BC96A22154455600E65EA5 /* script.js in Resources */ = {isa = PBXBuildFile; fileRef = E4BC96A12154455600E65EA5 /* script.js */; };
19 | E4BC96A82154455600E65EA5 /* RSS Button.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E4BC96942154455500E65EA5 /* RSS Button.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
20 | E4BC96BD21547DF500E65EA5 /* SafariExtensionStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BC96BC21547DF500E65EA5 /* SafariExtensionStateManager.swift */; };
21 | E4BC96BF2154CB2700E65EA5 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = E4BC96BE2154CB2700E65EA5 /* ToolbarItemIcon.pdf */; };
22 | E4BC96CB2158A5AA00E65EA5 /* FeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BC96C92158A2EB00E65EA5 /* FeedModel.swift */; };
23 | E4BC96D02158A71400E65EA5 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E4BC96CE2158A71400E65EA5 /* SafariExtensionViewController.xib */; };
24 | E4C55BE4215EF78D00BF9426 /* FeedHandlerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C55BE3215EF78D00BF9426 /* FeedHandlerModel.swift */; };
25 | E4C55BE62160730100BF9426 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C55BE52160730100BF9426 /* SettingsManager.swift */; };
26 | E4C55BE7216074D400BF9426 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C55BE52160730100BF9426 /* SettingsManager.swift */; };
27 | E4C55BE8216074DC00BF9426 /* FeedHandlerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C55BE3215EF78D00BF9426 /* FeedHandlerModel.swift */; };
28 | E4C55BE92160A10A00BF9426 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4BC96962154455500E65EA5 /* Cocoa.framework */; };
29 | E4C55BF0216432EB00BF9426 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4C55BEF216432EB00BF9426 /* SafariServices.framework */; };
30 | E4C55BF12164331000BF9426 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4C55BEF216432EB00BF9426 /* SafariServices.framework */; };
31 | E4C55BF32164333200BF9426 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4C55BF22164333200BF9426 /* Foundation.framework */; };
32 | E4C55BF42164333F00BF9426 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4C55BF22164333200BF9426 /* Foundation.framework */; };
33 | /* End PBXBuildFile section */
34 |
35 | /* Begin PBXContainerItemProxy section */
36 | E4BC96742154452900E65EA5 /* PBXContainerItemProxy */ = {
37 | isa = PBXContainerItemProxy;
38 | containerPortal = E4BC96592154452800E65EA5 /* Project object */;
39 | proxyType = 1;
40 | remoteGlobalIDString = E4BC96602154452800E65EA5;
41 | remoteInfo = "RSS Button for Safari";
42 | };
43 | E4BC967F2154452900E65EA5 /* PBXContainerItemProxy */ = {
44 | isa = PBXContainerItemProxy;
45 | containerPortal = E4BC96592154452800E65EA5 /* Project object */;
46 | proxyType = 1;
47 | remoteGlobalIDString = E4BC96602154452800E65EA5;
48 | remoteInfo = "RSS Button for Safari";
49 | };
50 | E4BC96A62154455600E65EA5 /* PBXContainerItemProxy */ = {
51 | isa = PBXContainerItemProxy;
52 | containerPortal = E4BC96592154452800E65EA5 /* Project object */;
53 | proxyType = 1;
54 | remoteGlobalIDString = E4BC96932154455500E65EA5;
55 | remoteInfo = "RSS Button";
56 | };
57 | /* End PBXContainerItemProxy section */
58 |
59 | /* Begin PBXCopyFilesBuildPhase section */
60 | E4BC96AC2154455600E65EA5 /* Embed App Extensions */ = {
61 | isa = PBXCopyFilesBuildPhase;
62 | buildActionMask = 2147483647;
63 | dstPath = "";
64 | dstSubfolderSpec = 13;
65 | files = (
66 | E4BC96A82154455600E65EA5 /* RSS Button.appex in Embed App Extensions */,
67 | );
68 | name = "Embed App Extensions";
69 | runOnlyForDeploymentPostprocessing = 0;
70 | };
71 | /* End PBXCopyFilesBuildPhase section */
72 |
73 | /* Begin PBXFileReference section */
74 | E4BC96612154452800E65EA5 /* RSS Button for Safari.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "RSS Button for Safari.app"; sourceTree = BUILT_PRODUCTS_DIR; };
75 | E4BC96642154452800E65EA5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
76 | E4BC96662154452800E65EA5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
77 | E4BC96682154452900E65EA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
78 | E4BC966B2154452900E65EA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
79 | E4BC966D2154452900E65EA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
80 | E4BC966E2154452900E65EA5 /* RSS_Button_for_Safari.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RSS_Button_for_Safari.entitlements; sourceTree = ""; };
81 | E4BC96732154452900E65EA5 /* RSS-Button-for-SafariTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RSS-Button-for-SafariTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
82 | E4BC967E2154452900E65EA5 /* RSS-Button-for-SafariUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RSS-Button-for-SafariUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
83 | E4BC96942154455500E65EA5 /* RSS Button.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "RSS Button.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
84 | E4BC96962154455500E65EA5 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
85 | E4BC96992154455600E65EA5 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = ""; };
86 | E4BC969B2154455600E65EA5 /* SafariExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionViewController.swift; sourceTree = ""; };
87 | E4BC96A02154455600E65EA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
88 | E4BC96A12154455600E65EA5 /* script.js */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.javascript; path = script.js; sourceTree = ""; usesTabs = 0; };
89 | E4BC96A52154455600E65EA5 /* RSS_Button.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RSS_Button.entitlements; sourceTree = ""; };
90 | E4BC96BC21547DF500E65EA5 /* SafariExtensionStateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionStateManager.swift; sourceTree = ""; };
91 | E4BC96BE2154CB2700E65EA5 /* ToolbarItemIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ToolbarItemIcon.pdf; sourceTree = ""; };
92 | E4BC96C92158A2EB00E65EA5 /* FeedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedModel.swift; sourceTree = ""; };
93 | E4BC96CF2158A71400E65EA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SafariExtensionViewController.xib; sourceTree = ""; };
94 | E4C55BE3215EF78D00BF9426 /* FeedHandlerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedHandlerModel.swift; sourceTree = ""; };
95 | E4C55BE52160730100BF9426 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = ""; };
96 | E4C55BEF216432EB00BF9426 /* SafariServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SafariServices.framework; path = System/Library/Frameworks/SafariServices.framework; sourceTree = SDKROOT; };
97 | E4C55BF22164333200BF9426 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
98 | /* End PBXFileReference section */
99 |
100 | /* Begin PBXFrameworksBuildPhase section */
101 | E4BC965E2154452800E65EA5 /* Frameworks */ = {
102 | isa = PBXFrameworksBuildPhase;
103 | buildActionMask = 2147483647;
104 | files = (
105 | E4C55BF42164333F00BF9426 /* Foundation.framework in Frameworks */,
106 | E4C55BE92160A10A00BF9426 /* Cocoa.framework in Frameworks */,
107 | E4C55BF0216432EB00BF9426 /* SafariServices.framework in Frameworks */,
108 | );
109 | runOnlyForDeploymentPostprocessing = 0;
110 | };
111 | E4BC96702154452900E65EA5 /* Frameworks */ = {
112 | isa = PBXFrameworksBuildPhase;
113 | buildActionMask = 2147483647;
114 | files = (
115 | );
116 | runOnlyForDeploymentPostprocessing = 0;
117 | };
118 | E4BC967B2154452900E65EA5 /* Frameworks */ = {
119 | isa = PBXFrameworksBuildPhase;
120 | buildActionMask = 2147483647;
121 | files = (
122 | );
123 | runOnlyForDeploymentPostprocessing = 0;
124 | };
125 | E4BC96912154455500E65EA5 /* Frameworks */ = {
126 | isa = PBXFrameworksBuildPhase;
127 | buildActionMask = 2147483647;
128 | files = (
129 | E4C55BF32164333200BF9426 /* Foundation.framework in Frameworks */,
130 | E4BC96972154455500E65EA5 /* Cocoa.framework in Frameworks */,
131 | E4C55BF12164331000BF9426 /* SafariServices.framework in Frameworks */,
132 | );
133 | runOnlyForDeploymentPostprocessing = 0;
134 | };
135 | /* End PBXFrameworksBuildPhase section */
136 |
137 | /* Begin PBXGroup section */
138 | E4BC96582154452800E65EA5 = {
139 | isa = PBXGroup;
140 | children = (
141 | E4BC96632154452800E65EA5 /* RSS-Button-MacOS */,
142 | E4BC96982154455500E65EA5 /* RSS-Button-Safari-Extension */,
143 | E4BC96952154455500E65EA5 /* Frameworks */,
144 | E4BC96622154452800E65EA5 /* Products */,
145 | );
146 | sourceTree = "";
147 | };
148 | E4BC96622154452800E65EA5 /* Products */ = {
149 | isa = PBXGroup;
150 | children = (
151 | E4BC96612154452800E65EA5 /* RSS Button for Safari.app */,
152 | E4BC96732154452900E65EA5 /* RSS-Button-for-SafariTests.xctest */,
153 | E4BC967E2154452900E65EA5 /* RSS-Button-for-SafariUITests.xctest */,
154 | E4BC96942154455500E65EA5 /* RSS Button.appex */,
155 | );
156 | name = Products;
157 | sourceTree = "";
158 | };
159 | E4BC96632154452800E65EA5 /* RSS-Button-MacOS */ = {
160 | isa = PBXGroup;
161 | children = (
162 | E4BC96642154452800E65EA5 /* AppDelegate.swift */,
163 | E4C55BE52160730100BF9426 /* SettingsManager.swift */,
164 | E4BC96662154452800E65EA5 /* ViewController.swift */,
165 | E4C55BE3215EF78D00BF9426 /* FeedHandlerModel.swift */,
166 | E4BC96682154452900E65EA5 /* Assets.xcassets */,
167 | E4BC966A2154452900E65EA5 /* Main.storyboard */,
168 | E4BC966D2154452900E65EA5 /* Info.plist */,
169 | E4BC966E2154452900E65EA5 /* RSS_Button_for_Safari.entitlements */,
170 | );
171 | path = "RSS-Button-MacOS";
172 | sourceTree = "";
173 | };
174 | E4BC96952154455500E65EA5 /* Frameworks */ = {
175 | isa = PBXGroup;
176 | children = (
177 | E4C55BF22164333200BF9426 /* Foundation.framework */,
178 | E4C55BEF216432EB00BF9426 /* SafariServices.framework */,
179 | E4BC96962154455500E65EA5 /* Cocoa.framework */,
180 | );
181 | name = Frameworks;
182 | sourceTree = "";
183 | };
184 | E4BC96982154455500E65EA5 /* RSS-Button-Safari-Extension */ = {
185 | isa = PBXGroup;
186 | children = (
187 | E4BC96992154455600E65EA5 /* SafariExtensionHandler.swift */,
188 | E4BC96CE2158A71400E65EA5 /* SafariExtensionViewController.xib */,
189 | E4BC96BC21547DF500E65EA5 /* SafariExtensionStateManager.swift */,
190 | E4BC969B2154455600E65EA5 /* SafariExtensionViewController.swift */,
191 | E4BC96BE2154CB2700E65EA5 /* ToolbarItemIcon.pdf */,
192 | E4BC96A02154455600E65EA5 /* Info.plist */,
193 | E4BC96A12154455600E65EA5 /* script.js */,
194 | E4BC96A52154455600E65EA5 /* RSS_Button.entitlements */,
195 | E4BC96C92158A2EB00E65EA5 /* FeedModel.swift */,
196 | );
197 | path = "RSS-Button-Safari-Extension";
198 | sourceTree = "";
199 | };
200 | /* End PBXGroup section */
201 |
202 | /* Begin PBXNativeTarget section */
203 | E4BC96602154452800E65EA5 /* RSS Button for Safari */ = {
204 | isa = PBXNativeTarget;
205 | buildConfigurationList = E4BC96872154452900E65EA5 /* Build configuration list for PBXNativeTarget "RSS Button for Safari" */;
206 | buildPhases = (
207 | E4BC965D2154452800E65EA5 /* Sources */,
208 | E4BC965E2154452800E65EA5 /* Frameworks */,
209 | E4BC965F2154452800E65EA5 /* Resources */,
210 | E4BC96AC2154455600E65EA5 /* Embed App Extensions */,
211 | );
212 | buildRules = (
213 | );
214 | dependencies = (
215 | E4BC96A72154455600E65EA5 /* PBXTargetDependency */,
216 | );
217 | name = "RSS Button for Safari";
218 | productName = "RSS Button for Safari";
219 | productReference = E4BC96612154452800E65EA5 /* RSS Button for Safari.app */;
220 | productType = "com.apple.product-type.application";
221 | };
222 | E4BC96722154452900E65EA5 /* RSS-Button-for-SafariTests */ = {
223 | isa = PBXNativeTarget;
224 | buildConfigurationList = E4BC968A2154452900E65EA5 /* Build configuration list for PBXNativeTarget "RSS-Button-for-SafariTests" */;
225 | buildPhases = (
226 | E4BC966F2154452900E65EA5 /* Sources */,
227 | E4BC96702154452900E65EA5 /* Frameworks */,
228 | E4BC96712154452900E65EA5 /* Resources */,
229 | );
230 | buildRules = (
231 | );
232 | dependencies = (
233 | E4BC96752154452900E65EA5 /* PBXTargetDependency */,
234 | );
235 | name = "RSS-Button-for-SafariTests";
236 | productName = "RSS Button for SafariTests";
237 | productReference = E4BC96732154452900E65EA5 /* RSS-Button-for-SafariTests.xctest */;
238 | productType = "com.apple.product-type.bundle.unit-test";
239 | };
240 | E4BC967D2154452900E65EA5 /* RSS-Button-for-SafariUITests */ = {
241 | isa = PBXNativeTarget;
242 | buildConfigurationList = E4BC968D2154452900E65EA5 /* Build configuration list for PBXNativeTarget "RSS-Button-for-SafariUITests" */;
243 | buildPhases = (
244 | E4BC967A2154452900E65EA5 /* Sources */,
245 | E4BC967B2154452900E65EA5 /* Frameworks */,
246 | E4BC967C2154452900E65EA5 /* Resources */,
247 | );
248 | buildRules = (
249 | );
250 | dependencies = (
251 | E4BC96802154452900E65EA5 /* PBXTargetDependency */,
252 | );
253 | name = "RSS-Button-for-SafariUITests";
254 | productName = "RSS Button for SafariUITests";
255 | productReference = E4BC967E2154452900E65EA5 /* RSS-Button-for-SafariUITests.xctest */;
256 | productType = "com.apple.product-type.bundle.ui-testing";
257 | };
258 | E4BC96932154455500E65EA5 /* RSS Button */ = {
259 | isa = PBXNativeTarget;
260 | buildConfigurationList = E4BC96A92154455600E65EA5 /* Build configuration list for PBXNativeTarget "RSS Button" */;
261 | buildPhases = (
262 | E4BC96902154455500E65EA5 /* Sources */,
263 | E4BC96912154455500E65EA5 /* Frameworks */,
264 | E4BC96922154455500E65EA5 /* Resources */,
265 | );
266 | buildRules = (
267 | );
268 | dependencies = (
269 | );
270 | name = "RSS Button";
271 | productName = "RSS Button";
272 | productReference = E4BC96942154455500E65EA5 /* RSS Button.appex */;
273 | productType = "com.apple.product-type.app-extension";
274 | };
275 | /* End PBXNativeTarget section */
276 |
277 | /* Begin PBXProject section */
278 | E4BC96592154452800E65EA5 /* Project object */ = {
279 | isa = PBXProject;
280 | attributes = {
281 | LastSwiftUpdateCheck = 1000;
282 | LastUpgradeCheck = 1240;
283 | ORGANIZATIONNAME = "BitPiston Studios";
284 | TargetAttributes = {
285 | E4BC96602154452800E65EA5 = {
286 | CreatedOnToolsVersion = 10.0;
287 | LastSwiftMigration = 1100;
288 | SystemCapabilities = {
289 | com.apple.ApplicationGroups.Mac = {
290 | enabled = 1;
291 | };
292 | com.apple.HardenedRuntime = {
293 | enabled = 1;
294 | };
295 | com.apple.Sandbox = {
296 | enabled = 1;
297 | };
298 | };
299 | };
300 | E4BC96722154452900E65EA5 = {
301 | CreatedOnToolsVersion = 10.0;
302 | TestTargetID = E4BC96602154452800E65EA5;
303 | };
304 | E4BC967D2154452900E65EA5 = {
305 | CreatedOnToolsVersion = 10.0;
306 | TestTargetID = E4BC96602154452800E65EA5;
307 | };
308 | E4BC96932154455500E65EA5 = {
309 | CreatedOnToolsVersion = 10.0;
310 | LastSwiftMigration = 1100;
311 | SystemCapabilities = {
312 | com.apple.ApplicationGroups.Mac = {
313 | enabled = 1;
314 | };
315 | com.apple.HardenedRuntime = {
316 | enabled = 1;
317 | };
318 | com.apple.Sandbox = {
319 | enabled = 1;
320 | };
321 | };
322 | };
323 | };
324 | };
325 | buildConfigurationList = E4BC965C2154452800E65EA5 /* Build configuration list for PBXProject "RSS-Button-for-Safari" */;
326 | compatibilityVersion = "Xcode 9.3";
327 | developmentRegion = en;
328 | hasScannedForEncodings = 0;
329 | knownRegions = (
330 | en,
331 | Base,
332 | );
333 | mainGroup = E4BC96582154452800E65EA5;
334 | productRefGroup = E4BC96622154452800E65EA5 /* Products */;
335 | projectDirPath = "";
336 | projectRoot = "";
337 | targets = (
338 | E4BC96602154452800E65EA5 /* RSS Button for Safari */,
339 | E4BC96722154452900E65EA5 /* RSS-Button-for-SafariTests */,
340 | E4BC967D2154452900E65EA5 /* RSS-Button-for-SafariUITests */,
341 | E4BC96932154455500E65EA5 /* RSS Button */,
342 | );
343 | };
344 | /* End PBXProject section */
345 |
346 | /* Begin PBXResourcesBuildPhase section */
347 | E4BC965F2154452800E65EA5 /* Resources */ = {
348 | isa = PBXResourcesBuildPhase;
349 | buildActionMask = 2147483647;
350 | files = (
351 | E4BC96692154452900E65EA5 /* Assets.xcassets in Resources */,
352 | E4BC966C2154452900E65EA5 /* Main.storyboard in Resources */,
353 | );
354 | runOnlyForDeploymentPostprocessing = 0;
355 | };
356 | E4BC96712154452900E65EA5 /* Resources */ = {
357 | isa = PBXResourcesBuildPhase;
358 | buildActionMask = 2147483647;
359 | files = (
360 | );
361 | runOnlyForDeploymentPostprocessing = 0;
362 | };
363 | E4BC967C2154452900E65EA5 /* Resources */ = {
364 | isa = PBXResourcesBuildPhase;
365 | buildActionMask = 2147483647;
366 | files = (
367 | );
368 | runOnlyForDeploymentPostprocessing = 0;
369 | };
370 | E4BC96922154455500E65EA5 /* Resources */ = {
371 | isa = PBXResourcesBuildPhase;
372 | buildActionMask = 2147483647;
373 | files = (
374 | E4BC96D02158A71400E65EA5 /* SafariExtensionViewController.xib in Resources */,
375 | E4BC96A22154455600E65EA5 /* script.js in Resources */,
376 | E4BC96BF2154CB2700E65EA5 /* ToolbarItemIcon.pdf in Resources */,
377 | E4643371216764E5000B213E /* Assets.xcassets in Resources */,
378 | );
379 | runOnlyForDeploymentPostprocessing = 0;
380 | };
381 | /* End PBXResourcesBuildPhase section */
382 |
383 | /* Begin PBXSourcesBuildPhase section */
384 | E4BC965D2154452800E65EA5 /* Sources */ = {
385 | isa = PBXSourcesBuildPhase;
386 | buildActionMask = 2147483647;
387 | files = (
388 | E4C55BE4215EF78D00BF9426 /* FeedHandlerModel.swift in Sources */,
389 | E4C55BE62160730100BF9426 /* SettingsManager.swift in Sources */,
390 | E4BC96672154452800E65EA5 /* ViewController.swift in Sources */,
391 | E4BC96652154452800E65EA5 /* AppDelegate.swift in Sources */,
392 | );
393 | runOnlyForDeploymentPostprocessing = 0;
394 | };
395 | E4BC966F2154452900E65EA5 /* Sources */ = {
396 | isa = PBXSourcesBuildPhase;
397 | buildActionMask = 2147483647;
398 | files = (
399 | );
400 | runOnlyForDeploymentPostprocessing = 0;
401 | };
402 | E4BC967A2154452900E65EA5 /* Sources */ = {
403 | isa = PBXSourcesBuildPhase;
404 | buildActionMask = 2147483647;
405 | files = (
406 | );
407 | runOnlyForDeploymentPostprocessing = 0;
408 | };
409 | E4BC96902154455500E65EA5 /* Sources */ = {
410 | isa = PBXSourcesBuildPhase;
411 | buildActionMask = 2147483647;
412 | files = (
413 | E4C55BE8216074DC00BF9426 /* FeedHandlerModel.swift in Sources */,
414 | E4BC96CB2158A5AA00E65EA5 /* FeedModel.swift in Sources */,
415 | E4C55BE7216074D400BF9426 /* SettingsManager.swift in Sources */,
416 | E4BC96BD21547DF500E65EA5 /* SafariExtensionStateManager.swift in Sources */,
417 | E4BC969A2154455600E65EA5 /* SafariExtensionHandler.swift in Sources */,
418 | E4BC969C2154455600E65EA5 /* SafariExtensionViewController.swift in Sources */,
419 | );
420 | runOnlyForDeploymentPostprocessing = 0;
421 | };
422 | /* End PBXSourcesBuildPhase section */
423 |
424 | /* Begin PBXTargetDependency section */
425 | E4BC96752154452900E65EA5 /* PBXTargetDependency */ = {
426 | isa = PBXTargetDependency;
427 | target = E4BC96602154452800E65EA5 /* RSS Button for Safari */;
428 | targetProxy = E4BC96742154452900E65EA5 /* PBXContainerItemProxy */;
429 | };
430 | E4BC96802154452900E65EA5 /* PBXTargetDependency */ = {
431 | isa = PBXTargetDependency;
432 | target = E4BC96602154452800E65EA5 /* RSS Button for Safari */;
433 | targetProxy = E4BC967F2154452900E65EA5 /* PBXContainerItemProxy */;
434 | };
435 | E4BC96A72154455600E65EA5 /* PBXTargetDependency */ = {
436 | isa = PBXTargetDependency;
437 | target = E4BC96932154455500E65EA5 /* RSS Button */;
438 | targetProxy = E4BC96A62154455600E65EA5 /* PBXContainerItemProxy */;
439 | };
440 | /* End PBXTargetDependency section */
441 |
442 | /* Begin PBXVariantGroup section */
443 | E4BC966A2154452900E65EA5 /* Main.storyboard */ = {
444 | isa = PBXVariantGroup;
445 | children = (
446 | E4BC966B2154452900E65EA5 /* Base */,
447 | );
448 | name = Main.storyboard;
449 | sourceTree = "";
450 | };
451 | E4BC96CE2158A71400E65EA5 /* SafariExtensionViewController.xib */ = {
452 | isa = PBXVariantGroup;
453 | children = (
454 | E4BC96CF2158A71400E65EA5 /* Base */,
455 | );
456 | name = SafariExtensionViewController.xib;
457 | sourceTree = "";
458 | };
459 | /* End PBXVariantGroup section */
460 |
461 | /* Begin XCBuildConfiguration section */
462 | E4BC96852154452900E65EA5 /* Debug */ = {
463 | isa = XCBuildConfiguration;
464 | buildSettings = {
465 | ALWAYS_SEARCH_USER_PATHS = NO;
466 | CLANG_ANALYZER_NONNULL = YES;
467 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
468 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
469 | CLANG_CXX_LIBRARY = "libc++";
470 | CLANG_ENABLE_MODULES = YES;
471 | CLANG_ENABLE_OBJC_ARC = YES;
472 | CLANG_ENABLE_OBJC_WEAK = YES;
473 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
474 | CLANG_WARN_BOOL_CONVERSION = YES;
475 | CLANG_WARN_COMMA = YES;
476 | CLANG_WARN_CONSTANT_CONVERSION = YES;
477 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
478 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
479 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
480 | CLANG_WARN_EMPTY_BODY = YES;
481 | CLANG_WARN_ENUM_CONVERSION = YES;
482 | CLANG_WARN_INFINITE_RECURSION = YES;
483 | CLANG_WARN_INT_CONVERSION = YES;
484 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
485 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
486 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
487 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
488 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
489 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
490 | CLANG_WARN_STRICT_PROTOTYPES = YES;
491 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
492 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
493 | CLANG_WARN_UNREACHABLE_CODE = YES;
494 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
495 | CODE_SIGN_IDENTITY = "-";
496 | COPY_PHASE_STRIP = NO;
497 | DEBUG_INFORMATION_FORMAT = dwarf;
498 | ENABLE_STRICT_OBJC_MSGSEND = YES;
499 | ENABLE_TESTABILITY = YES;
500 | GCC_C_LANGUAGE_STANDARD = gnu11;
501 | GCC_DYNAMIC_NO_PIC = NO;
502 | GCC_NO_COMMON_BLOCKS = YES;
503 | GCC_OPTIMIZATION_LEVEL = 0;
504 | GCC_PREPROCESSOR_DEFINITIONS = (
505 | "DEBUG=1",
506 | "$(inherited)",
507 | );
508 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
509 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
510 | GCC_WARN_UNDECLARED_SELECTOR = YES;
511 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
512 | GCC_WARN_UNUSED_FUNCTION = YES;
513 | GCC_WARN_UNUSED_VARIABLE = YES;
514 | MACOSX_DEPLOYMENT_TARGET = 10.12;
515 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
516 | MTL_FAST_MATH = YES;
517 | ONLY_ACTIVE_ARCH = YES;
518 | SDKROOT = macosx;
519 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
520 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
521 | };
522 | name = Debug;
523 | };
524 | E4BC96862154452900E65EA5 /* Release */ = {
525 | isa = XCBuildConfiguration;
526 | buildSettings = {
527 | ALWAYS_SEARCH_USER_PATHS = NO;
528 | CLANG_ANALYZER_NONNULL = YES;
529 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
530 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
531 | CLANG_CXX_LIBRARY = "libc++";
532 | CLANG_ENABLE_MODULES = YES;
533 | CLANG_ENABLE_OBJC_ARC = YES;
534 | CLANG_ENABLE_OBJC_WEAK = YES;
535 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
536 | CLANG_WARN_BOOL_CONVERSION = YES;
537 | CLANG_WARN_COMMA = YES;
538 | CLANG_WARN_CONSTANT_CONVERSION = YES;
539 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
540 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
541 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
542 | CLANG_WARN_EMPTY_BODY = YES;
543 | CLANG_WARN_ENUM_CONVERSION = YES;
544 | CLANG_WARN_INFINITE_RECURSION = YES;
545 | CLANG_WARN_INT_CONVERSION = YES;
546 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
547 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
548 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
549 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
550 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
551 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
552 | CLANG_WARN_STRICT_PROTOTYPES = YES;
553 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
554 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
555 | CLANG_WARN_UNREACHABLE_CODE = YES;
556 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
557 | CODE_SIGN_IDENTITY = "-";
558 | COPY_PHASE_STRIP = NO;
559 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
560 | ENABLE_NS_ASSERTIONS = NO;
561 | ENABLE_STRICT_OBJC_MSGSEND = YES;
562 | GCC_C_LANGUAGE_STANDARD = gnu11;
563 | GCC_NO_COMMON_BLOCKS = YES;
564 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
565 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
566 | GCC_WARN_UNDECLARED_SELECTOR = YES;
567 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
568 | GCC_WARN_UNUSED_FUNCTION = YES;
569 | GCC_WARN_UNUSED_VARIABLE = YES;
570 | MACOSX_DEPLOYMENT_TARGET = 10.12;
571 | MTL_ENABLE_DEBUG_INFO = NO;
572 | MTL_FAST_MATH = YES;
573 | SDKROOT = macosx;
574 | SWIFT_COMPILATION_MODE = wholemodule;
575 | SWIFT_OPTIMIZATION_LEVEL = "-O";
576 | };
577 | name = Release;
578 | };
579 | E4BC96882154452900E65EA5 /* Debug */ = {
580 | isa = XCBuildConfiguration;
581 | buildSettings = {
582 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
583 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
584 | CLANG_USE_OPTIMIZATION_PROFILE = YES;
585 | CODE_SIGN_ENTITLEMENTS = "RSS-Button-MacOS/RSS_Button_for_Safari.entitlements";
586 | CODE_SIGN_IDENTITY = "Apple Development";
587 | CODE_SIGN_STYLE = Automatic;
588 | COMBINE_HIDPI_IMAGES = YES;
589 | CURRENT_PROJECT_VERSION = 78;
590 | DEVELOPMENT_TEAM = YE2H3T9GV5;
591 | ENABLE_HARDENED_RUNTIME = YES;
592 | INFOPLIST_FILE = "RSS-Button-MacOS/Info.plist";
593 | LD_RUNPATH_SEARCH_PATHS = (
594 | "$(inherited)",
595 | "@executable_path/../Frameworks",
596 | );
597 | MACOSX_DEPLOYMENT_TARGET = 10.12;
598 | MARKETING_VERSION = 1.7.3;
599 | "OTHER_SWIFT_FLAGS[arch=*]" = "-D DEBUG";
600 | PRODUCT_BUNDLE_IDENTIFIER = com.bitpiston.RSSButton4Safari;
601 | PRODUCT_NAME = "$(TARGET_NAME)";
602 | PROVISIONING_PROFILE_SPECIFIER = "";
603 | SWIFT_VERSION = 5.0;
604 | };
605 | name = Debug;
606 | };
607 | E4BC96892154452900E65EA5 /* Release */ = {
608 | isa = XCBuildConfiguration;
609 | buildSettings = {
610 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
611 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
612 | CLANG_USE_OPTIMIZATION_PROFILE = YES;
613 | CODE_SIGN_ENTITLEMENTS = "RSS-Button-MacOS/RSS_Button_for_Safari.entitlements";
614 | CODE_SIGN_IDENTITY = "Apple Development";
615 | CODE_SIGN_STYLE = Automatic;
616 | COMBINE_HIDPI_IMAGES = YES;
617 | CURRENT_PROJECT_VERSION = 78;
618 | DEVELOPMENT_TEAM = YE2H3T9GV5;
619 | ENABLE_HARDENED_RUNTIME = YES;
620 | INFOPLIST_FILE = "RSS-Button-MacOS/Info.plist";
621 | LD_RUNPATH_SEARCH_PATHS = (
622 | "$(inherited)",
623 | "@executable_path/../Frameworks",
624 | );
625 | MACOSX_DEPLOYMENT_TARGET = 10.12;
626 | MARKETING_VERSION = 1.7.3;
627 | PRODUCT_BUNDLE_IDENTIFIER = com.bitpiston.RSSButton4Safari;
628 | PRODUCT_NAME = "$(TARGET_NAME)";
629 | PROVISIONING_PROFILE_SPECIFIER = "";
630 | SWIFT_VERSION = 5.0;
631 | };
632 | name = Release;
633 | };
634 | E4BC968B2154452900E65EA5 /* Debug */ = {
635 | isa = XCBuildConfiguration;
636 | buildSettings = {
637 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
638 | BUNDLE_LOADER = "$(TEST_HOST)";
639 | CODE_SIGN_STYLE = Automatic;
640 | COMBINE_HIDPI_IMAGES = YES;
641 | INFOPLIST_FILE = "RSS Button for SafariTests/Info.plist";
642 | LD_RUNPATH_SEARCH_PATHS = (
643 | "$(inherited)",
644 | "@executable_path/../Frameworks",
645 | "@loader_path/../Frameworks",
646 | );
647 | PRODUCT_BUNDLE_IDENTIFIER = "bitpiston.RSS-Button-for-SafariTests";
648 | PRODUCT_NAME = "$(TARGET_NAME)";
649 | SWIFT_VERSION = 4.2;
650 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RSS-Button-for-Safari.app/Contents/MacOS/RSS-Button-for-Safari";
651 | };
652 | name = Debug;
653 | };
654 | E4BC968C2154452900E65EA5 /* Release */ = {
655 | isa = XCBuildConfiguration;
656 | buildSettings = {
657 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
658 | BUNDLE_LOADER = "$(TEST_HOST)";
659 | CODE_SIGN_STYLE = Automatic;
660 | COMBINE_HIDPI_IMAGES = YES;
661 | INFOPLIST_FILE = "RSS Button for SafariTests/Info.plist";
662 | LD_RUNPATH_SEARCH_PATHS = (
663 | "$(inherited)",
664 | "@executable_path/../Frameworks",
665 | "@loader_path/../Frameworks",
666 | );
667 | PRODUCT_BUNDLE_IDENTIFIER = "bitpiston.RSS-Button-for-SafariTests";
668 | PRODUCT_NAME = "$(TARGET_NAME)";
669 | SWIFT_VERSION = 4.2;
670 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RSS-Button-for-Safari.app/Contents/MacOS/RSS-Button-for-Safari";
671 | };
672 | name = Release;
673 | };
674 | E4BC968E2154452900E65EA5 /* Debug */ = {
675 | isa = XCBuildConfiguration;
676 | buildSettings = {
677 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
678 | CODE_SIGN_STYLE = Automatic;
679 | COMBINE_HIDPI_IMAGES = YES;
680 | INFOPLIST_FILE = "RSS Button for SafariUITests/Info.plist";
681 | LD_RUNPATH_SEARCH_PATHS = (
682 | "$(inherited)",
683 | "@executable_path/../Frameworks",
684 | "@loader_path/../Frameworks",
685 | );
686 | PRODUCT_BUNDLE_IDENTIFIER = "bitpiston.RSS-Button-for-SafariUITests";
687 | PRODUCT_NAME = "$(TARGET_NAME)";
688 | SWIFT_VERSION = 4.2;
689 | TEST_TARGET_NAME = "RSS Button for Safari";
690 | };
691 | name = Debug;
692 | };
693 | E4BC968F2154452900E65EA5 /* Release */ = {
694 | isa = XCBuildConfiguration;
695 | buildSettings = {
696 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
697 | CODE_SIGN_STYLE = Automatic;
698 | COMBINE_HIDPI_IMAGES = YES;
699 | INFOPLIST_FILE = "RSS Button for SafariUITests/Info.plist";
700 | LD_RUNPATH_SEARCH_PATHS = (
701 | "$(inherited)",
702 | "@executable_path/../Frameworks",
703 | "@loader_path/../Frameworks",
704 | );
705 | PRODUCT_BUNDLE_IDENTIFIER = "bitpiston.RSS-Button-for-SafariUITests";
706 | PRODUCT_NAME = "$(TARGET_NAME)";
707 | SWIFT_VERSION = 4.2;
708 | TEST_TARGET_NAME = "RSS Button for Safari";
709 | };
710 | name = Release;
711 | };
712 | E4BC96AA2154455600E65EA5 /* Debug */ = {
713 | isa = XCBuildConfiguration;
714 | buildSettings = {
715 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
716 | CLANG_USE_OPTIMIZATION_PROFILE = YES;
717 | CODE_SIGN_ENTITLEMENTS = "RSS-Button-Safari-Extension/RSS_Button.entitlements";
718 | CODE_SIGN_IDENTITY = "Mac Developer";
719 | CODE_SIGN_STYLE = Automatic;
720 | CURRENT_PROJECT_VERSION = 78;
721 | DEVELOPMENT_TEAM = YE2H3T9GV5;
722 | ENABLE_HARDENED_RUNTIME = YES;
723 | INFOPLIST_FILE = "RSS-Button-Safari-Extension/Info.plist";
724 | LD_RUNPATH_SEARCH_PATHS = (
725 | "$(inherited)",
726 | "@executable_path/../Frameworks",
727 | "@executable_path/../../../../Frameworks",
728 | );
729 | MACOSX_DEPLOYMENT_TARGET = 10.12;
730 | MARKETING_VERSION = 1.7.3;
731 | "OTHER_SWIFT_FLAGS[arch=*]" = "-D DEBUG";
732 | PRODUCT_BUNDLE_IDENTIFIER = com.bitpiston.RSSButton4Safari.SafariExtension;
733 | PRODUCT_NAME = "$(TARGET_NAME)";
734 | PROVISIONING_PROFILE_SPECIFIER = "";
735 | SKIP_INSTALL = YES;
736 | SWIFT_VERSION = 5.0;
737 | };
738 | name = Debug;
739 | };
740 | E4BC96AB2154455600E65EA5 /* Release */ = {
741 | isa = XCBuildConfiguration;
742 | buildSettings = {
743 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
744 | CLANG_USE_OPTIMIZATION_PROFILE = YES;
745 | CODE_SIGN_ENTITLEMENTS = "RSS-Button-Safari-Extension/RSS_Button.entitlements";
746 | CODE_SIGN_IDENTITY = "Mac Developer";
747 | CODE_SIGN_STYLE = Automatic;
748 | CURRENT_PROJECT_VERSION = 78;
749 | DEVELOPMENT_TEAM = YE2H3T9GV5;
750 | ENABLE_HARDENED_RUNTIME = YES;
751 | INFOPLIST_FILE = "RSS-Button-Safari-Extension/Info.plist";
752 | LD_RUNPATH_SEARCH_PATHS = (
753 | "$(inherited)",
754 | "@executable_path/../Frameworks",
755 | "@executable_path/../../../../Frameworks",
756 | );
757 | MACOSX_DEPLOYMENT_TARGET = 10.12;
758 | MARKETING_VERSION = 1.7.3;
759 | PRODUCT_BUNDLE_IDENTIFIER = com.bitpiston.RSSButton4Safari.SafariExtension;
760 | PRODUCT_NAME = "$(TARGET_NAME)";
761 | PROVISIONING_PROFILE_SPECIFIER = "";
762 | SKIP_INSTALL = YES;
763 | SWIFT_VERSION = 5.0;
764 | };
765 | name = Release;
766 | };
767 | /* End XCBuildConfiguration section */
768 |
769 | /* Begin XCConfigurationList section */
770 | E4BC965C2154452800E65EA5 /* Build configuration list for PBXProject "RSS-Button-for-Safari" */ = {
771 | isa = XCConfigurationList;
772 | buildConfigurations = (
773 | E4BC96852154452900E65EA5 /* Debug */,
774 | E4BC96862154452900E65EA5 /* Release */,
775 | );
776 | defaultConfigurationIsVisible = 0;
777 | defaultConfigurationName = Release;
778 | };
779 | E4BC96872154452900E65EA5 /* Build configuration list for PBXNativeTarget "RSS Button for Safari" */ = {
780 | isa = XCConfigurationList;
781 | buildConfigurations = (
782 | E4BC96882154452900E65EA5 /* Debug */,
783 | E4BC96892154452900E65EA5 /* Release */,
784 | );
785 | defaultConfigurationIsVisible = 0;
786 | defaultConfigurationName = Release;
787 | };
788 | E4BC968A2154452900E65EA5 /* Build configuration list for PBXNativeTarget "RSS-Button-for-SafariTests" */ = {
789 | isa = XCConfigurationList;
790 | buildConfigurations = (
791 | E4BC968B2154452900E65EA5 /* Debug */,
792 | E4BC968C2154452900E65EA5 /* Release */,
793 | );
794 | defaultConfigurationIsVisible = 0;
795 | defaultConfigurationName = Release;
796 | };
797 | E4BC968D2154452900E65EA5 /* Build configuration list for PBXNativeTarget "RSS-Button-for-SafariUITests" */ = {
798 | isa = XCConfigurationList;
799 | buildConfigurations = (
800 | E4BC968E2154452900E65EA5 /* Debug */,
801 | E4BC968F2154452900E65EA5 /* Release */,
802 | );
803 | defaultConfigurationIsVisible = 0;
804 | defaultConfigurationName = Release;
805 | };
806 | E4BC96A92154455600E65EA5 /* Build configuration list for PBXNativeTarget "RSS Button" */ = {
807 | isa = XCConfigurationList;
808 | buildConfigurations = (
809 | E4BC96AA2154455600E65EA5 /* Debug */,
810 | E4BC96AB2154455600E65EA5 /* Release */,
811 | );
812 | defaultConfigurationIsVisible = 0;
813 | defaultConfigurationName = Release;
814 | };
815 | /* End XCConfigurationList section */
816 | };
817 | rootObject = E4BC96592154452800E65EA5 /* Project object */;
818 | }
819 |
--------------------------------------------------------------------------------
/RSS-Button-for-Safari.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/RSS-Button-for-Safari.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ToolbarItemIcon.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitpiston/rss-button-for-safari/71c6dcf725ecf4182a31e6a37a9b5973c9e12823/ToolbarItemIcon.sketch
--------------------------------------------------------------------------------