├── .gitignore ├── Keychain.swift ├── LICENSE ├── README.md ├── Shared ├── BezelMessage.swift ├── GitHubAPI.swift ├── GitHubCredential.swift └── GitHubRouter.swift ├── Xgist.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Xgist ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── copy.imageset │ │ ├── Contents.json │ │ └── copy.pdf │ └── message.imageset │ │ ├── Contents.json │ │ └── message.pdf ├── BezelMessageWindowController.swift ├── Info.plist ├── LoginViewController.swift ├── Main.storyboard ├── TwoFactorViewController.swift └── Xgist.entitlements ├── XgistEx ├── Info.plist ├── SourceEditorCommand.swift ├── SourceEditorExtension.swift └── XgistEx.entitlements └── other ├── demo.gif ├── keychain.png ├── login.png ├── menuOptions.png ├── preferences.png └── xgist_pref.png /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Keychain.swift: -------------------------------------------------------------------------------- 1 | 2 | //https://developer.apple.com/library/content/samplecode/GenericKeychain/Introduction/Intro.html 3 | 4 | import Foundation 5 | 6 | struct KeychainConfiguration { 7 | static let serviceName = "Xgist" 8 | static let accessGroup = "com.idevzilla.Xgist" 9 | static let account = "XgistAccount" 10 | } 11 | 12 | struct Keychain { 13 | 14 | enum KeychainError: Error { 15 | case noPassword 16 | case unexpectedPasswordData 17 | case unexpectedItemData 18 | case unhandledError(status: OSStatus) 19 | } 20 | 21 | func savePassword(_ password: String) throws { 22 | // Encode the password into an Data object. 23 | let encodedPassword = password.data(using: String.Encoding.utf8)! 24 | 25 | do { 26 | // Check for an existing item in the keychain. 27 | try _ = readPassword() 28 | 29 | // Update the existing item with the new password. 30 | var attributesToUpdate = [String : AnyObject]() 31 | attributesToUpdate[kSecValueData as String] = encodedPassword as AnyObject? 32 | 33 | let query = keychainQuery(withService: KeychainConfiguration.serviceName, account: KeychainConfiguration.account, accessGroup: KeychainConfiguration.accessGroup) 34 | let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary) 35 | 36 | // Throw an error if an unexpected status was returned. 37 | guard status == noErr else { throw KeychainError.unhandledError(status: status) } 38 | } 39 | catch KeychainError.noPassword { 40 | /* 41 | No password was found in the keychain. Create a dictionary to save 42 | as a new keychain item. 43 | */ 44 | var newItem = keychainQuery(withService: KeychainConfiguration.serviceName, account: KeychainConfiguration.account, accessGroup: KeychainConfiguration.accessGroup) 45 | newItem[kSecValueData as String] = encodedPassword as AnyObject? 46 | 47 | // Add a the new item to the keychain. 48 | let status = SecItemAdd(newItem as CFDictionary, nil) 49 | 50 | // Throw an error if an unexpected status was returned. 51 | guard status == noErr else { throw KeychainError.unhandledError(status: status) } 52 | } 53 | } 54 | 55 | func readPassword() throws -> String { 56 | /* 57 | Build a query to find the item that matches the service, account and 58 | access group. 59 | */ 60 | var query = keychainQuery(withService: KeychainConfiguration.serviceName, account: KeychainConfiguration.account, accessGroup: KeychainConfiguration.accessGroup) 61 | 62 | query[kSecMatchLimit as String] = kSecMatchLimitOne 63 | query[kSecReturnAttributes as String] = kCFBooleanTrue 64 | query[kSecReturnData as String] = kCFBooleanTrue 65 | 66 | // Try to fetch the existing keychain item that matches the query. 67 | var queryResult: AnyObject? 68 | let status = withUnsafeMutablePointer(to: &queryResult) { 69 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) 70 | } 71 | 72 | // Check the return status and throw an error if appropriate. 73 | guard status != errSecItemNotFound else { throw KeychainError.noPassword } 74 | guard status == noErr else { throw KeychainError.unhandledError(status: status) } 75 | 76 | // Parse the password string from the query result. 77 | guard let existingItem = queryResult as? [String : AnyObject], 78 | let passwordData = existingItem[kSecValueData as String] as? Data, 79 | let password = String(data: passwordData, encoding: String.Encoding.utf8) 80 | else { 81 | throw KeychainError.unexpectedPasswordData 82 | } 83 | 84 | return password 85 | } 86 | 87 | func deleteItem() throws { 88 | // Delete the existing item from the keychain. 89 | let query = keychainQuery(withService: KeychainConfiguration.serviceName, account: KeychainConfiguration.account, accessGroup: KeychainConfiguration.accessGroup) 90 | let status = SecItemDelete(query as CFDictionary) 91 | 92 | // Throw an error if an unexpected status was returned. 93 | guard status == noErr || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) } 94 | } 95 | 96 | private func keychainQuery(withService service: String, account: String? = nil, accessGroup: String? = nil) -> [String : AnyObject] { 97 | var query = [String : AnyObject]() 98 | query[kSecClass as String] = kSecClassGenericPassword 99 | query[kSecAttrService as String] = service as AnyObject? 100 | 101 | if let account = account { 102 | query[kSecAttrAccount as String] = account as AnyObject? 103 | } 104 | 105 | if let accessGroup = accessGroup { 106 | query[kSecAttrAccessGroup as String] = accessGroup as AnyObject? 107 | } 108 | 109 | return query 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Fernando Bunn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xgist 2 | Xgist is a Xcode extension to send code to GitHub's Gist. 3 | The extension will send your code selection to Gist and automatically copy the Gist URL into your Clipboard. 4 | 5 | Be aware that it will replace your current clipboard with the Gist URL! 6 | 7 | # Authentication (Optional) 8 | By default Xgist will post your gists as anonymous, but once you open the .app you'll see a login form where you can login to your GitHub account and post authenticated gists. 9 | 10 | ![screenshot](./other/login.png) 11 | 12 | After you login you'll need to restart Xcode so it will give you the authenticated gist option 13 | 14 | ![screenshot](./other/menuOptions.png) 15 | 16 | Note that Xcode might ask to use your keychain since that's how your GitHub access token is stored. 17 | 18 | ![screenshot](./other/keychain.png) 19 | 20 | 21 | [Youtube demo video](https://youtu.be/LtyPq-bzjM0) 22 | 23 | ![screenshot](./other/demo.gif) 24 | 25 | 26 | # Installation 27 | 28 | # Easy way 29 | Thanks to [Rambo](https://twitter.com/_inside) you can download a signed version of the app [here](https://github.com/Bunn/Xgist/releases/latest): 30 | 31 | After opening it, you might need to go to the system preferences app and enable Xgist plugin. 32 | 33 | ![screenshot](./other/preferences.png) 34 | ![screenshot](./other/xgist_pref.png) 35 | 36 | 37 | 38 | # Manual way 39 | *Note that by choosing this way you'll need to create your own GitHub app and add your app ID and Secret into the `GitHubCredential.swift` file* 40 | 41 | 1. Clone the repo and open ``Xgist.xcodeproj``; 42 | 2. Enable target signing for both the Application and the Source Code Extension using your own developer ID; 43 | 3. Product > Archive; 44 | 4. Right click archive > Show in Finder; 45 | 5. Right click archive > Show Package Contents; 46 | 6. Open Products/Applications; 47 | 7. Drag ``Xgist.app`` to your Applications folder and double click on it; 48 | 8. Xgist macOS app will open and install the extension; 49 | 9. Close Xgist.app 50 | 10. Done. 51 | -------------------------------------------------------------------------------- /Shared/BezelMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BezelMessage.swift 3 | // Xgist 4 | // 5 | // Created by Guilherme Rambo on 15/03/17. 6 | // Copyright © 2017 Fernando Bunn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct BezelMessage { 12 | let imageName: String 13 | let title: String 14 | } 15 | 16 | extension BezelMessage { 17 | static let clipboard = BezelMessage(imageName: "copy", title: "Link Copied") 18 | static let installed = BezelMessage(imageName: "message", title: "Xgist is Installed") 19 | 20 | private var dict: [String: String] { 21 | return [ 22 | "imageName": imageName, 23 | "title": title 24 | ] 25 | } 26 | 27 | var urlEncoded: URL? { 28 | var components = URLComponents(string: "XgistMessage://message") 29 | 30 | let imageNameItem = URLQueryItem(name: "imageName", value: imageName) 31 | let titleItem = URLQueryItem(name: "title", value: title) 32 | 33 | components?.queryItems = [imageNameItem, titleItem] 34 | 35 | return components?.url 36 | } 37 | 38 | init?(url: URL) { 39 | guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false), 40 | let queryItems = components.queryItems else { return nil } 41 | 42 | guard let imageName = queryItems.first(where: { $0.name == "imageName" })?.value, 43 | let title = queryItems.first(where: { $0.name == "title" })?.value else { return nil } 44 | 45 | self.imageName = imageName 46 | self.title = title 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Shared/GitHubAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubAPI.swift 3 | // Xgist 4 | // 5 | // Created by Fernando Bunn on 27/03/17. 6 | // Copyright © 2017 Fernando Bunn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct GitHubAPI { 12 | let twoFactorHeader = "X-GitHub-OTP" 13 | 14 | enum GitHubAPIError: Error { 15 | case badHHTPStatus 16 | case invalidRequest 17 | case invalidJSON 18 | case tokenNotFound 19 | case twoFactorRequired 20 | } 21 | 22 | 23 | //MARK: - Variables 24 | var isAuthenticated: Bool { 25 | if let _ = token { 26 | return true 27 | } else { 28 | return false 29 | } 30 | } 31 | 32 | var token: String? { 33 | do { 34 | let password = try Keychain().readPassword() 35 | return password 36 | } catch { 37 | return nil 38 | } 39 | } 40 | 41 | 42 | //MARK: - Public Functions 43 | 44 | func logout() { 45 | do { 46 | try Keychain().deleteItem() 47 | } catch { 48 | fatalError("Error deleting keychain item - \(error)") 49 | } 50 | } 51 | 52 | func post(gist: String, fileExtension: String, authenticated: Bool, completion: @escaping (Error?, String?) -> Void) { 53 | var file = [String : Any]() 54 | file["content"] = gist 55 | 56 | var files = [String : Any]() 57 | files["Xgist.\(fileExtension)"] = file 58 | 59 | let formatter = DateFormatter() 60 | formatter.dateStyle = .medium 61 | formatter.timeStyle = .medium 62 | let dateString = formatter.string(from: Date()) 63 | 64 | var jsonDictionary = [String : Any]() 65 | jsonDictionary["description"] = "Generated by Xgist (https://github.com/Bunn/Xgist) at \(dateString)" 66 | jsonDictionary["public"] = false 67 | jsonDictionary["files"] = files 68 | 69 | guard let request = GitHubRouter.gist(jsonDictionary, authenticated).request else { 70 | completion(GitHubAPIError.invalidRequest, nil) 71 | return 72 | } 73 | 74 | //Setup Session 75 | let task = URLSession.shared.dataTask(with: request) { (data, response, error) in 76 | guard let data = data, error == nil else { 77 | completion(error, nil) 78 | return 79 | } 80 | 81 | if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 201 { 82 | completion(GitHubAPIError.badHHTPStatus, nil) 83 | return 84 | } 85 | 86 | if let json = try? JSONSerialization.jsonObject(with: data, options: []) as! [String : Any] { 87 | if let htmlURL = json["html_url"] as? String { 88 | completion(nil, htmlURL) 89 | } 90 | } 91 | } 92 | 93 | task.resume() 94 | } 95 | 96 | func authenticate (username: String, password: String, twoFactorCode: String? = nil, completion: @escaping (Error?) -> Void) { 97 | let scopes = ["gist"] 98 | let params = ["client_secret" : GitHubCredential.clientSecret.rawValue, 99 | "scopes" : scopes, 100 | "note" : "testNote"] as [String : Any] 101 | 102 | guard var request = GitHubRouter.auth(params).request else { 103 | completion(GitHubAPIError.invalidRequest) 104 | return 105 | 106 | } 107 | if let code = twoFactorCode { 108 | request.setValue(code, forHTTPHeaderField: twoFactorHeader) 109 | } 110 | let loginData = base64Login(username: username, password: password) 111 | request.setValue("Basic \(loginData)", forHTTPHeaderField: "Authorization") 112 | 113 | //Setup Session 114 | let task = URLSession.shared.dataTask(with: request) { (data, response, error) in 115 | guard let responseData = data, error == nil, 116 | let jsonObject = try? JSONSerialization.jsonObject(with: responseData, options: []) else { 117 | completion(error) 118 | return 119 | } 120 | 121 | guard let json = jsonObject as? [String : Any] else { 122 | completion(GitHubAPIError.invalidJSON) 123 | return 124 | } 125 | 126 | guard let httpStatus = response as? HTTPURLResponse else { 127 | completion(GitHubAPIError.invalidRequest) 128 | return 129 | } 130 | 131 | guard httpStatus.allHeaderFields[self.twoFactorHeader] == nil else { 132 | completion(GitHubAPIError.twoFactorRequired) 133 | return 134 | } 135 | 136 | guard let token = json["token"] as? String else { 137 | completion(GitHubAPIError.tokenNotFound) 138 | return 139 | } 140 | 141 | do { 142 | try Keychain().savePassword(token) 143 | completion(nil) 144 | } catch { 145 | completion(error) 146 | } 147 | } 148 | task.resume() 149 | } 150 | 151 | 152 | //MARK: - Private Functions 153 | 154 | fileprivate func base64Login(username: String, password: String) -> String { 155 | let loginString = "\(username):\(password)" 156 | let loginData = loginString.data(using: String.Encoding.utf8)! 157 | let base64LoginString = loginData.base64EncodedString() 158 | return base64LoginString 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Shared/GitHubCredential.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubCredential.swift 3 | // Xgist 4 | // 5 | // Created by Fernando Bunn on 28/03/17. 6 | // Copyright © 2017 Fernando Bunn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum GitHubCredential: String { 12 | case clientSecret = "XXX" 13 | case clientID = "XXXX" 14 | } 15 | -------------------------------------------------------------------------------- /Shared/GitHubRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubRouter.swift 3 | // Xgist 4 | // 5 | // Created by Fernando Bunn on 27/03/17. 6 | // Copyright © 2017 Fernando Bunn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum GitHubRouter { 12 | case auth([String : Any]) 13 | case gist([String : Any], Bool) 14 | 15 | var basePath: String { 16 | return "https://api.github.com" 17 | } 18 | 19 | var path: String { 20 | switch self { 21 | case .auth: 22 | return "authorizations/clients/\(GitHubCredential.clientID.rawValue)/\(UUID.init())" 23 | case .gist( _ , let authenticated): 24 | if authenticated { 25 | guard let token = GitHubAPI().token else { fatalError("No Token") } 26 | return "gists?access_token=\(token)" 27 | } else { 28 | return "gists" 29 | } 30 | } 31 | } 32 | 33 | var method: String { 34 | switch self { 35 | case .auth: 36 | return "PUT" 37 | case .gist: 38 | return "POST" 39 | } 40 | } 41 | 42 | var httpBody: Data? { 43 | switch self { 44 | case .auth(let params), .gist(let params, _): 45 | let jsonData = try? JSONSerialization.data(withJSONObject: params) 46 | return jsonData 47 | } 48 | } 49 | 50 | var request: URLRequest? { 51 | guard let url = URL(string: "\(basePath)/\(path)") else { 52 | print("Invalid URL") 53 | return nil 54 | } 55 | 56 | var request = URLRequest(url: url) 57 | request.httpMethod = method 58 | request.httpBody = httpBody 59 | return request 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Xgist.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 315CB4EF1DFC9BD30060F611 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315CB4EE1DFC9BD30060F611 /* AppDelegate.swift */; }; 11 | 315CB4F11DFC9BD30060F611 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 315CB4F01DFC9BD30060F611 /* Assets.xcassets */; }; 12 | 315CB5021DFC9C590060F611 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 315CB5011DFC9C590060F611 /* Cocoa.framework */; }; 13 | 315CB5071DFC9C590060F611 /* SourceEditorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315CB5061DFC9C590060F611 /* SourceEditorExtension.swift */; }; 14 | 315CB5091DFC9C590060F611 /* SourceEditorCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315CB5081DFC9C590060F611 /* SourceEditorCommand.swift */; }; 15 | 315CB50D1DFC9C590060F611 /* XgistEx.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 315CB4FF1DFC9C590060F611 /* XgistEx.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 16 | 31A0C6AD1E9D9A450059B814 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A0C6AC1E9D9A450059B814 /* Keychain.swift */; }; 17 | 31A0C6AE1E9DA0210059B814 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A0C6AC1E9D9A450059B814 /* Keychain.swift */; }; 18 | 31A99D181E8B47350021D6F5 /* GitHubAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A99D151E8B47350021D6F5 /* GitHubAPI.swift */; }; 19 | 31A99D191E8B47350021D6F5 /* GitHubAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A99D151E8B47350021D6F5 /* GitHubAPI.swift */; }; 20 | 31A99D1A1E8B47350021D6F5 /* GitHubCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A99D161E8B47350021D6F5 /* GitHubCredential.swift */; }; 21 | 31A99D1B1E8B47350021D6F5 /* GitHubCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A99D161E8B47350021D6F5 /* GitHubCredential.swift */; }; 22 | 31A99D1C1E8B47350021D6F5 /* GitHubRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A99D171E8B47350021D6F5 /* GitHubRouter.swift */; }; 23 | 31A99D1D1E8B47350021D6F5 /* GitHubRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A99D171E8B47350021D6F5 /* GitHubRouter.swift */; }; 24 | 31CF2DCC1EC34E8C005B0EFA /* TwoFactorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CF2DCB1EC34E8C005B0EFA /* TwoFactorViewController.swift */; }; 25 | 31E20B821EB95C0B00BC2338 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E20B811EB95C0B00BC2338 /* LoginViewController.swift */; }; 26 | DD8619641ED3B27B005BF20A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DD8619631ED3B27B005BF20A /* Main.storyboard */; }; 27 | DD8F78E41E79F6B200124045 /* BezelMessageWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8F78E31E79F6B200124045 /* BezelMessageWindowController.swift */; }; 28 | DD8F78EA1E79F9BC00124045 /* BezelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8F78E91E79F9BC00124045 /* BezelMessage.swift */; }; 29 | DD8F78EB1E79F9BC00124045 /* BezelMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8F78E91E79F9BC00124045 /* BezelMessage.swift */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | 315CB50B1DFC9C590060F611 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 315CB4E31DFC9BD30060F611 /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 315CB4FE1DFC9C590060F611; 38 | remoteInfo = XgistEx; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXCopyFilesBuildPhase section */ 43 | 315CB5111DFC9C590060F611 /* Embed App Extensions */ = { 44 | isa = PBXCopyFilesBuildPhase; 45 | buildActionMask = 2147483647; 46 | dstPath = ""; 47 | dstSubfolderSpec = 13; 48 | files = ( 49 | 315CB50D1DFC9C590060F611 /* XgistEx.appex in Embed App Extensions */, 50 | ); 51 | name = "Embed App Extensions"; 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXCopyFilesBuildPhase section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | 315CB4EB1DFC9BD30060F611 /* Xgist.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Xgist.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 315CB4EE1DFC9BD30060F611 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | 315CB4F01DFC9BD30060F611 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 60 | 315CB4F51DFC9BD30060F611 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 315CB4FF1DFC9C590060F611 /* XgistEx.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = XgistEx.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | 315CB5011DFC9C590060F611 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 63 | 315CB5051DFC9C590060F611 /* XgistEx.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = XgistEx.entitlements; sourceTree = ""; }; 64 | 315CB5061DFC9C590060F611 /* SourceEditorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorExtension.swift; sourceTree = ""; }; 65 | 315CB5081DFC9C590060F611 /* SourceEditorCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorCommand.swift; sourceTree = ""; }; 66 | 315CB50A1DFC9C590060F611 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | 31A0C6AB1E9D99D40059B814 /* Xgist.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Xgist.entitlements; sourceTree = ""; }; 68 | 31A0C6AC1E9D9A450059B814 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; 69 | 31A99D151E8B47350021D6F5 /* GitHubAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubAPI.swift; path = Shared/GitHubAPI.swift; sourceTree = ""; }; 70 | 31A99D161E8B47350021D6F5 /* GitHubCredential.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubCredential.swift; path = Shared/GitHubCredential.swift; sourceTree = ""; }; 71 | 31A99D171E8B47350021D6F5 /* GitHubRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GitHubRouter.swift; path = Shared/GitHubRouter.swift; sourceTree = ""; }; 72 | 31CF2DCB1EC34E8C005B0EFA /* TwoFactorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoFactorViewController.swift; sourceTree = ""; }; 73 | 31E20B811EB95C0B00BC2338 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 74 | DD8619631ED3B27B005BF20A /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 75 | DD8F78E31E79F6B200124045 /* BezelMessageWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BezelMessageWindowController.swift; sourceTree = ""; }; 76 | DD8F78E91E79F9BC00124045 /* BezelMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BezelMessage.swift; path = Shared/BezelMessage.swift; sourceTree = ""; }; 77 | /* End PBXFileReference section */ 78 | 79 | /* Begin PBXFrameworksBuildPhase section */ 80 | 315CB4E81DFC9BD30060F611 /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | 315CB4FC1DFC9C590060F611 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | 315CB5021DFC9C590060F611 /* Cocoa.framework in Frameworks */, 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXFrameworksBuildPhase section */ 96 | 97 | /* Begin PBXGroup section */ 98 | 315CB4E21DFC9BD30060F611 = { 99 | isa = PBXGroup; 100 | children = ( 101 | DD8F78E51E79F96B00124045 /* Shared */, 102 | 315CB4ED1DFC9BD30060F611 /* Xgist */, 103 | 315CB5031DFC9C590060F611 /* XgistEx */, 104 | 315CB5001DFC9C590060F611 /* Frameworks */, 105 | 315CB4EC1DFC9BD30060F611 /* Products */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | 315CB4EC1DFC9BD30060F611 /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 315CB4EB1DFC9BD30060F611 /* Xgist.app */, 113 | 315CB4FF1DFC9C590060F611 /* XgistEx.appex */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 315CB4ED1DFC9BD30060F611 /* Xgist */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 31A0C6AB1E9D99D40059B814 /* Xgist.entitlements */, 122 | DD8F78E21E79F6A700124045 /* Controllers */, 123 | DD8F78E11E79F69100124045 /* Resources */, 124 | DD8F78E01E79F68D00124045 /* Bootstrap */, 125 | ); 126 | path = Xgist; 127 | sourceTree = ""; 128 | }; 129 | 315CB5001DFC9C590060F611 /* Frameworks */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 315CB5011DFC9C590060F611 /* Cocoa.framework */, 133 | ); 134 | name = Frameworks; 135 | sourceTree = ""; 136 | }; 137 | 315CB5031DFC9C590060F611 /* XgistEx */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 315CB5061DFC9C590060F611 /* SourceEditorExtension.swift */, 141 | 315CB5081DFC9C590060F611 /* SourceEditorCommand.swift */, 142 | 315CB50A1DFC9C590060F611 /* Info.plist */, 143 | 315CB5041DFC9C590060F611 /* Supporting Files */, 144 | ); 145 | path = XgistEx; 146 | sourceTree = ""; 147 | }; 148 | 315CB5041DFC9C590060F611 /* Supporting Files */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 315CB5051DFC9C590060F611 /* XgistEx.entitlements */, 152 | ); 153 | name = "Supporting Files"; 154 | sourceTree = ""; 155 | }; 156 | 31CA8EF21E8A018900D6F11E /* GitHub */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 31A99D151E8B47350021D6F5 /* GitHubAPI.swift */, 160 | 31A99D161E8B47350021D6F5 /* GitHubCredential.swift */, 161 | 31A99D171E8B47350021D6F5 /* GitHubRouter.swift */, 162 | ); 163 | name = GitHub; 164 | sourceTree = ""; 165 | }; 166 | DD8F78E01E79F68D00124045 /* Bootstrap */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 315CB4EE1DFC9BD30060F611 /* AppDelegate.swift */, 170 | ); 171 | name = Bootstrap; 172 | sourceTree = ""; 173 | }; 174 | DD8F78E11E79F69100124045 /* Resources */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 315CB4F01DFC9BD30060F611 /* Assets.xcassets */, 178 | 315CB4F51DFC9BD30060F611 /* Info.plist */, 179 | DD8619631ED3B27B005BF20A /* Main.storyboard */, 180 | ); 181 | name = Resources; 182 | sourceTree = ""; 183 | }; 184 | DD8F78E21E79F6A700124045 /* Controllers */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | DD8F78E31E79F6B200124045 /* BezelMessageWindowController.swift */, 188 | 31E20B811EB95C0B00BC2338 /* LoginViewController.swift */, 189 | 31CF2DCB1EC34E8C005B0EFA /* TwoFactorViewController.swift */, 190 | ); 191 | name = Controllers; 192 | sourceTree = ""; 193 | }; 194 | DD8F78E51E79F96B00124045 /* Shared */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 31CA8EF21E8A018900D6F11E /* GitHub */, 198 | DD8F78E91E79F9BC00124045 /* BezelMessage.swift */, 199 | 31A0C6AC1E9D9A450059B814 /* Keychain.swift */, 200 | ); 201 | name = Shared; 202 | sourceTree = ""; 203 | }; 204 | /* End PBXGroup section */ 205 | 206 | /* Begin PBXNativeTarget section */ 207 | 315CB4EA1DFC9BD30060F611 /* Xgist */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = 315CB4F81DFC9BD30060F611 /* Build configuration list for PBXNativeTarget "Xgist" */; 210 | buildPhases = ( 211 | 315CB4E71DFC9BD30060F611 /* Sources */, 212 | 315CB4E81DFC9BD30060F611 /* Frameworks */, 213 | 315CB4E91DFC9BD30060F611 /* Resources */, 214 | 315CB5111DFC9C590060F611 /* Embed App Extensions */, 215 | ); 216 | buildRules = ( 217 | ); 218 | dependencies = ( 219 | 315CB50C1DFC9C590060F611 /* PBXTargetDependency */, 220 | ); 221 | name = Xgist; 222 | productName = Xgist; 223 | productReference = 315CB4EB1DFC9BD30060F611 /* Xgist.app */; 224 | productType = "com.apple.product-type.application"; 225 | }; 226 | 315CB4FE1DFC9C590060F611 /* XgistEx */ = { 227 | isa = PBXNativeTarget; 228 | buildConfigurationList = 315CB50E1DFC9C590060F611 /* Build configuration list for PBXNativeTarget "XgistEx" */; 229 | buildPhases = ( 230 | 315CB4FB1DFC9C590060F611 /* Sources */, 231 | 315CB4FC1DFC9C590060F611 /* Frameworks */, 232 | 315CB4FD1DFC9C590060F611 /* Resources */, 233 | ); 234 | buildRules = ( 235 | ); 236 | dependencies = ( 237 | ); 238 | name = XgistEx; 239 | productName = XgistEx; 240 | productReference = 315CB4FF1DFC9C590060F611 /* XgistEx.appex */; 241 | productType = "com.apple.product-type.xcode-extension"; 242 | }; 243 | /* End PBXNativeTarget section */ 244 | 245 | /* Begin PBXProject section */ 246 | 315CB4E31DFC9BD30060F611 /* Project object */ = { 247 | isa = PBXProject; 248 | attributes = { 249 | LastSwiftUpdateCheck = 0810; 250 | LastUpgradeCheck = 0830; 251 | ORGANIZATIONNAME = "Fernando Bunn"; 252 | TargetAttributes = { 253 | 315CB4EA1DFC9BD30060F611 = { 254 | CreatedOnToolsVersion = 8.1; 255 | DevelopmentTeam = 8C7439RJLG; 256 | ProvisioningStyle = Automatic; 257 | SystemCapabilities = { 258 | com.apple.Keychain = { 259 | enabled = 1; 260 | }; 261 | }; 262 | }; 263 | 315CB4FE1DFC9C590060F611 = { 264 | CreatedOnToolsVersion = 8.1; 265 | DevelopmentTeam = 8C7439RJLG; 266 | ProvisioningStyle = Automatic; 267 | SystemCapabilities = { 268 | com.apple.Keychain = { 269 | enabled = 1; 270 | }; 271 | }; 272 | }; 273 | }; 274 | }; 275 | buildConfigurationList = 315CB4E61DFC9BD30060F611 /* Build configuration list for PBXProject "Xgist" */; 276 | compatibilityVersion = "Xcode 3.2"; 277 | developmentRegion = English; 278 | hasScannedForEncodings = 0; 279 | knownRegions = ( 280 | en, 281 | Base, 282 | ); 283 | mainGroup = 315CB4E21DFC9BD30060F611; 284 | productRefGroup = 315CB4EC1DFC9BD30060F611 /* Products */; 285 | projectDirPath = ""; 286 | projectRoot = ""; 287 | targets = ( 288 | 315CB4EA1DFC9BD30060F611 /* Xgist */, 289 | 315CB4FE1DFC9C590060F611 /* XgistEx */, 290 | ); 291 | }; 292 | /* End PBXProject section */ 293 | 294 | /* Begin PBXResourcesBuildPhase section */ 295 | 315CB4E91DFC9BD30060F611 /* Resources */ = { 296 | isa = PBXResourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | DD8619641ED3B27B005BF20A /* Main.storyboard in Resources */, 300 | 315CB4F11DFC9BD30060F611 /* Assets.xcassets in Resources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 315CB4FD1DFC9C590060F611 /* Resources */ = { 305 | isa = PBXResourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | /* End PBXResourcesBuildPhase section */ 312 | 313 | /* Begin PBXSourcesBuildPhase section */ 314 | 315CB4E71DFC9BD30060F611 /* Sources */ = { 315 | isa = PBXSourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | DD8F78EA1E79F9BC00124045 /* BezelMessage.swift in Sources */, 319 | 31CF2DCC1EC34E8C005B0EFA /* TwoFactorViewController.swift in Sources */, 320 | 315CB4EF1DFC9BD30060F611 /* AppDelegate.swift in Sources */, 321 | DD8F78E41E79F6B200124045 /* BezelMessageWindowController.swift in Sources */, 322 | 31A99D1A1E8B47350021D6F5 /* GitHubCredential.swift in Sources */, 323 | 31E20B821EB95C0B00BC2338 /* LoginViewController.swift in Sources */, 324 | 31A99D181E8B47350021D6F5 /* GitHubAPI.swift in Sources */, 325 | 31A99D1C1E8B47350021D6F5 /* GitHubRouter.swift in Sources */, 326 | 31A0C6AD1E9D9A450059B814 /* Keychain.swift in Sources */, 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | 315CB4FB1DFC9C590060F611 /* Sources */ = { 331 | isa = PBXSourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | DD8F78EB1E79F9BC00124045 /* BezelMessage.swift in Sources */, 335 | 315CB5071DFC9C590060F611 /* SourceEditorExtension.swift in Sources */, 336 | 315CB5091DFC9C590060F611 /* SourceEditorCommand.swift in Sources */, 337 | 31A99D1B1E8B47350021D6F5 /* GitHubCredential.swift in Sources */, 338 | 31A99D191E8B47350021D6F5 /* GitHubAPI.swift in Sources */, 339 | 31A99D1D1E8B47350021D6F5 /* GitHubRouter.swift in Sources */, 340 | 31A0C6AE1E9DA0210059B814 /* Keychain.swift in Sources */, 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | }; 344 | /* End PBXSourcesBuildPhase section */ 345 | 346 | /* Begin PBXTargetDependency section */ 347 | 315CB50C1DFC9C590060F611 /* PBXTargetDependency */ = { 348 | isa = PBXTargetDependency; 349 | target = 315CB4FE1DFC9C590060F611 /* XgistEx */; 350 | targetProxy = 315CB50B1DFC9C590060F611 /* PBXContainerItemProxy */; 351 | }; 352 | /* End PBXTargetDependency section */ 353 | 354 | /* Begin XCBuildConfiguration section */ 355 | 315CB4F61DFC9BD30060F611 /* Debug */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ALWAYS_SEARCH_USER_PATHS = NO; 359 | CLANG_ANALYZER_NONNULL = YES; 360 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 361 | CLANG_CXX_LIBRARY = "libc++"; 362 | CLANG_ENABLE_MODULES = YES; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_WARN_BOOL_CONVERSION = YES; 365 | CLANG_WARN_CONSTANT_CONVERSION = YES; 366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 367 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 368 | CLANG_WARN_EMPTY_BODY = YES; 369 | CLANG_WARN_ENUM_CONVERSION = YES; 370 | CLANG_WARN_INFINITE_RECURSION = YES; 371 | CLANG_WARN_INT_CONVERSION = YES; 372 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 373 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 374 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 375 | CLANG_WARN_UNREACHABLE_CODE = YES; 376 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 377 | CODE_SIGN_IDENTITY = "-"; 378 | COPY_PHASE_STRIP = NO; 379 | DEBUG_INFORMATION_FORMAT = dwarf; 380 | ENABLE_STRICT_OBJC_MSGSEND = YES; 381 | ENABLE_TESTABILITY = YES; 382 | GCC_C_LANGUAGE_STANDARD = gnu99; 383 | GCC_DYNAMIC_NO_PIC = NO; 384 | GCC_NO_COMMON_BLOCKS = YES; 385 | GCC_OPTIMIZATION_LEVEL = 0; 386 | GCC_PREPROCESSOR_DEFINITIONS = ( 387 | "DEBUG=1", 388 | "$(inherited)", 389 | ); 390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 392 | GCC_WARN_UNDECLARED_SELECTOR = YES; 393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 394 | GCC_WARN_UNUSED_FUNCTION = YES; 395 | GCC_WARN_UNUSED_VARIABLE = YES; 396 | MACOSX_DEPLOYMENT_TARGET = 10.12; 397 | MTL_ENABLE_DEBUG_INFO = YES; 398 | ONLY_ACTIVE_ARCH = YES; 399 | SDKROOT = macosx; 400 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 401 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 402 | }; 403 | name = Debug; 404 | }; 405 | 315CB4F71DFC9BD30060F611 /* Release */ = { 406 | isa = XCBuildConfiguration; 407 | buildSettings = { 408 | ALWAYS_SEARCH_USER_PATHS = NO; 409 | CLANG_ANALYZER_NONNULL = YES; 410 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 411 | CLANG_CXX_LIBRARY = "libc++"; 412 | CLANG_ENABLE_MODULES = YES; 413 | CLANG_ENABLE_OBJC_ARC = YES; 414 | CLANG_WARN_BOOL_CONVERSION = YES; 415 | CLANG_WARN_CONSTANT_CONVERSION = YES; 416 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 417 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 418 | CLANG_WARN_EMPTY_BODY = YES; 419 | CLANG_WARN_ENUM_CONVERSION = YES; 420 | CLANG_WARN_INFINITE_RECURSION = YES; 421 | CLANG_WARN_INT_CONVERSION = YES; 422 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 423 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 424 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 425 | CLANG_WARN_UNREACHABLE_CODE = YES; 426 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 427 | CODE_SIGN_IDENTITY = "-"; 428 | COPY_PHASE_STRIP = NO; 429 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 430 | ENABLE_NS_ASSERTIONS = NO; 431 | ENABLE_STRICT_OBJC_MSGSEND = YES; 432 | GCC_C_LANGUAGE_STANDARD = gnu99; 433 | GCC_NO_COMMON_BLOCKS = YES; 434 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 435 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 436 | GCC_WARN_UNDECLARED_SELECTOR = YES; 437 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 438 | GCC_WARN_UNUSED_FUNCTION = YES; 439 | GCC_WARN_UNUSED_VARIABLE = YES; 440 | MACOSX_DEPLOYMENT_TARGET = 10.12; 441 | MTL_ENABLE_DEBUG_INFO = NO; 442 | SDKROOT = macosx; 443 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 444 | }; 445 | name = Release; 446 | }; 447 | 315CB4F91DFC9BD30060F611 /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 451 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 452 | CODE_SIGN_ENTITLEMENTS = Xgist/Xgist.entitlements; 453 | CODE_SIGN_IDENTITY = "Mac Developer"; 454 | COMBINE_HIDPI_IMAGES = YES; 455 | DEVELOPMENT_TEAM = 8C7439RJLG; 456 | INFOPLIST_FILE = Xgist/Info.plist; 457 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 458 | PRODUCT_BUNDLE_IDENTIFIER = com.idevzilla.Xgist.release; 459 | PRODUCT_NAME = "$(TARGET_NAME)"; 460 | SWIFT_VERSION = 3.0; 461 | }; 462 | name = Debug; 463 | }; 464 | 315CB4FA1DFC9BD30060F611 /* Release */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 468 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 469 | CODE_SIGN_ENTITLEMENTS = Xgist/Xgist.entitlements; 470 | CODE_SIGN_IDENTITY = "Mac Developer"; 471 | COMBINE_HIDPI_IMAGES = YES; 472 | DEVELOPMENT_TEAM = 8C7439RJLG; 473 | INFOPLIST_FILE = Xgist/Info.plist; 474 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 475 | PRODUCT_BUNDLE_IDENTIFIER = com.idevzilla.Xgist.release; 476 | PRODUCT_NAME = "$(TARGET_NAME)"; 477 | SWIFT_VERSION = 3.0; 478 | }; 479 | name = Release; 480 | }; 481 | 315CB50F1DFC9C590060F611 /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | CODE_SIGN_ENTITLEMENTS = XgistEx/XgistEx.entitlements; 485 | CODE_SIGN_IDENTITY = "Mac Developer"; 486 | COMBINE_HIDPI_IMAGES = YES; 487 | DEVELOPMENT_TEAM = 8C7439RJLG; 488 | INFOPLIST_FILE = XgistEx/Info.plist; 489 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; 490 | MACOSX_DEPLOYMENT_TARGET = 10.11; 491 | PRODUCT_BUNDLE_IDENTIFIER = com.idevzilla.Xgist.release.XgistEx; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | SKIP_INSTALL = YES; 494 | SWIFT_VERSION = 3.0; 495 | }; 496 | name = Debug; 497 | }; 498 | 315CB5101DFC9C590060F611 /* Release */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | CODE_SIGN_ENTITLEMENTS = XgistEx/XgistEx.entitlements; 502 | CODE_SIGN_IDENTITY = "Mac Developer"; 503 | COMBINE_HIDPI_IMAGES = YES; 504 | DEVELOPMENT_TEAM = 8C7439RJLG; 505 | INFOPLIST_FILE = XgistEx/Info.plist; 506 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; 507 | MACOSX_DEPLOYMENT_TARGET = 10.11; 508 | PRODUCT_BUNDLE_IDENTIFIER = com.idevzilla.Xgist.release.XgistEx; 509 | PRODUCT_NAME = "$(TARGET_NAME)"; 510 | SKIP_INSTALL = YES; 511 | SWIFT_VERSION = 3.0; 512 | }; 513 | name = Release; 514 | }; 515 | /* End XCBuildConfiguration section */ 516 | 517 | /* Begin XCConfigurationList section */ 518 | 315CB4E61DFC9BD30060F611 /* Build configuration list for PBXProject "Xgist" */ = { 519 | isa = XCConfigurationList; 520 | buildConfigurations = ( 521 | 315CB4F61DFC9BD30060F611 /* Debug */, 522 | 315CB4F71DFC9BD30060F611 /* Release */, 523 | ); 524 | defaultConfigurationIsVisible = 0; 525 | defaultConfigurationName = Release; 526 | }; 527 | 315CB4F81DFC9BD30060F611 /* Build configuration list for PBXNativeTarget "Xgist" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | 315CB4F91DFC9BD30060F611 /* Debug */, 531 | 315CB4FA1DFC9BD30060F611 /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | 315CB50E1DFC9C590060F611 /* Build configuration list for PBXNativeTarget "XgistEx" */ = { 537 | isa = XCConfigurationList; 538 | buildConfigurations = ( 539 | 315CB50F1DFC9C590060F611 /* Debug */, 540 | 315CB5101DFC9C590060F611 /* Release */, 541 | ); 542 | defaultConfigurationIsVisible = 0; 543 | defaultConfigurationName = Release; 544 | }; 545 | /* End XCConfigurationList section */ 546 | }; 547 | rootObject = 315CB4E31DFC9BD30060F611 /* Project object */; 548 | } 549 | -------------------------------------------------------------------------------- /Xgist.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Xgist/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Xgist 4 | // 5 | // Created by Fernando Bunn on 10/12/16. 6 | // Copyright © 2016 Fernando Bunn. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | 16 | private var statusWindowController: BezelMessageWindowController! 17 | 18 | private var didOpenURL = false 19 | 20 | func applicationWillFinishLaunching(_ notification: Notification) { 21 | NSApp.setActivationPolicy(.prohibited) 22 | 23 | NSAppleEventManager.shared().setEventHandler(self, 24 | andSelector: #selector(handleURLEvent(_:replyEvent:)), 25 | forEventClass: UInt32(kInternetEventClass), 26 | andEventID: UInt32(kAEGetURL)) 27 | } 28 | 29 | func applicationDidFinishLaunching(_ aNotification: Notification) { 30 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { 31 | if !self.didOpenURL { 32 | // Application was launched normally, just show 33 | NSApp.setActivationPolicy(.regular) 34 | NSApp.activate(ignoringOtherApps: true) 35 | } 36 | } 37 | } 38 | 39 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 40 | return true 41 | } 42 | 43 | func handleURLEvent(_ event: NSAppleEventDescriptor!, replyEvent: NSAppleEventDescriptor!) { 44 | guard let urlString = event.paramDescriptor(forKeyword: UInt32(keyDirectObject))?.stringValue else { return } 45 | guard let url = URL(string: urlString) else { return } 46 | guard let message = BezelMessage(url: url) else { return } 47 | 48 | didOpenURL = true 49 | 50 | showBezelMessage(message) 51 | } 52 | 53 | private func showBezelMessage(_ message: BezelMessage) { 54 | statusWindowController = BezelMessageWindowController(status: message) 55 | 56 | statusWindowController.show(for: 3.0) { 57 | NSApp.terminate(nil) 58 | } 59 | } 60 | 61 | @objc private func killAgent() { 62 | NSApp.terminate(nil) 63 | } 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /Xgist/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Xgist/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Xgist/Assets.xcassets/copy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "copy.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /Xgist/Assets.xcassets/copy.imageset/copy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bunn/Xgist/580fc09856c1e7e5d257832717c56db82d7fa99d/Xgist/Assets.xcassets/copy.imageset/copy.pdf -------------------------------------------------------------------------------- /Xgist/Assets.xcassets/message.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "message.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /Xgist/Assets.xcassets/message.imageset/message.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bunn/Xgist/580fc09856c1e7e5d257832717c56db82d7fa99d/Xgist/Assets.xcassets/message.imageset/message.pdf -------------------------------------------------------------------------------- /Xgist/BezelMessageWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BezelMessageWindowController.swift 3 | // Xgist 4 | // 5 | // Created by Guilherme Rambo on 15/03/17. 6 | // Copyright © 2017 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | extension BezelMessage { 12 | 13 | fileprivate var image: NSImage? { 14 | return NSImage(named: imageName) 15 | } 16 | 17 | } 18 | 19 | final class BezelMessageWindowController: NSWindowController { 20 | 21 | private struct Metrics { 22 | static let defaultY: CGFloat = 280.0 23 | static let size = NSSize(width: 200, height: 200) 24 | static let cornerRadius: CGFloat = 18.0 25 | static let iconSize: CGFloat = 82.0 26 | } 27 | 28 | var status: BezelMessage { 29 | didSet { 30 | updateUI() 31 | } 32 | } 33 | 34 | init(status: BezelMessage) { 35 | self.status = status 36 | 37 | let rect = NSRect(origin: .zero, size: Metrics.size) 38 | let window = NSWindow(contentRect: rect, styleMask: .borderless, backing: .buffered, defer: false) 39 | 40 | super.init(window: window) 41 | 42 | position(window) 43 | 44 | windowDidLoad() 45 | updateUI() 46 | } 47 | 48 | required init?(coder: NSCoder) { 49 | fatalError("init(coder:) has not been implemented") 50 | } 51 | 52 | private func position(_ window: NSWindow) { 53 | let offset: CGFloat 54 | 55 | if let screen = NSScreen.main() { 56 | offset = screen.frame.height - screen.visibleFrame.height 57 | } else { 58 | offset = 0 59 | } 60 | 61 | window.center() 62 | 63 | var adjustedRect = window.frame 64 | adjustedRect.origin.y = Metrics.defaultY - offset / 2 - window.frame.height / 2 65 | window.setFrame(adjustedRect, display: false) 66 | } 67 | 68 | private lazy var vfxView: NSVisualEffectView = { 69 | let v = NSVisualEffectView(frame: .zero) 70 | 71 | v.state = .active 72 | v.blendingMode = .behindWindow 73 | v.material = .dark 74 | v.appearance = NSAppearance(named: NSAppearanceNameVibrantDark) 75 | v.maskImage = self.maskImage(with: Metrics.cornerRadius) 76 | 77 | return v 78 | }() 79 | 80 | private lazy var imageView: NSImageView = { 81 | let v = NSImageView(frame: .zero) 82 | 83 | v.heightAnchor.constraint(equalToConstant: Metrics.iconSize).isActive = true 84 | v.widthAnchor.constraint(equalToConstant: Metrics.iconSize).isActive = true 85 | 86 | return v 87 | }() 88 | 89 | private lazy var label: NSTextField = { 90 | let f = NSTextField(frame: .zero) 91 | 92 | f.isEditable = false 93 | f.drawsBackground = false 94 | f.isBezeled = false 95 | f.isBordered = false 96 | f.isSelectable = false 97 | f.textColor = .labelColor 98 | f.font = NSFont.systemFont(ofSize: 18.0) 99 | f.alignment = .center 100 | f.lineBreakMode = .byWordWrapping 101 | f.setContentHuggingPriority(NSLayoutPriorityRequired, for: .horizontal) 102 | f.setContentCompressionResistancePriority(NSLayoutPriorityDefaultLow, for: .horizontal) 103 | 104 | return f 105 | }() 106 | 107 | private lazy var stackView: NSStackView = { 108 | let v = NSStackView(views: [self.imageView, self.label]) 109 | 110 | v.orientation = .vertical 111 | v.spacing = 18 112 | v.translatesAutoresizingMaskIntoConstraints = false 113 | 114 | return v 115 | }() 116 | 117 | private func maskImage(with cornerRadius: CGFloat) -> NSImage { 118 | let edgeLength = 2.0 * cornerRadius + 1.0 119 | let size = NSSize(width: edgeLength, height: edgeLength) 120 | 121 | let image = NSImage(size: size, flipped: false) { rect in 122 | NSColor.black.set() 123 | 124 | let bezierPath = NSBezierPath(roundedRect: rect, 125 | xRadius: cornerRadius, 126 | yRadius: cornerRadius) 127 | 128 | bezierPath.fill() 129 | 130 | return true 131 | } 132 | 133 | image.capInsets = EdgeInsets(top: cornerRadius, 134 | left: cornerRadius, 135 | bottom: cornerRadius, 136 | right: cornerRadius) 137 | image.resizingMode = .stretch 138 | 139 | return image 140 | } 141 | 142 | override func windowDidLoad() { 143 | super.windowDidLoad() 144 | 145 | guard let window = window else { return } 146 | 147 | window.isOpaque = false 148 | window.backgroundColor = .clear 149 | 150 | window.level = Int(CGWindowLevelForKey(.popUpMenuWindow)) 151 | window.collectionBehavior = [.moveToActiveSpace, .ignoresCycle, .stationary] 152 | 153 | window.contentView = vfxView 154 | 155 | vfxView.addSubview(stackView) 156 | stackView.centerYAnchor.constraint(equalTo: vfxView.centerYAnchor).isActive = true 157 | stackView.leadingAnchor.constraint(equalTo: vfxView.leadingAnchor, constant: 22.0).isActive = true 158 | stackView.trailingAnchor.constraint(equalTo: vfxView.trailingAnchor, constant: -22.0).isActive = true 159 | } 160 | 161 | func show(for duration: TimeInterval, completion: (() -> Void)? = nil) { 162 | guard let window = window else { return } 163 | 164 | window.alphaValue = 0 165 | 166 | showWindow(nil) 167 | 168 | window.animator().alphaValue = 1 169 | 170 | DispatchQueue.main.asyncAfter(deadline: .now() + duration) { 171 | NSAnimationContext.runAnimationGroup({ _ in 172 | window.animator().alphaValue = 0 173 | }, completionHandler: { 174 | self.close() 175 | completion?() 176 | }) 177 | } 178 | } 179 | 180 | private func updateUI() { 181 | imageView.image = status.image 182 | label.stringValue = status.title 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /Xgist/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.3 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleURLName 25 | Bezel Message 26 | CFBundleURLSchemes 27 | 28 | XgistMessage 29 | 30 | 31 | 32 | CFBundleVersion 33 | 1 34 | LSMinimumSystemVersion 35 | $(MACOSX_DEPLOYMENT_TARGET) 36 | NSHumanReadableCopyright 37 | Copyright © 2016 Fernando Bunn. All rights reserved. 38 | NSMainStoryboardFile 39 | Main 40 | NSPrincipalClass 41 | NSApplication 42 | 43 | 44 | -------------------------------------------------------------------------------- /Xgist/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // Xgist 4 | // 5 | // Created by Fernando Bunn on 02/05/17. 6 | // Copyright © 2017 Fernando Bunn. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | enum SegueIdentifier: String { 12 | case twoFactorAuthenticate = "showTwoFactorController" 13 | } 14 | 15 | enum DefaultKeys: String { 16 | case username = "usernameKey" 17 | } 18 | 19 | class LoginViewController: NSViewController { 20 | @IBOutlet weak var usernameTextField: NSTextField! 21 | @IBOutlet weak var passwordTextField: NSTextField! 22 | @IBOutlet weak var headerText: NSTextField! 23 | @IBOutlet weak var actionButton: NSButton! 24 | @IBOutlet weak var spinner: NSProgressIndicator! 25 | private let githubAPI = GitHubAPI() 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | setupUI() 30 | } 31 | 32 | private func setupUI() { 33 | usernameTextField.isEnabled = !githubAPI.isAuthenticated 34 | passwordTextField.isEnabled = !githubAPI.isAuthenticated; 35 | showSpinner(show: false) 36 | passwordTextField.stringValue = "" 37 | headerText.textColor = NSColor.black 38 | 39 | if githubAPI.isAuthenticated { 40 | actionButton.title = "Logout" 41 | headerText.stringValue = "You are logged in" 42 | if let name = UserDefaults.standard.string(forKey: DefaultKeys.username.rawValue) { 43 | usernameTextField.stringValue = name 44 | } 45 | } else { 46 | actionButton.title = "Login" 47 | headerText.stringValue = "To create authenticated Gists, fill out the following information:" 48 | usernameTextField.stringValue = "" 49 | passwordTextField.stringValue = "" 50 | } 51 | } 52 | 53 | private func showSpinner(show: Bool) { 54 | spinner.isHidden = !show 55 | actionButton.isHidden = show 56 | if show { 57 | spinner.startAnimation(nil) 58 | } else { 59 | spinner.stopAnimation(nil) 60 | } 61 | } 62 | 63 | private func displayError(message: String) { 64 | showSpinner(show: false) 65 | headerText.stringValue = message 66 | headerText.textColor = NSColor.red 67 | } 68 | 69 | fileprivate func authenticate(twoFactorCode: String? = nil) { 70 | showSpinner(show: true) 71 | githubAPI.authenticate(username: usernameTextField.stringValue, password: passwordTextField.stringValue, twoFactorCode: twoFactorCode) { (error: Error?) in 72 | print("Error \(String(describing: error))") 73 | DispatchQueue.main.async { 74 | if error != nil { 75 | if let apiError = error as? GitHubAPI.GitHubAPIError { 76 | switch apiError { 77 | case .twoFactorRequired: 78 | self.openTwoFactorController() 79 | return 80 | default: break 81 | } 82 | } 83 | self.displayError(message: "Bad username or password") 84 | } else { 85 | UserDefaults.standard.set(self.usernameTextField.stringValue, forKey: DefaultKeys.username.rawValue) 86 | self.setupUI() 87 | } 88 | } 89 | } 90 | } 91 | 92 | @IBAction func actionButtonClicked(_ sender: NSButton) { 93 | if githubAPI.isAuthenticated { 94 | githubAPI.logout() 95 | setupUI() 96 | } else { 97 | authenticate() 98 | } 99 | } 100 | 101 | private func openTwoFactorController() { 102 | performSegue(withIdentifier: SegueIdentifier.twoFactorAuthenticate.rawValue, sender: nil) 103 | } 104 | 105 | override func prepare(for segue: NSStoryboardSegue, sender: Any?) { 106 | guard let segueIdentifier = segue.identifier else { return } 107 | 108 | switch segueIdentifier { 109 | case SegueIdentifier.twoFactorAuthenticate.rawValue: 110 | if let controller = segue.destinationController as? TwoFactorViewController { 111 | controller.delegate = self 112 | } 113 | break 114 | default: 115 | print("No segue found") 116 | break 117 | } 118 | } 119 | } 120 | 121 | extension LoginViewController: TwoFactorViewControllerDelegate { 122 | func didEnter(code: String, controller: TwoFactorViewController) { 123 | dismissViewController(controller) 124 | authenticate(twoFactorCode: code) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Xgist/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 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 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 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | Default 512 | 513 | 514 | 515 | 516 | 517 | 518 | Left to Right 519 | 520 | 521 | 522 | 523 | 524 | 525 | Right to Left 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | Default 537 | 538 | 539 | 540 | 541 | 542 | 543 | Left to Right 544 | 545 | 546 | 547 | 548 | 549 | 550 | Right to Left 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | -------------------------------------------------------------------------------- /Xgist/TwoFactorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TwoFactorViewController.swift 3 | // Xgist 4 | // 5 | // Created by Fernando Bunn on 10/05/17. 6 | // Copyright © 2017 Fernando Bunn. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TwoFactorViewController: NSViewController { 12 | @IBOutlet weak var codeTextField: NSTextField! 13 | weak var delegate: TwoFactorViewControllerDelegate? 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | } 18 | 19 | @IBAction func authenticateButtonClicked(_ sender: NSButton) { 20 | delegate?.didEnter(code: codeTextField.stringValue, controller: self) 21 | dismiss(nil) 22 | } 23 | } 24 | 25 | protocol TwoFactorViewControllerDelegate: class { 26 | func didEnter(code: String, controller: TwoFactorViewController) 27 | } 28 | -------------------------------------------------------------------------------- /Xgist/Xgist.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | keychain-access-groups 6 | 7 | $(AppIdentifierPrefix)com.idevzilla.Xgist 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /XgistEx/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Xgist 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Xgist 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.3 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | XCSourceEditorExtensionPrincipalClass 30 | $(PRODUCT_MODULE_NAME).SourceEditorExtension 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.dt.Xcode.extension.source-editor 34 | 35 | NSHumanReadableCopyright 36 | Copyright © 2016 Fernando Bunn. All rights reserved. 37 | 38 | 39 | -------------------------------------------------------------------------------- /XgistEx/SourceEditorCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Xgist 4 | // 5 | // Created by Fernando Bunn on 10/12/16. 6 | // Copyright © 2016 Fernando Bunn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XcodeKit 11 | import AppKit 12 | 13 | class SourceEditorCommand: NSObject, XCSourceEditorCommand { 14 | let gitHubAPI = GitHubAPI() 15 | 16 | func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void { 17 | 18 | guard let textContent = invocation.content else { 19 | completionHandler(nil) 20 | return 21 | } 22 | 23 | gitHubAPI.post(gist: textContent, fileExtension: invocation.codeType, authenticated: invocation.authenticated) { (error, result) in 24 | if let content = result { 25 | self.copyToPasteBoard(value: content) 26 | self.showSuccessMessage() 27 | } 28 | completionHandler(error) 29 | } 30 | } 31 | 32 | private func copyToPasteBoard(value: String) -> Void { 33 | let pasteboard = NSPasteboard.general() 34 | pasteboard.declareTypes([NSPasteboardTypeString], owner: nil) 35 | pasteboard.setString(value, forType: NSPasteboardTypeString) 36 | } 37 | 38 | 39 | //MARK: - UI Agent 40 | 41 | private func showSuccessMessage() { 42 | guard let url = BezelMessage.clipboard.urlEncoded else { return } 43 | _ = NSWorkspace.shared().open(url) 44 | } 45 | } 46 | 47 | fileprivate extension XCSourceEditorCommandInvocation { 48 | 49 | enum CommandType: String { 50 | case anonymous = "SourceEditorCommandAnonymous" 51 | case authenticated = "SourceEditorCommandAuthenticated" 52 | } 53 | 54 | var authenticated: Bool { 55 | return commandType == CommandType.authenticated 56 | } 57 | 58 | private func getTextSelectionFrom(buffer: XCSourceTextBuffer) -> String { 59 | var text = "" 60 | buffer.selections.forEach { selection in 61 | guard let range = selection as? XCSourceTextRange else { return } 62 | 63 | for l in range.start.line...range.end.line { 64 | if l >= buffer.lines.count { 65 | continue 66 | } 67 | guard let line = buffer.lines[l] as? String else { continue } 68 | text.append(line) 69 | } 70 | } 71 | return text 72 | } 73 | 74 | var commandType: CommandType { 75 | if commandIdentifier.contains(CommandType.authenticated.rawValue) { 76 | return .authenticated 77 | } else { 78 | return .anonymous 79 | } 80 | } 81 | 82 | var content: String? { 83 | return getTextSelectionFrom(buffer: buffer) 84 | } 85 | 86 | var codeType: String { 87 | //Github doesn't recognize the type "objective-c" or ".playground" 88 | //There's probably a better way to solve this, but this will do for now 89 | let types = [("objective-c", "m"), 90 | ("com.apple.dt.playground", "playground.swift"), 91 | ("swift","swift"), 92 | ("xml", "xml")] 93 | 94 | for type in types { 95 | if buffer.contentUTI.contains(type.0) { 96 | return type.1 97 | } 98 | } 99 | return buffer.contentUTI 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /XgistEx/SourceEditorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceEditorExtension.swift 3 | // XgistEx 4 | // 5 | // Created by Fernando Bunn on 10/12/16. 6 | // Copyright © 2016 Fernando Bunn. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import XcodeKit 11 | 12 | class SourceEditorExtension: NSObject, XCSourceEditorExtension { 13 | 14 | var commandDefinitions: [[XCSourceEditorCommandDefinitionKey : Any]] { 15 | 16 | let anonymous = keyWith(identifier: "SourceEditorCommandAnonymous", name: "Anonymous Gist") 17 | let authenticated = keyWith(identifier: "SourceEditorCommandAuthenticated", name: "Authenticated Gist") 18 | 19 | var result = [[XCSourceEditorCommandDefinitionKey : Any]]() 20 | result.append(anonymous) 21 | 22 | if GitHubAPI().isAuthenticated { 23 | result.append(authenticated) 24 | } 25 | 26 | return result 27 | } 28 | 29 | func extensionDidFinishLaunching() { 30 | 31 | } 32 | 33 | 34 | fileprivate func keyWith(identifier: String, name: String) -> [XCSourceEditorCommandDefinitionKey : Any] { 35 | return [ 36 | .classNameKey : "XgistEx.SourceEditorCommand", 37 | .identifierKey : "com.idevzilla.XgistEx.\(identifier)", 38 | .nameKey : name 39 | ] 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /XgistEx/XgistEx.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.network.server 10 | 11 | keychain-access-groups 12 | 13 | $(AppIdentifierPrefix)com.idevzilla.Xgist.XgistEx 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /other/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bunn/Xgist/580fc09856c1e7e5d257832717c56db82d7fa99d/other/demo.gif -------------------------------------------------------------------------------- /other/keychain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bunn/Xgist/580fc09856c1e7e5d257832717c56db82d7fa99d/other/keychain.png -------------------------------------------------------------------------------- /other/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bunn/Xgist/580fc09856c1e7e5d257832717c56db82d7fa99d/other/login.png -------------------------------------------------------------------------------- /other/menuOptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bunn/Xgist/580fc09856c1e7e5d257832717c56db82d7fa99d/other/menuOptions.png -------------------------------------------------------------------------------- /other/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bunn/Xgist/580fc09856c1e7e5d257832717c56db82d7fa99d/other/preferences.png -------------------------------------------------------------------------------- /other/xgist_pref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bunn/Xgist/580fc09856c1e7e5d257832717c56db82d7fa99d/other/xgist_pref.png --------------------------------------------------------------------------------