├── .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 | ![Choose news reader](https://rss-extension.bitpiston.com/screens/choose-default-reader@2x.webp) 28 | 29 | 4) Enable the extension from Safari Preferences under the extensions tab: 30 | 31 | ![Enable extension in Safari](https://rss-extension.bitpiston.com/screens/enable-extension-from-safari@2x.webp) 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 | ![Active toolbar button when a page has feeds](https://rss-extension.bitpiston.com/screens/page-has-feeds@2x.webp) 84 | 85 | ![Inactive toolbar button when a page does not have feeds](https://rss-extension.bitpiston.com/screens/no-feeds-available@2x.webp) 86 | 87 | ![List of available feeds for a page](https://rss-extension.bitpiston.com/screens/simply-view-feeds@2x.webp) 88 | 89 | ![Subscribing a feed](https://rss-extension.bitpiston.com/screens/subscribe-to-feed@2x.webp) 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 | 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 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 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 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 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 | 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 | 125 | 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 --------------------------------------------------------------------------------