├── .github └── FUNDING.yml ├── .gitignore ├── Cartfile ├── Cartfile.resolved ├── Carthage └── Build │ ├── .Alamofire.version │ ├── .BurntCocoaUI.version │ ├── .BurntFoundation.version │ ├── .Grain.version │ ├── .Ono.version │ ├── .Syrup.version │ └── Mac │ ├── Alamofire.framework.dSYM │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ └── DWARF │ │ └── Alamofire │ ├── Alamofire.framework │ ├── Alamofire │ ├── Headers │ ├── Modules │ ├── Resources │ └── Versions │ │ ├── A │ │ ├── Alamofire │ │ ├── Headers │ │ │ ├── Alamofire-Swift.h │ │ │ └── Alamofire.h │ │ ├── Modules │ │ │ ├── Alamofire.swiftmodule │ │ │ │ ├── arm64-apple-macos.swiftdoc │ │ │ │ ├── arm64-apple-macos.swiftmodule │ │ │ │ ├── arm64.swiftdoc │ │ │ │ ├── arm64.swiftmodule │ │ │ │ ├── x86_64-apple-macos.swiftdoc │ │ │ │ ├── x86_64-apple-macos.swiftmodule │ │ │ │ ├── x86_64.swiftdoc │ │ │ │ └── x86_64.swiftmodule │ │ │ └── module.modulemap │ │ └── Resources │ │ │ └── Info.plist │ │ └── Current │ ├── BurntCocoaUI.framework.dSYM │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ └── DWARF │ │ └── BurntCocoaUI │ ├── BurntCocoaUI.framework │ ├── BurntCocoaUI │ ├── Headers │ ├── Modules │ ├── Resources │ └── Versions │ │ ├── A │ │ ├── BurntCocoaUI │ │ ├── Headers │ │ │ ├── BurntCocoaUI-Swift.h │ │ │ └── BurntCocoaUI.h │ │ ├── Modules │ │ │ ├── BurntCocoaUI.swiftmodule │ │ │ │ ├── arm64-apple-macos.swiftdoc │ │ │ │ ├── arm64-apple-macos.swiftmodule │ │ │ │ ├── arm64.swiftdoc │ │ │ │ ├── arm64.swiftmodule │ │ │ │ ├── x86_64-apple-macos.swiftdoc │ │ │ │ ├── x86_64-apple-macos.swiftmodule │ │ │ │ ├── x86_64.swiftdoc │ │ │ │ └── x86_64.swiftmodule │ │ │ └── module.modulemap │ │ └── Resources │ │ │ └── Info.plist │ │ └── Current │ ├── BurntFoundation.framework.dSYM │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ └── DWARF │ │ └── BurntFoundation │ ├── BurntFoundation.framework │ ├── BurntFoundation │ ├── Headers │ ├── Modules │ ├── Resources │ └── Versions │ │ ├── A │ │ ├── BurntFoundation │ │ ├── Headers │ │ │ ├── BurntFoundation-Swift.h │ │ │ └── BurntFoundation.h │ │ ├── Modules │ │ │ ├── BurntFoundation.swiftmodule │ │ │ │ ├── arm64-apple-macos.swiftdoc │ │ │ │ ├── arm64-apple-macos.swiftmodule │ │ │ │ ├── arm64.swiftdoc │ │ │ │ ├── arm64.swiftmodule │ │ │ │ ├── x86_64-apple-macos.swiftdoc │ │ │ │ ├── x86_64-apple-macos.swiftmodule │ │ │ │ ├── x86_64.swiftdoc │ │ │ │ └── x86_64.swiftmodule │ │ │ └── module.modulemap │ │ └── Resources │ │ │ └── Info.plist │ │ └── Current │ ├── Grain.framework.dSYM │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ └── DWARF │ │ └── Grain │ ├── Grain.framework │ ├── Grain │ ├── Headers │ ├── Modules │ ├── Resources │ └── Versions │ │ ├── A │ │ ├── Grain │ │ ├── Headers │ │ │ ├── Grain-Swift.h │ │ │ └── Grain.h │ │ ├── Modules │ │ │ ├── Grain.swiftmodule │ │ │ │ ├── x86_64.swiftdoc │ │ │ │ └── x86_64.swiftmodule │ │ │ └── module.modulemap │ │ └── Resources │ │ │ └── Info.plist │ │ └── Current │ ├── Ono.framework.dSYM │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ └── DWARF │ │ └── Ono │ ├── Ono.framework │ ├── Headers │ ├── Modules │ ├── Ono │ ├── Resources │ └── Versions │ │ ├── A │ │ ├── Headers │ │ │ ├── ONOXMLDocument.h │ │ │ └── Ono.h │ │ ├── Modules │ │ │ └── module.modulemap │ │ ├── Ono │ │ └── Resources │ │ │ └── Info.plist │ │ └── Current │ ├── Syrup.framework.dSYM │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ └── DWARF │ │ └── Syrup │ └── Syrup.framework │ ├── Headers │ ├── Modules │ ├── Resources │ ├── Syrup │ └── Versions │ ├── A │ ├── Headers │ │ ├── Syrup-Swift.h │ │ └── Syrup.h │ ├── Modules │ │ ├── Syrup.swiftmodule │ │ │ ├── arm64-apple-macos.swiftdoc │ │ │ ├── arm64-apple-macos.swiftmodule │ │ │ ├── arm64.swiftdoc │ │ │ ├── arm64.swiftmodule │ │ │ ├── x86_64-apple-macos.swiftdoc │ │ │ ├── x86_64-apple-macos.swiftmodule │ │ │ ├── x86_64.swiftdoc │ │ │ └── x86_64.swiftmodule │ │ └── module.modulemap │ ├── Resources │ │ └── Info.plist │ └── Syrup │ └── Current ├── LICENSE ├── Lantern.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── Hoverlytics.xccheckout │ │ ├── IDEWorkspaceChecks.plist │ │ ├── Lantern.xccheckout │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ └── pgwsmith.xcuserdatad │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ ├── Lantern Model.xcscheme │ └── Lantern.xcscheme ├── Lantern ├── AppDelegate.swift ├── Base.lproj │ └── Main.storyboard ├── BrowserMenuController.swift ├── BrowserPreferences.swift ├── ContentPreview.storyboard ├── ContentPreview.swift ├── CrawlerMenuController.swift ├── CrawlerPreferences.swift ├── Credits.rtf ├── ImagePreviewViewController.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 256x256-1.png │ │ ├── 256x256.png │ │ ├── 32x32-1.png │ │ ├── 32x32.png │ │ ├── 32x32@2x.png │ │ ├── 512x512-1.png │ │ ├── 512x512.png │ │ ├── 512x512@2x.png │ │ └── Contents.json ├── Info.plist ├── Lantern-Bridging-Header.h ├── Lantern.entitlements ├── MainSection.swift ├── MainState.swift ├── MainView.storyboard ├── MainWindowController.swift ├── MetaViewController.swift ├── MultipleStringPreviewViewController.swift ├── NSEvent+extension.swift ├── NSViewController+PGWSConstraintConvenience.h ├── NSViewController+PGWSConstraintConvenience.m ├── Page.storyboard ├── PageMapper+UI.swift ├── PageViewController.swift ├── SiteMenuItem.swift ├── SiteSettings.storyboard ├── SiteSettingsViewController.swift ├── SourcePreviewViewController.swift ├── StatsViewController.swift ├── TableView-OutlineView-Subclass.swift ├── ValidatedStringValue+Presentation.swift ├── ViewController.swift ├── ViewState.swift ├── WKUserContentController+bundledScripts.swift ├── code-sign-frameworks.sh ├── console.js ├── insertHoverlytics.js ├── panelAuthorizationChanged.js ├── setPanelAuthorizationToken.js ├── userAgent.js └── windowClose.js ├── LanternModel ├── Dictionary+UpdateClosure.swift ├── EnabledFeatures.swift ├── Info.plist ├── LanternModel.h ├── ModelManager.swift ├── NSData+ONOXMLDocument.swift ├── ONOXMLDocument+StringEncoding.swift ├── PageInfo.swift ├── PageInfoRequest.swift ├── PageMapper+CSV.swift ├── PageMapper+Filtering.swift ├── PageMapper+InfoPresenting.swift ├── PageMapper+LinkFiltering.swift ├── PageMapper+Validating.swift ├── PageMapper.swift ├── Site.swift ├── SiteEssentialsInfo.swift ├── SystemDirectory.swift ├── UniqueURLArray.swift ├── ValidatedStringValue.swift ├── ValueStorable.swift └── ValueValidation.swift ├── LanternModelTests ├── Info.plist └── LanternModelTests.swift ├── LanternTests ├── Info.plist └── LanternTests.swift ├── Makefile └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: lantern 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Carthage/Checkouts 2 | xcuserdata 3 | /Lantern.xcodeproj/xcuserdata 4 | /build 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" ~> 4.7 2 | github "burntcaramel/Ono" "master" 3 | github "burntcaramel/BurntFoundation" >= 0.7 4 | github "burntcaramel/BurntCocoaUI" >= 0.6.0 5 | github "burntcaramel/Syrup" >= 0.10 6 | 7 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" "4.9.1" 2 | github "burntcaramel/BurntCocoaUI" "0.6.0" 3 | github "burntcaramel/BurntFoundation" "0.8.0" 4 | github "burntcaramel/Ono" "f7facf05e2e1c020a8421c8b3836755f9f15f2dc" 5 | github "burntcaramel/Syrup" "0.10.0" 6 | -------------------------------------------------------------------------------- /Carthage/Build/.Alamofire.version: -------------------------------------------------------------------------------- 1 | { 2 | "commitish" : "4.9.1", 3 | "Mac" : [ 4 | { 5 | "hash" : "b15bbc536b5d7123db92652c3239a77bce9d279e271de43fe1f6297d6611ef86", 6 | "name" : "Alamofire", 7 | "linking" : "dynamic", 8 | "swiftToolchainVersion" : "5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /Carthage/Build/.BurntCocoaUI.version: -------------------------------------------------------------------------------- 1 | { 2 | "commitish" : "0.6.0", 3 | "Mac" : [ 4 | { 5 | "hash" : "75254fc433075ae1ad7d18fdc44a301eb4054fef727b951ceeefb7ee439d8199", 6 | "name" : "BurntCocoaUI", 7 | "linking" : "dynamic", 8 | "swiftToolchainVersion" : "5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /Carthage/Build/.BurntFoundation.version: -------------------------------------------------------------------------------- 1 | { 2 | "commitish" : "0.8.0", 3 | "Mac" : [ 4 | { 5 | "hash" : "19097d4ee677017d2bbfddcd9dad5318ed60a2719d5628a1ab40566014af6995", 6 | "name" : "BurntFoundation", 7 | "linking" : "dynamic", 8 | "swiftToolchainVersion" : "5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /Carthage/Build/.Grain.version: -------------------------------------------------------------------------------- 1 | { 2 | "commitish" : "0.8.3", 3 | "Mac" : [ 4 | { 5 | "name" : "Syrup", 6 | "hash" : "6b64f7d2f01aa5c3eafd2cf982d2ac80b2a01585bf1b376f8393398f09e4c516" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /Carthage/Build/.Ono.version: -------------------------------------------------------------------------------- 1 | { 2 | "commitish" : "f7facf05e2e1c020a8421c8b3836755f9f15f2dc", 3 | "Mac" : [ 4 | { 5 | "name" : "Ono", 6 | "hash" : "4999445f7dce5b1d18b80af11e1ef0070460aaf6cc72d08397437ec62dec3ae6", 7 | "linking" : "dynamic" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /Carthage/Build/.Syrup.version: -------------------------------------------------------------------------------- 1 | { 2 | "commitish" : "0.10.0", 3 | "Mac" : [ 4 | { 5 | "hash" : "b5ebfa6e94a52bdff89a248d72828f1987d290fe27d3d0ae6ccd5b294a844032", 6 | "name" : "Syrup", 7 | "linking" : "dynamic", 8 | "swiftToolchainVersion" : "5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55)" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.org.alamofire.Alamofire 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 4.9.1 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework.dSYM/Contents/Resources/DWARF/Alamofire: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework.dSYM/Contents/Resources/DWARF/Alamofire -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Alamofire: -------------------------------------------------------------------------------- 1 | Versions/Current/Alamofire -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Alamofire: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework/Versions/A/Alamofire -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Headers/Alamofire.h: -------------------------------------------------------------------------------- 1 | // 2 | // Alamofire.h 3 | // 4 | // Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/) 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 14 | // all 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 22 | // THE SOFTWARE. 23 | // 24 | 25 | @import Foundation; 26 | 27 | FOUNDATION_EXPORT double AlamofireVersionNumber; 28 | FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; 29 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/arm64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/arm64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/arm64-apple-macos.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/arm64-apple-macos.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/arm64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/arm64.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/arm64.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/arm64.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/x86_64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/x86_64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/x86_64-apple-macos.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/x86_64-apple-macos.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/x86_64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/x86_64.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/x86_64.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/Alamofire.swiftmodule/x86_64.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module Alamofire { 2 | umbrella header "Alamofire.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | 8 | module Alamofire.Swift { 9 | header "Alamofire-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 20E5210c 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | Alamofire 11 | CFBundleIdentifier 12 | org.alamofire.Alamofire 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Alamofire 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 4.9.1 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 12E262 33 | DTPlatformName 34 | macosx 35 | DTPlatformVersion 36 | 11.3 37 | DTSDKBuild 38 | 20E214 39 | DTSDKName 40 | macosx11.3 41 | DTXcode 42 | 1250 43 | DTXcodeBuild 44 | 12E262 45 | LSMinimumSystemVersion 46 | 10.10 47 | 48 | 49 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Alamofire.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.com.burntcaramel.BurntCocoaUI 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 0.5 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework.dSYM/Contents/Resources/DWARF/BurntCocoaUI: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework.dSYM/Contents/Resources/DWARF/BurntCocoaUI -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/BurntCocoaUI: -------------------------------------------------------------------------------- 1 | Versions/Current/BurntCocoaUI -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/BurntCocoaUI: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/BurntCocoaUI -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Headers/BurntCocoaUI.h: -------------------------------------------------------------------------------- 1 | // 2 | // BurntCocoaUI.h 3 | // BurntCocoaUI 4 | // 5 | // Created by Patrick Smith on 4/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for BurntCocoaUI. 12 | FOUNDATION_EXPORT double BurntCocoaUIVersionNumber; 13 | 14 | //! Project version string for BurntCocoaUI. 15 | FOUNDATION_EXPORT const unsigned char BurntCocoaUIVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/arm64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/arm64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/arm64-apple-macos.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/arm64-apple-macos.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/arm64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/arm64.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/arm64.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/arm64.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/x86_64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/x86_64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/x86_64-apple-macos.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/x86_64-apple-macos.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/x86_64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/x86_64.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/x86_64.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/BurntCocoaUI.swiftmodule/x86_64.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module BurntCocoaUI { 2 | umbrella header "BurntCocoaUI.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | 8 | module BurntCocoaUI.Swift { 9 | header "BurntCocoaUI-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 20E5210c 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | BurntCocoaUI 11 | CFBundleIdentifier 12 | com.burntcaramel.BurntCocoaUI 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | BurntCocoaUI 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 0.5 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 12E262 33 | DTPlatformName 34 | macosx 35 | DTPlatformVersion 36 | 11.3 37 | DTSDKBuild 38 | 20E214 39 | DTSDKName 40 | macosx11.3 41 | DTXcode 42 | 1250 43 | DTXcodeBuild 44 | 12E262 45 | LSMinimumSystemVersion 46 | 10.10 47 | NSHumanReadableCopyright 48 | Copyright © 2015 Burnt Caramel. All rights reserved. 49 | 50 | 51 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntCocoaUI.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.com.burntcaramel.BurntFoundation 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework.dSYM/Contents/Resources/DWARF/BurntFoundation: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework.dSYM/Contents/Resources/DWARF/BurntFoundation -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/BurntFoundation: -------------------------------------------------------------------------------- 1 | Versions/Current/BurntFoundation -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/BurntFoundation: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework/Versions/A/BurntFoundation -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Headers/BurntFoundation.h: -------------------------------------------------------------------------------- 1 | // 2 | // BurntFoundation.h 3 | // BurntFoundation 4 | // 5 | // Created by Patrick Smith on 16/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for BurntFoundation. 12 | FOUNDATION_EXPORT double BurntFoundationVersionNumber; 13 | 14 | //! Project version string for BurntFoundation. 15 | FOUNDATION_EXPORT const unsigned char BurntFoundationVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/arm64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/arm64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/arm64-apple-macos.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/arm64-apple-macos.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/arm64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/arm64.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/arm64.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/arm64.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/x86_64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/x86_64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/x86_64-apple-macos.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/x86_64-apple-macos.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/x86_64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/x86_64.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/x86_64.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/BurntFoundation.swiftmodule/x86_64.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module BurntFoundation { 2 | umbrella header "BurntFoundation.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | 8 | module BurntFoundation.Swift { 9 | header "BurntFoundation-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 20E5210c 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | BurntFoundation 11 | CFBundleIdentifier 12 | com.burntcaramel.BurntFoundation 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | BurntFoundation 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 12E262 33 | DTPlatformName 34 | macosx 35 | DTPlatformVersion 36 | 11.3 37 | DTSDKBuild 38 | 20E214 39 | DTSDKName 40 | macosx11.3 41 | DTXcode 42 | 1250 43 | DTXcodeBuild 44 | 12E262 45 | LSMinimumSystemVersion 46 | 10.10 47 | NSHumanReadableCopyright 48 | Copyright © 2015 Burnt Caramel. All rights reserved. 49 | 50 | 51 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/BurntFoundation.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.com.burntcaramel.Grain 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 0.2.0 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework.dSYM/Contents/Resources/DWARF/Grain: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Grain.framework.dSYM/Contents/Resources/DWARF/Grain -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Grain: -------------------------------------------------------------------------------- 1 | Versions/Current/Grain -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Versions/A/Grain: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Grain.framework/Versions/A/Grain -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Versions/A/Headers/Grain-Swift.h: -------------------------------------------------------------------------------- 1 | // Generated by Apple Swift version 3.1 (swiftlang-802.0.53 clang-802.0.42) 2 | #pragma clang diagnostic push 3 | 4 | #if defined(__has_include) && __has_include() 5 | # include 6 | #endif 7 | 8 | #pragma clang diagnostic ignored "-Wauto-import" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #if !defined(SWIFT_TYPEDEFS) 15 | # define SWIFT_TYPEDEFS 1 16 | # if defined(__has_include) && __has_include() 17 | # include 18 | # elif !defined(__cplusplus) || __cplusplus < 201103L 19 | typedef uint_least16_t char16_t; 20 | typedef uint_least32_t char32_t; 21 | # endif 22 | typedef float swift_float2 __attribute__((__ext_vector_type__(2))); 23 | typedef float swift_float3 __attribute__((__ext_vector_type__(3))); 24 | typedef float swift_float4 __attribute__((__ext_vector_type__(4))); 25 | typedef double swift_double2 __attribute__((__ext_vector_type__(2))); 26 | typedef double swift_double3 __attribute__((__ext_vector_type__(3))); 27 | typedef double swift_double4 __attribute__((__ext_vector_type__(4))); 28 | typedef int swift_int2 __attribute__((__ext_vector_type__(2))); 29 | typedef int swift_int3 __attribute__((__ext_vector_type__(3))); 30 | typedef int swift_int4 __attribute__((__ext_vector_type__(4))); 31 | typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); 32 | typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); 33 | typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); 34 | #endif 35 | 36 | #if !defined(SWIFT_PASTE) 37 | # define SWIFT_PASTE_HELPER(x, y) x##y 38 | # define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) 39 | #endif 40 | #if !defined(SWIFT_METATYPE) 41 | # define SWIFT_METATYPE(X) Class 42 | #endif 43 | #if !defined(SWIFT_CLASS_PROPERTY) 44 | # if __has_feature(objc_class_property) 45 | # define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ 46 | # else 47 | # define SWIFT_CLASS_PROPERTY(...) 48 | # endif 49 | #endif 50 | 51 | #if defined(__has_attribute) && __has_attribute(objc_runtime_name) 52 | # define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) 53 | #else 54 | # define SWIFT_RUNTIME_NAME(X) 55 | #endif 56 | #if defined(__has_attribute) && __has_attribute(swift_name) 57 | # define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) 58 | #else 59 | # define SWIFT_COMPILE_NAME(X) 60 | #endif 61 | #if defined(__has_attribute) && __has_attribute(objc_method_family) 62 | # define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) 63 | #else 64 | # define SWIFT_METHOD_FAMILY(X) 65 | #endif 66 | #if defined(__has_attribute) && __has_attribute(noescape) 67 | # define SWIFT_NOESCAPE __attribute__((noescape)) 68 | #else 69 | # define SWIFT_NOESCAPE 70 | #endif 71 | #if defined(__has_attribute) && __has_attribute(warn_unused_result) 72 | # define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) 73 | #else 74 | # define SWIFT_WARN_UNUSED_RESULT 75 | #endif 76 | #if !defined(SWIFT_CLASS_EXTRA) 77 | # define SWIFT_CLASS_EXTRA 78 | #endif 79 | #if !defined(SWIFT_PROTOCOL_EXTRA) 80 | # define SWIFT_PROTOCOL_EXTRA 81 | #endif 82 | #if !defined(SWIFT_ENUM_EXTRA) 83 | # define SWIFT_ENUM_EXTRA 84 | #endif 85 | #if !defined(SWIFT_CLASS) 86 | # if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) 87 | # define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA 88 | # define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA 89 | # else 90 | # define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA 91 | # define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA 92 | # endif 93 | #endif 94 | 95 | #if !defined(SWIFT_PROTOCOL) 96 | # define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA 97 | # define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA 98 | #endif 99 | 100 | #if !defined(SWIFT_EXTENSION) 101 | # define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) 102 | #endif 103 | 104 | #if !defined(OBJC_DESIGNATED_INITIALIZER) 105 | # if defined(__has_attribute) && __has_attribute(objc_designated_initializer) 106 | # define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) 107 | # else 108 | # define OBJC_DESIGNATED_INITIALIZER 109 | # endif 110 | #endif 111 | #if !defined(SWIFT_ENUM) 112 | # define SWIFT_ENUM(_type, _name) enum _name : _type _name; enum SWIFT_ENUM_EXTRA _name : _type 113 | # if defined(__has_feature) && __has_feature(generalized_swift_name) 114 | # define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_EXTRA _name : _type 115 | # else 116 | # define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME) SWIFT_ENUM(_type, _name) 117 | # endif 118 | #endif 119 | #if !defined(SWIFT_UNAVAILABLE) 120 | # define SWIFT_UNAVAILABLE __attribute__((unavailable)) 121 | #endif 122 | #if !defined(SWIFT_UNAVAILABLE_MSG) 123 | # define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) 124 | #endif 125 | #if !defined(SWIFT_AVAILABILITY) 126 | # define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) 127 | #endif 128 | #if !defined(SWIFT_DEPRECATED) 129 | # define SWIFT_DEPRECATED __attribute__((deprecated)) 130 | #endif 131 | #if !defined(SWIFT_DEPRECATED_MSG) 132 | # define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) 133 | #endif 134 | #if defined(__has_feature) && __has_feature(modules) 135 | #endif 136 | 137 | #pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" 138 | #pragma clang diagnostic ignored "-Wduplicate-method-arg" 139 | #pragma clang diagnostic pop 140 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Versions/A/Headers/Grain.h: -------------------------------------------------------------------------------- 1 | // 2 | // Grain.h 3 | // Grain 4 | // 5 | // Created by Patrick Smith on 17/03/2016. 6 | // Copyright © 2016 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | //! Project version number for Grain. 12 | FOUNDATION_EXPORT double GrainVersionNumber; 13 | 14 | //! Project version string for Grain. 15 | FOUNDATION_EXPORT const unsigned char GrainVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Versions/A/Modules/Grain.swiftmodule/x86_64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Grain.framework/Versions/A/Modules/Grain.swiftmodule/x86_64.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Versions/A/Modules/Grain.swiftmodule/x86_64.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Grain.framework/Versions/A/Modules/Grain.swiftmodule/x86_64.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module Grain { 2 | umbrella header "Grain.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | 8 | module Grain.Swift { 9 | header "Grain-Swift.h" 10 | } 11 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 16E195 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | Grain 11 | CFBundleIdentifier 12 | com.burntcaramel.Grain 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Grain 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 0.2.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 8E2002 33 | DTPlatformVersion 34 | GM 35 | DTSDKBuild 36 | 16E185 37 | DTSDKName 38 | macosx10.12 39 | DTXcode 40 | 0832 41 | DTXcodeBuild 42 | 8E2002 43 | NSHumanReadableCopyright 44 | Copyright © 2016 Burnt Caramel. All rights reserved. 45 | 46 | 47 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Grain.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.com.mattt.Ono 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework.dSYM/Contents/Resources/DWARF/Ono: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Ono.framework.dSYM/Contents/Resources/DWARF/Ono -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework/Ono: -------------------------------------------------------------------------------- 1 | Versions/Current/Ono -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework/Versions/A/Headers/Ono.h: -------------------------------------------------------------------------------- 1 | // Ono.h 2 | // 3 | // Copyright (c) 2014 Mattt Thompson (http://mattt.me/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import 24 | 25 | //! Project version number for Ono iOS. 26 | FOUNDATION_EXPORT double Ono_VersionNumber; 27 | 28 | //! Project version string for Ono iOS. 29 | FOUNDATION_EXPORT const unsigned char Ono_VersionString[]; 30 | 31 | #import 32 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module Ono { 2 | umbrella header "Ono.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework/Versions/A/Ono: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Ono.framework/Versions/A/Ono -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 20E5210c 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | Ono 11 | CFBundleIdentifier 12 | com.mattt.Ono 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Ono 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 12E262 33 | DTPlatformName 34 | macosx 35 | DTPlatformVersion 36 | 11.3 37 | DTSDKBuild 38 | 20E214 39 | DTSDKName 40 | macosx11.3 41 | DTXcode 42 | 1250 43 | DTXcodeBuild 44 | 12E262 45 | LSMinimumSystemVersion 46 | 10.10 47 | 48 | 49 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Ono.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.com.burntcaramel.Syrup 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 0.10.0 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework.dSYM/Contents/Resources/DWARF/Syrup: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework.dSYM/Contents/Resources/DWARF/Syrup -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Syrup: -------------------------------------------------------------------------------- 1 | Versions/Current/Syrup -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Headers/Syrup.h: -------------------------------------------------------------------------------- 1 | // 2 | // Syrup.h 3 | // Syrup 4 | // 5 | // Created by Patrick Smith on 17/03/2016. 6 | // Copyright © 2016–2017 Patrick Smith. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | //! Project version number for Syrup. 12 | FOUNDATION_EXPORT double SyrupVersionNumber; 13 | 14 | //! Project version string for Syrup. 15 | FOUNDATION_EXPORT const unsigned char SyrupVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/arm64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/arm64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/arm64-apple-macos.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/arm64-apple-macos.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/arm64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/arm64.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/arm64.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/arm64.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/x86_64-apple-macos.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/x86_64-apple-macos.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/x86_64-apple-macos.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/x86_64-apple-macos.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/x86_64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/x86_64.swiftdoc -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/x86_64.swiftmodule: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/Syrup.swiftmodule/x86_64.swiftmodule -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module Syrup { 2 | umbrella header "Syrup.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | 8 | module Syrup.Swift { 9 | header "Syrup-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 20E5210c 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | Syrup 11 | CFBundleIdentifier 12 | com.burntcaramel.Syrup 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Grain 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 0.10.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 12E262 33 | DTPlatformName 34 | macosx 35 | DTPlatformVersion 36 | 11.3 37 | DTSDKBuild 38 | 20E214 39 | DTSDKName 40 | macosx11.3 41 | DTXcode 42 | 1250 43 | DTXcodeBuild 44 | 12E262 45 | LSMinimumSystemVersion 46 | 10.12 47 | NSHumanReadableCopyright 48 | Copyright © 2016–2017 Patrick Smith 49 | 50 | 51 | -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/A/Syrup: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Carthage/Build/Mac/Syrup.framework/Versions/A/Syrup -------------------------------------------------------------------------------- /Carthage/Build/Mac/Syrup.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Lantern.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Lantern.xcodeproj/project.xcworkspace/xcshareddata/Hoverlytics.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | D93679C8-6A02-4CC0-AA2C-8C9204E909AE 9 | IDESourceControlProjectName 10 | Hoverlytics 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 2F4F68DFA0550A2C87C14C3AC2F3397F1F46A29B 14 | burntcaramel.git.beanstalkapp.com:/burntcaramel/hoverlytics-for-mac.git 15 | 16 | IDESourceControlProjectPath 17 | Hoverlytics.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 2F4F68DFA0550A2C87C14C3AC2F3397F1F46A29B 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | burntcaramel.git.beanstalkapp.com:/burntcaramel/hoverlytics-for-mac.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 2F4F68DFA0550A2C87C14C3AC2F3397F1F46A29B 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 2F4F68DFA0550A2C87C14C3AC2F3397F1F46A29B 36 | IDESourceControlWCCName 37 | Hoverlytics%20for%20Mac 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Lantern.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Lantern.xcodeproj/project.xcworkspace/xcshareddata/Lantern.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 7C530460-06FB-4D0C-81F5-3318564DDC8B 9 | IDESourceControlProjectName 10 | project 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 2F4F68DFA0550A2C87C14C3AC2F3397F1F46A29B 14 | burntcaramel.git.beanstalkapp.com:/burntcaramel/hoverlytics-for-mac.git 15 | 16 | IDESourceControlProjectPath 17 | Lantern.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 2F4F68DFA0550A2C87C14C3AC2F3397F1F46A29B 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | burntcaramel.git.beanstalkapp.com:/burntcaramel/hoverlytics-for-mac.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 2F4F68DFA0550A2C87C14C3AC2F3397F1F46A29B 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 2F4F68DFA0550A2C87C14C3AC2F3397F1F46A29B 36 | IDESourceControlWCCName 37 | hoverlytics-for-mac 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Lantern.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CSV.swift", 6 | "repositoryURL": "https://github.com/yaslab/CSV.swift.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "67c5209ae699d5d3ded866de5ee6a6b4005a1b56", 10 | "version": "2.4.2" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Lantern.xcodeproj/project.xcworkspace/xcuserdata/pgwsmith.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Lantern.xcodeproj/xcshareddata/xcschemes/Lantern Model.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /Lantern.xcodeproj/xcshareddata/xcschemes/Lantern.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 52 | 58 | 59 | 60 | 61 | 62 | 72 | 74 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 95 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /Lantern/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Hoverlytics for Mac 4 | // 5 | // Created by Patrick Smith on 28/03/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import LanternModel 11 | 12 | 13 | let NSApp = NSApplication.shared 14 | 15 | @NSApplicationMain 16 | class AppDelegate: NSObject, NSApplicationDelegate { 17 | 18 | var browserMenuController: BrowserMenuController! 19 | @IBOutlet var browserWidthPlaceholderMenuItem: NSMenuItem! 20 | 21 | var crawlerMenuController: CrawlerMenuController! 22 | @IBOutlet var crawlerImageDownloadPlaceholderMenuItem: NSMenuItem! 23 | 24 | 25 | deinit { 26 | let nc = NotificationCenter.default 27 | for observer in windowWillCloseObservers { 28 | nc.removeObserver(observer) 29 | } 30 | windowWillCloseObservers.removeAll() 31 | } 32 | 33 | func applicationDidFinishLaunching(_ aNotification: Notification) { 34 | // Create shared manager to ensure quickest start up time. 35 | let modelManager = LanternModel.ModelManager.sharedManager 36 | modelManager.errorReceiver.errorCallback = { error in 37 | NSApp.presentError(error) 38 | } 39 | 40 | browserMenuController = BrowserMenuController(browserWidthPlaceholderMenuItem: browserWidthPlaceholderMenuItem) 41 | crawlerMenuController = CrawlerMenuController(imageDownloadPlaceholderMenuItem: crawlerImageDownloadPlaceholderMenuItem) 42 | 43 | #if DEBUG 44 | // Update the bloody Dock icon 45 | NSApp.applicationIconImage = nil 46 | 47 | UserDefaults.standard.set(true, forKey: "NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints") 48 | #endif 49 | } 50 | 51 | func applicationWillTerminate(_ aNotification: Notification) { 52 | // Insert code here to tear down your application 53 | } 54 | 55 | 56 | 57 | lazy var mainStoryboard: NSStoryboard = { 58 | return NSStoryboard(name: "Main", bundle: nil) 59 | }() 60 | 61 | var mainWindowControllers = [MainWindowController]() 62 | var windowWillCloseObservers = [AnyObject]() 63 | 64 | func applicationOpenUntitledFile(_ sender: NSApplication) -> Bool { 65 | let windowController = mainStoryboard.instantiateInitialController() as! MainWindowController 66 | windowController.showWindow(nil) 67 | 68 | mainWindowControllers.append(windowController) 69 | 70 | let nc = NotificationCenter.default 71 | windowWillCloseObservers.append(nc.addObserver(forName: NSWindow.willCloseNotification, object: windowController.window!, queue: nil, using: { [unowned self] note in 72 | if let index = self.mainWindowControllers.firstIndex(of: windowController) { 73 | self.mainWindowControllers.remove(at: index) 74 | } 75 | })) 76 | 77 | return true 78 | } 79 | 80 | @IBAction func newDocument(_ sender: AnyObject?) { 81 | _ = self.applicationOpenUntitledFile(NSApp) 82 | } 83 | 84 | @IBAction func forkOnGitHub(_ sender: AnyObject?) { 85 | let URL = Foundation.URL(string: "https://github.com/BurntCaramel/Lantern")! 86 | NSWorkspace.shared.open(URL) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Lantern/BrowserMenuController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BrowserMenuAssistant.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 11/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import BurntFoundation 11 | import BurntCocoaUI 12 | 13 | 14 | extension BrowserWidthChoice : UIChoiceRepresentative { 15 | var tag: Int? { return self.rawValue } 16 | 17 | typealias UniqueIdentifier = BrowserWidthChoice 18 | var uniqueIdentifier: UniqueIdentifier { return self } 19 | } 20 | 21 | extension BrowserWidthChoice : UIChoiceEnumerable { 22 | static var allChoices: [BrowserWidthChoice] { 23 | return [ 24 | .slimMobile, 25 | .mediumMobile, 26 | .mediumTabletPortrait, 27 | .mediumTabletLandscape, 28 | .fullWidth 29 | ] 30 | } 31 | } 32 | 33 | 34 | 35 | class BrowserMenuController: NSObject, NSUserInterfaceValidations { 36 | var widthMenuItemsAssistant: PlaceholderMenuItemAssistant! 37 | 38 | var browserPreferencesObserver: NotificationObserver! 39 | 40 | var browserPrefences: BrowserPreferences { 41 | return BrowserPreferences.shared 42 | } 43 | 44 | init(browserWidthPlaceholderMenuItem: NSMenuItem) { 45 | super.init() 46 | 47 | widthMenuItemsAssistant = PlaceholderMenuItemAssistant(placeholderMenuItem: browserWidthPlaceholderMenuItem) 48 | widthMenuItemsAssistant.menuItemRepresentatives = [ 49 | .slimMobile, 50 | .mediumMobile, 51 | .mediumTabletPortrait, 52 | .mediumTabletLandscape, 53 | .fullWidth 54 | ] 55 | widthMenuItemsAssistant.customization.actionAndTarget = { [weak self] widthChoice in 56 | return (action: #selector(BrowserMenuController.changeWidthChoice(_:)), target: self) 57 | } 58 | // widthMenuItemsAssistant.customization.state = { widthChoice in 59 | // let chosenWidthChoice = BrowserPreferences.sharedBrowserPreferences.widthChoice 60 | // return (chosenWidthChoice == widthChoice) ? NSOnState : NSOffState 61 | // } 62 | widthMenuItemsAssistant.customization.additionalSetUp = { widthChoice, menuItem in 63 | let chosenWidthChoice = BrowserPreferences.shared.widthChoice 64 | menuItem.state = (chosenWidthChoice == widthChoice) ? NSControl.StateValue.on : NSControl.StateValue.off 65 | } 66 | 67 | updateWidthMenu() 68 | 69 | startObservingBrowserPreferences() 70 | } 71 | 72 | deinit { 73 | stopObservingBrowserPreferences() 74 | } 75 | 76 | func updateWidthMenu() { 77 | widthMenuItemsAssistant.update() 78 | } 79 | 80 | func startObservingBrowserPreferences() { 81 | browserPreferencesObserver = NotificationObserver(object: BrowserPreferences.shared) 82 | 83 | browserPreferencesObserver.observe(.widthChoiceDidChange) { notification in 84 | self.updateWidthMenu() 85 | } 86 | } 87 | 88 | func stopObservingBrowserPreferences() { 89 | browserPreferencesObserver.stopObserving() 90 | browserPreferencesObserver = nil 91 | } 92 | 93 | @IBAction func changeWidthChoice(_ sender: AnyObject?) { 94 | if let 95 | menuItem = sender as? NSMenuItem, 96 | let widthChoice = widthMenuItemsAssistant.itemRepresentative(for: menuItem) 97 | { 98 | BrowserPreferences.shared.widthChoice = widthChoice 99 | } 100 | } 101 | 102 | @objc func validateUserInterfaceItem(_ anItem: NSValidatedUserInterfaceItem) -> Bool { 103 | return true 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Lantern/BrowserPreferences.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BrowserState.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 11/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BurntFoundation 11 | 12 | 13 | enum BrowserWidthChoice : Int { 14 | case slimMobile = 1 15 | case mediumMobile 16 | case mediumTabletPortrait 17 | case mediumTabletLandscape 18 | case fullWidth 19 | 20 | var value: CGFloat? { 21 | switch self { 22 | case .slimMobile: 23 | return 320.0 24 | case .mediumMobile: 25 | return 480.0 26 | case .mediumTabletPortrait: 27 | return 768.0 28 | case .mediumTabletLandscape: 29 | return 1024.0 30 | case .fullWidth: 31 | return nil 32 | } 33 | } 34 | 35 | var title: String { 36 | switch self { 37 | case .slimMobile: 38 | return "320" 39 | case .mediumMobile: 40 | return "375" 41 | case .mediumTabletPortrait: 42 | return "768" 43 | case .mediumTabletLandscape: 44 | return "1024" 45 | case .fullWidth: 46 | return "Fluid" 47 | } 48 | /*switch self { 49 | case .slimMobile: 50 | return "Slim Mobile (iPhone 4)" 51 | case .mediumMobile: 52 | return "Medium Mobile (iPhone 6)" 53 | case .mediumTabletPortrait: 54 | return "Medium Tablet Portrait (iPad)" 55 | case .mediumTabletLandscape: 56 | return "Medium Tablet Landscape (iPad)" 57 | case .fullWidth: 58 | return "Full Width" 59 | }(*/ 60 | } 61 | } 62 | 63 | extension BrowserWidthChoice : UserDefaultsChoiceRepresentable { 64 | static var identifier = "browserPreferences.widthChoice" 65 | static var defaultValue: BrowserWidthChoice = .fullWidth 66 | } 67 | 68 | 69 | private var ud = UserDefaults.standard 70 | 71 | 72 | class BrowserPreferences { 73 | enum Notification: String { 74 | case widthChoiceDidChange = "BrowserPreferences.WidthChoiceDidChange" 75 | 76 | var name : Foundation.Notification.Name { 77 | return Foundation.Notification.Name(rawValue: rawValue) 78 | } 79 | } 80 | 81 | private func notify(_ identifier: Notification, userInfo: [String:AnyObject]? = nil) { 82 | let nc = NotificationCenter.default 83 | nc.post(name: Notification.widthChoiceDidChange.name, object: self, userInfo: userInfo) 84 | } 85 | 86 | var widthChoice: BrowserWidthChoice = .fullWidth { 87 | didSet { 88 | ud.setChoice(widthChoice) 89 | 90 | notify(.widthChoiceDidChange) 91 | } 92 | } 93 | 94 | private func updateFromDefaults() { 95 | widthChoice = ud.choice(BrowserWidthChoice.self) 96 | } 97 | 98 | private init() { 99 | updateFromDefaults() 100 | } 101 | 102 | static var shared = BrowserPreferences() 103 | } 104 | -------------------------------------------------------------------------------- /Lantern/ContentPreview.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentPreview.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 3/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | // Globals are lazy in Swift 13 | var contentPreviewStoryboard: NSStoryboard = NSStoryboard(name: "ContentPreview", bundle: nil) 14 | 15 | extension NSStoryboard { 16 | class var lantern_contentPreviewStoryboard: NSStoryboard { 17 | return contentPreviewStoryboard 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Lantern/CrawlerMenuController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrawlerMenuController.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 12/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import BurntFoundation 11 | import BurntCocoaUI 12 | 13 | 14 | extension CrawlerImageDownloadChoice: UIChoiceRepresentative { 15 | var tag: Int? { return self.rawValue } 16 | 17 | typealias UniqueIdentifier = CrawlerImageDownloadChoice 18 | var uniqueIdentifier: UniqueIdentifier { return self } 19 | } 20 | 21 | 22 | class CrawlerMenuController: NSObject, NSUserInterfaceValidations { 23 | var imageDownloadMenuItemsAssistant: PlaceholderMenuItemAssistant! 24 | 25 | var crawlerPreferencesObserver: NotificationObserver! 26 | 27 | init(imageDownloadPlaceholderMenuItem: NSMenuItem) { 28 | super.init() 29 | 30 | imageDownloadMenuItemsAssistant = PlaceholderMenuItemAssistant(placeholderMenuItem: imageDownloadPlaceholderMenuItem) 31 | imageDownloadMenuItemsAssistant.menuItemRepresentatives = [ 32 | .neverDownload, 33 | .total1MB, 34 | .total10MB, 35 | .total100MB, 36 | .unlimited 37 | ] 38 | imageDownloadMenuItemsAssistant.customization.actionAndTarget = { [weak self] widthChoice in 39 | return (action: #selector(CrawlerMenuController.changeImageDownloadChoice(_:)), target: self) 40 | } 41 | // imageDownloadMenuItemsAssistant.customization.state = { imageDownloadChoice in 42 | // let chosenImageDownloadChoice = CrawlerPreferences.sharedCrawlerPreferences.imageDownloadChoice 43 | // return (chosenImageDownloadChoice == imageDownloadChoice) ? NSOnState : NSOffState 44 | // } 45 | imageDownloadMenuItemsAssistant.customization.additionalSetUp = { imageDownloadChoice, menuItem in 46 | let chosenImageDownloadChoice = CrawlerPreferences.shared.imageDownloadChoice 47 | menuItem.state = (chosenImageDownloadChoice == imageDownloadChoice) ? NSControl.StateValue.on : NSControl.StateValue.off 48 | } 49 | 50 | updateImageDownloadMenu() 51 | 52 | startObservingCrawlerPreferences() 53 | } 54 | 55 | deinit { 56 | stopObservingCrawlerPreferences() 57 | } 58 | 59 | func updateImageDownloadMenu() { 60 | imageDownloadMenuItemsAssistant?.update() 61 | } 62 | 63 | func startObservingCrawlerPreferences() { 64 | crawlerPreferencesObserver = NotificationObserver(object: CrawlerPreferences.shared) 65 | 66 | crawlerPreferencesObserver.observe(.ImageDownloadChoiceDidChange) { notification in 67 | self.updateImageDownloadMenu() 68 | } 69 | } 70 | 71 | func stopObservingCrawlerPreferences() { 72 | crawlerPreferencesObserver.stopObserving() 73 | crawlerPreferencesObserver = nil 74 | } 75 | 76 | @IBAction func changeImageDownloadChoice(_ sender: AnyObject?) { 77 | if let 78 | menuItem = sender as? NSMenuItem, 79 | let imageDownloadChoice = imageDownloadMenuItemsAssistant.itemRepresentative(for: menuItem) 80 | { 81 | CrawlerPreferences.shared.imageDownloadChoice = imageDownloadChoice 82 | } 83 | } 84 | 85 | @objc func validateUserInterfaceItem(_ anItem: NSValidatedUserInterfaceItem) -> Bool { 86 | return true 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Lantern/CrawlerPreferences.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrawlerPreferences.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 12/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import BurntFoundation 11 | 12 | 13 | private func bytesForMegabytes(_ MB: UInt) -> UInt { 14 | return MB * 1024 * 1024 15 | } 16 | 17 | 18 | enum CrawlerImageDownloadChoice: Int { 19 | case neverDownload = 0 20 | case total1MB = 1 21 | case total10MB = 10 22 | case total100MB = 100 23 | case unlimited = -1 24 | 25 | var maximumByteCount: UInt? { 26 | switch self { 27 | case .neverDownload: 28 | return 0 29 | case .total1MB: 30 | return bytesForMegabytes(1) 31 | case .total10MB: 32 | return bytesForMegabytes(10) 33 | case .total100MB: 34 | return bytesForMegabytes(100) 35 | case .unlimited: 36 | return nil 37 | } 38 | } 39 | 40 | var title: String { 41 | switch self { 42 | case .neverDownload: 43 | return "Never Download" 44 | case .total1MB: 45 | return "Total 1MB" 46 | case .total10MB: 47 | return "Total 10MB" 48 | case .total100MB: 49 | return "Total 100MB" 50 | case .unlimited: 51 | return "Unlimited" 52 | } 53 | } 54 | } 55 | 56 | extension CrawlerImageDownloadChoice: UserDefaultsChoiceRepresentable { 57 | static var identifier = "crawlerPreferences.imageDownloadChoice" 58 | static var defaultValue: CrawlerImageDownloadChoice = .total10MB 59 | } 60 | 61 | 62 | private var ud = UserDefaults.standard 63 | 64 | 65 | class CrawlerPreferences { 66 | enum Notification: String { 67 | case ImageDownloadChoiceDidChange = "CrawlerPreferences.ImageDownloadChoiceDidChange" 68 | } 69 | 70 | func notify(_ identifier: Notification, userInfo: [String:AnyObject]? = nil) { 71 | NotificationCenter.default.postNotification(identifier, object: self, userInfo: userInfo) 72 | } 73 | 74 | var imageDownloadChoice: CrawlerImageDownloadChoice = .total10MB { 75 | didSet { 76 | ud.setChoice(imageDownloadChoice) 77 | 78 | notify(.ImageDownloadChoiceDidChange) 79 | } 80 | } 81 | 82 | func updateFromDefaults() { 83 | imageDownloadChoice = ud.choice(CrawlerImageDownloadChoice.self) 84 | } 85 | 86 | init() { 87 | updateFromDefaults() 88 | } 89 | 90 | static var shared = CrawlerPreferences() 91 | } 92 | -------------------------------------------------------------------------------- /Lantern/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf2580 2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 AvenirNext-Medium;\f1\fnil\fcharset0 AvenirNext-MediumItalic;\f2\fnil\fcharset0 AppleColorEmoji; 3 | } 4 | {\colortbl;\red255\green255\blue255;} 5 | {\*\expandedcolortbl;;} 6 | \margl1440\margr1440\vieww9000\viewh8400\viewkind0 7 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 8 | 9 | \f0\fs26 \cf0 \ 10 | 11 | \f1\i Support & Feedback 12 | \f0\i0 \ 13 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 14 | {\field{\*\fldinst{HYPERLINK "mailto:products@burntcaramel.com"}}{\fldrslt \cf0 products@burntcaramel.com}}\ 15 | \ 16 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 17 | 18 | \f1\i \cf0 Website 19 | \f0\i0 \ 20 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 21 | {\field{\*\fldinst{HYPERLINK "https://icing.space/tools/lantern/"}}{\fldrslt \cf0 icing.space/tools/lantern}}\ 22 | \ 23 | Please leave a review on the Mac App Store if you like Lantern!\ 24 | \ 25 | Designed & Developed in 26 | \f2 \uc0\u55356 \u57103 27 | \f0 Australia\ 28 | by Patrick Smith \'b7 {\field{\*\fldinst{HYPERLINK "http://twitter.com/concreteniche"}}{\fldrslt @concreteniche}}\ 29 | \ 30 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 31 | 32 | \f1\i \cf0 Uses open source: 33 | \f0\i0 \ 34 | \ 35 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 36 | {\field{\*\fldinst{HYPERLINK "https://github.com/Alamofire/Alamofire"}}{\fldrslt \cf0 https://github.com/Alamofire/Alamofire}}\ 37 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 38 | 39 | \fs22 \cf0 \'a9 2014\'962020 Alamofire Software Foundation ({\field{\*\fldinst{HYPERLINK "http://alamofire.org/"}}{\fldrslt http://alamofire.org/}}) 40 | \fs26 \ 41 | \ 42 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 43 | {\field{\*\fldinst{HYPERLINK "https://github.com/mattt/Ono"}}{\fldrslt \cf0 https://github.com/mattt/Ono}}\ 44 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 45 | 46 | \fs22 \cf0 \'a9 2014\'962020 Mattt Thompson ({\field{\*\fldinst{HYPERLINK "https://mat.tt/"}}{\fldrslt https://mat.tt/}})\ 47 | \ 48 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 49 | {\field{\*\fldinst{HYPERLINK "https://github.com/yaslab/CSV.swift"}}{\fldrslt 50 | \fs26 \cf0 https://github.com/yaslab/CSV.swift}} 51 | \fs26 \ 52 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 53 | 54 | \fs22 \cf0 \'a9 2016 Yasuhiro Hatta\ 55 | \ 56 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 57 | {\field{\*\fldinst{HYPERLINK "https://github.com/BurntCaramel/Grain"}}{\fldrslt 58 | \fs26 \cf0 https://github.com/BurntCaramel/Grain}}\ 59 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 60 | {\field{\*\fldinst{HYPERLINK "https://github.com/BurntCaramel/BurntFoundation"}}{\fldrslt 61 | \fs26 \cf0 https://github.com/BurntCaramel/BurntFoundation}} 62 | \fs26 \ 63 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 64 | {\field{\*\fldinst{HYPERLINK "https://github.com/BurntCaramel/BurntCocoaUI"}}{\fldrslt \cf0 https://github.com/BurntCaramel/BurntCocoaUI}}\ 65 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\partightenfactor0 66 | 67 | \fs22 \cf0 \'a9 2015\'962021 Patrick Smith\ 68 | } -------------------------------------------------------------------------------- /Lantern/ImagePreviewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePreviewViewController.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 4/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Quartz 11 | 12 | 13 | class ImagePreviewViewController: NSViewController { 14 | @IBOutlet var MIMETypeField: NSTextField! 15 | @IBOutlet var pixelWidthField: NSTextField! 16 | @IBOutlet var pixelHeightField: NSTextField! 17 | var innerImageViewController: ImagePreviewInnerViewController? 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | view.appearance = NSAppearance(named: NSAppearance.Name.aqua) 23 | } 24 | 25 | override func prepare(for segue: NSStoryboardSegue, sender: Any?) { 26 | if segue.identifier == "imageViewController" { 27 | let innerImageViewController = segue.destinationController as! ImagePreviewInnerViewController 28 | 29 | innerImageViewController.imageData = imageData 30 | 31 | innerImageViewController.wantsToDismiss = { 32 | self.dismiss(nil) 33 | } 34 | 35 | innerImageViewController.imagePropertiesDidLoad = { [weak self] imageProperties in 36 | if let 37 | receiver = self, 38 | let pixelWidthNumber = imageProperties[kCGImagePropertyPixelWidth] as? NSNumber, 39 | let pixelHeightNumber = imageProperties[kCGImagePropertyPixelHeight] as? NSNumber 40 | { 41 | receiver.pixelWidthField.integerValue = pixelWidthNumber.intValue 42 | receiver.pixelHeightField.integerValue = pixelHeightNumber.intValue 43 | } 44 | 45 | } 46 | 47 | self.innerImageViewController = innerImageViewController 48 | } 49 | } 50 | 51 | func resetUI() 52 | { 53 | pixelWidthField.stringValue = "" 54 | pixelHeightField.stringValue = "" 55 | } 56 | 57 | var sourceURL: URL? { 58 | didSet { 59 | if let sourceURL = sourceURL { 60 | title = sourceURL.absoluteString 61 | } 62 | } 63 | } 64 | 65 | var imageData: Data! { 66 | didSet { 67 | resetUI() 68 | 69 | innerImageViewController?.imageData = imageData 70 | } 71 | } 72 | 73 | var MIMEType: String? { 74 | didSet { 75 | MIMETypeField.stringValue = MIMEType ?? "" 76 | } 77 | } 78 | 79 | var contentSizeConstraints = [NSLayoutConstraint]() 80 | var superviewSizeConstraints = [NSLayoutConstraint]() 81 | //var screenWidthConstraint 82 | 83 | #if false 84 | override func updateViewConstraints() { 85 | /*if let screen = view.window?.screen { 86 | let size = screen.visibleFrame.size 87 | 88 | }*/ 89 | let view = self.view 90 | let imageView = self.imageView 91 | //if let windowContentView = view.window?.contentView as? NSView { 92 | if let window = view.window, let screen = window.screen { 93 | if self.contentSizeConstraints.count > 0 { 94 | view.removeConstraints(self.contentSizeConstraints) 95 | } 96 | 97 | func layoutConstraintWithView(view: NSView, attribute: NSLayoutAttribute, relatedBy: NSLayoutRelation, constant: CGFloat) -> NSLayoutConstraint { 98 | let constraint = NSLayoutConstraint(item: imageView, attribute: attribute, relatedBy: relatedBy, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: constant) 99 | return constraint 100 | } 101 | 102 | let imageSize = imageView.imageSize() 103 | let screenSize = screen.visibleFrame.size 104 | let windowSize = window.frame.size 105 | let maximumSize = NSSize( 106 | width: min(screenSize.width, windowSize.width), 107 | height: min(screenSize.height, windowSize.height) 108 | ) 109 | 110 | let contentSizeConstraints = [ 111 | //layoutConstraintWithView(imageView, attribute: .Width, relatedBy: .GreaterThanOrEqual, constant: imageSize.width, required: false), 112 | //layoutConstraintWithView(imageView, attribute: .Height, relatedBy: .GreaterThanOrEqual, constant: imageSize.height, required: false), 113 | layoutConstraintWithView(imageView, attribute: .Width, relatedBy: .LessThanOrEqual, constant: maximumSize.width), 114 | layoutConstraintWithView(imageView, attribute: .Height, relatedBy: .LessThanOrEqual, constant: maximumSize.width) 115 | ] 116 | 117 | view.addConstraints(contentSizeConstraints) 118 | 119 | self.contentSizeConstraints = contentSizeConstraints 120 | 121 | #if false 122 | if let superview = view.superview { 123 | let superviewSizeConstraints = [ 124 | layoutConstraintWithView(view, attribute: .Width, relatedBy: .LessThanOrEqual, constant: maximumSize.width), 125 | layoutConstraintWithView(view, attribute: .Height, relatedBy: .LessThanOrEqual, constant: maximumSize.width), 126 | NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .LessThanOrEqual, toItem: superview, attribute: .Width, multiplier: 1.0, constant: 0.0), 127 | NSLayoutConstraint(item: view, attribute: .Height, relatedBy: .LessThanOrEqual, toItem: superview, attribute: .Height, multiplier: 1.0, constant: 0.0), 128 | ] 129 | 130 | superview.addConstraints(superviewSizeConstraints) 131 | 132 | self.superviewSizeConstraints = superviewSizeConstraints 133 | } 134 | else { 135 | self.superviewSizeConstraints.removeAll() 136 | } 137 | #endif 138 | } 139 | 140 | super.updateViewConstraints() 141 | } 142 | #endif 143 | } 144 | 145 | extension ImagePreviewViewController { 146 | class func instantiateFromStoryboard() -> ImagePreviewViewController { 147 | let vc = NSStoryboard.lantern_contentPreviewStoryboard.instantiateController(withIdentifier: "Image View Controller") as! ImagePreviewViewController 148 | _ = vc.view // Stupid NSViewController 149 | return vc 150 | } 151 | } 152 | 153 | extension ImagePreviewViewController { 154 | @IBAction func copyImage(_ sender: AnyObject?) { 155 | performCopyImage() 156 | } 157 | 158 | private func performCopyImage() { 159 | if 160 | let imageData = imageData, 161 | let MIMEType = MIMEType, MIMEType != "", 162 | let UTIs = UTTypeCreateAllIdentifiersForTag(kUTTagClassMIMEType, MIMEType as CFString, kUTTypeImage)?.takeRetainedValue() as? [String]//, 163 | //let pasteboardType = UTTypeCopyPreferredTagWithClass(preferredUTI, kUTTagClassNSPboardType).takeRetainedValue() 164 | { 165 | let pasteboard = NSPasteboard.general 166 | pasteboard.clearContents() 167 | 168 | pasteboard.declareTypes(convertToNSPasteboardPasteboardTypeArray(UTIs), owner: nil) 169 | for UTI in UTIs { 170 | pasteboard.setData(imageData, forType: convertToNSPasteboardPasteboardType(UTI)) 171 | } 172 | } 173 | } 174 | 175 | override var acceptsFirstResponder: Bool { 176 | return true 177 | } 178 | 179 | override func cancelOperation(_ sender: Any?) { 180 | dismiss(sender) 181 | } 182 | 183 | override func keyDown(with theEvent: NSEvent) { 184 | if theEvent.burnt_isSpaceKey { 185 | // Just like QuickLook, use space to dismiss. 186 | dismiss(nil) 187 | } 188 | } 189 | } 190 | 191 | extension ImagePreviewViewController: NSPopoverDelegate { 192 | func popoverDidShow(_ notification: Notification) { 193 | if let window = view.window { 194 | window.makeFirstResponder(self) 195 | } 196 | 197 | view.layoutSubtreeIfNeeded() 198 | } 199 | 200 | func popoverShouldDetach(_ popover: NSPopover) -> Bool { 201 | return true 202 | } 203 | } 204 | 205 | 206 | class ImagePreviewInnerViewController: NSViewController { 207 | @IBOutlet var imageView: IKImageView! 208 | 209 | var imagePropertiesDidLoad: ((_ imageProperties: [NSString: AnyObject]) -> ())? 210 | var wantsToDismiss: (() -> ())? 211 | 212 | var backgroundQueue: DispatchQueue! 213 | 214 | override func viewDidLoad() { 215 | super.viewDidLoad() 216 | 217 | backgroundQueue = DispatchQueue(label: "com.burntcaramel.ImagePreviewViewController.background", attributes: []) 218 | } 219 | 220 | var coreGraphicsImageSource: CGImageSource! 221 | var viewedCoreGraphicsImage: CGImage? 222 | 223 | fileprivate func updateUIWithImage(_ image: CGImage, imageProperties: [NSString: AnyObject]?) { 224 | let view = self.view 225 | 226 | let imageView = self.imageView! 227 | imageView.setImage(viewedCoreGraphicsImage, imageProperties: imageProperties) 228 | //imageView.zoomImageToActualSize(nil) 229 | 230 | var imageSize = imageView.imageSize() 231 | #if DEBUG 232 | print("imageSize \(imageSize)") 233 | #endif 234 | 235 | if var screenSize = view.window?.screen?.visibleFrame.size { 236 | #if DEBUG 237 | print("screenSize \(screenSize)") 238 | #endif 239 | screenSize.width -= 13.0 * 2.0 + 8.0 240 | screenSize.height -= 13.0 * 2.0 + 32.0 241 | //screenSize.height -= 32.0 242 | 243 | imageSize.width = min(imageSize.width, screenSize.width) 244 | imageSize.height = min(imageSize.height, screenSize.height) 245 | } 246 | 247 | //imageSize.width -= 13.0 * 2.0 + 8.0 248 | //imageSize.height -= 32.0 249 | 250 | #if DEBUG 251 | print("preferredContentSize \(imageSize)") 252 | #endif 253 | 254 | imageView.autoresizes = false 255 | preferredContentSize = imageSize 256 | 257 | if let imageProperties = imageProperties { 258 | imagePropertiesDidLoad?(imageProperties) 259 | } 260 | 261 | view.needsUpdateConstraints = true 262 | 263 | view.layoutSubtreeIfNeeded() 264 | 265 | //imageView.zoomImageToFit(nil) 266 | imageView.zoomImageToActualSize(nil) 267 | //view.superview 268 | } 269 | 270 | var imageData: Data? { 271 | didSet { 272 | _ = self.view // Stupid NSViewController 273 | 274 | if let imageData = self.imageData { 275 | backgroundQueue.async { 276 | guard let coreGraphicsImageSource = CGImageSourceCreateWithData(imageData as CFData, nil) else { return } 277 | 278 | let viewedCoreGraphicsImage = CGImageSourceCreateImageAtIndex(coreGraphicsImageSource, 0, nil) 279 | let imageProperties = CGImageSourceCopyPropertiesAtIndex(coreGraphicsImageSource, 0, nil) as? [NSString: AnyObject] 280 | 281 | if let viewedCoreGraphicsImage = viewedCoreGraphicsImage { 282 | DispatchQueue.main.async { [weak self] in 283 | self?.updateUIWithImage(viewedCoreGraphicsImage, imageProperties: imageProperties) 284 | } 285 | } 286 | 287 | self.viewedCoreGraphicsImage = viewedCoreGraphicsImage 288 | self.coreGraphicsImageSource = coreGraphicsImageSource 289 | } 290 | } 291 | } 292 | } 293 | 294 | @IBAction func clickedImageView(_ sender: AnyObject?) { 295 | wantsToDismiss?() 296 | } 297 | 298 | 299 | #if false 300 | func createMenu() { 301 | let menu = NSMenu(title: "Values Menu") 302 | 303 | let copyImageItem = menu.addItemWithTitle("Copy Image", action: "copyImage:", keyEquivalent: "")! 304 | copyImageItem.target = self 305 | 306 | imageView.menu = menu 307 | } 308 | #endif 309 | } 310 | 311 | 312 | // Helper function inserted by Swift 4.2 migrator. 313 | fileprivate func convertToNSPasteboardPasteboardTypeArray(_ input: [String]) -> [NSPasteboard.PasteboardType] { 314 | return input.map { key in NSPasteboard.PasteboardType(key) } 315 | } 316 | 317 | // Helper function inserted by Swift 4.2 migrator. 318 | fileprivate func convertToNSPasteboardPasteboardType(_ input: String) -> NSPasteboard.PasteboardType { 319 | return NSPasteboard.PasteboardType(rawValue: input) 320 | } 321 | -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/128x128.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/16x16.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/256x256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/256x256-1.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/256x256.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/32x32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/32x32-1.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/32x32.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/32x32@2x.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/512x512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/512x512-1.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/512x512.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RoyalIcing/Lantern/2d1a2c06db7b20c49e42afa723151de04cb35b49/Lantern/Images.xcassets/AppIcon.appiconset/512x512@2x.png -------------------------------------------------------------------------------- /Lantern/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "32x32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "32x32-1.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "256x256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "256x256-1.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "512x512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "512x512-1.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Lantern/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 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSApplicationCategoryType 26 | public.app-category.developer-tools 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | NSAppTransportSecurity 30 | 31 | NSAllowsArbitraryLoads 32 | 33 | 34 | NSApplicationCrashOnExceptions 35 | 36 | NSHumanReadableCopyright 37 | Copyright © 2021 Patrick George Wyndham Smith. All rights reserved. 38 | NSMainStoryboardFile 39 | Main 40 | NSPrincipalClass 41 | NSApplication 42 | 43 | 44 | -------------------------------------------------------------------------------- /Lantern/Lantern-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "NSViewController+PGWSConstraintConvenience.h" 6 | -------------------------------------------------------------------------------- /Lantern/Lantern.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.icloud-container-identifiers 6 | 7 | com.apple.security.app-sandbox 8 | 9 | com.apple.security.files.user-selected.read-write 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Lantern/MainSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainSection.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 24/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | enum MainSection: String { 13 | case Preview = "preview" 14 | case Audience = "audience" 15 | case Audit = "audit" 16 | } 17 | -------------------------------------------------------------------------------- /Lantern/MainState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainState.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 31/03/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LanternModel 11 | 12 | 13 | enum SiteChoice { 14 | case savedSite(SiteValues) 15 | case custom 16 | } 17 | 18 | extension SiteChoice: Equatable {} 19 | 20 | func ==(lhs: SiteChoice, rhs: SiteChoice) -> Bool { 21 | switch (lhs, rhs) { 22 | case (.custom, .custom): 23 | return true 24 | case (.savedSite(let lSite), .savedSite(let rSite)): 25 | return lSite.UUID == rSite.UUID 26 | default: 27 | return false 28 | } 29 | } 30 | 31 | 32 | class MainState { 33 | let crawlerPreferences = CrawlerPreferences.shared 34 | let browserPreferences = BrowserPreferences.shared 35 | 36 | var startURL: URL? { 37 | didSet { 38 | print("startURL changing \(startURL)") 39 | if startURL == oldValue { return } 40 | mainQueue_notify(Self.startURLDidChangeNotification) 41 | } 42 | } 43 | // var currentURL: URL? 44 | 45 | var siteChoice = SiteChoice.custom { 46 | didSet { 47 | if siteChoice == oldValue { return } 48 | mainQueue_notify(Self.chosenSiteDidChangeNotification) 49 | } 50 | } 51 | 52 | var chosenSite: SiteValues? { 53 | switch siteChoice { 54 | case .savedSite(let site): return site 55 | default: return nil 56 | } 57 | } 58 | 59 | var initialHost: String? 60 | 61 | static let startURLDidChangeNotification = Notification.Name.init("LanternModel.MainState.startURLDidChangeNotification") 62 | 63 | static let chosenSiteDidChangeNotification = Notification.Name.init("LanternModel.MainState.ChosenSiteDidChangeNotification") 64 | 65 | private func mainQueue_notify(_ name: Notification.Name, userInfo: [String: AnyObject]? = nil) { 66 | let nc = NotificationCenter.default 67 | nc.post(name: name, object: self, userInfo: userInfo) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Lantern/MainView.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 | -------------------------------------------------------------------------------- /Lantern/MetaViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetaViewController.swift 3 | // Lantern 4 | // 5 | // Created by Patrick Smith on 1/11/16. 6 | // Copyright © 2016 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | //import LanternModel 11 | //import Ono 12 | 13 | 14 | class MetaViewController : NSViewController { 15 | //@IBOutlet var stackView: NSStackView! 16 | @IBOutlet var tableView: NSTableView! 17 | 18 | var crawlerProviderListenerUUID = UUID() 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | } 23 | 24 | override func viewDidAppear() { 25 | super.viewDidAppear() 26 | 27 | tableView.dataSource = self 28 | tableView.delegate = self 29 | 30 | if let provider = pageMapperProvider { 31 | let crawlerProviderListenerUUID = self.crawlerProviderListenerUUID 32 | 33 | provider[activeURLChangedCallback: crawlerProviderListenerUUID] = { [weak self] url in 34 | self?.updateUI(url: url) 35 | } 36 | 37 | provider[pageMapperCreatedCallback: crawlerProviderListenerUUID] = { [weak self] pageMapper in 38 | pageMapper[didUpdateCallback: crawlerProviderListenerUUID] = { [weak self] url in 39 | if url == provider.activeURL { 40 | self?.updateUI(url: url) 41 | } 42 | } 43 | } 44 | 45 | updateUI(url: provider.activeURL) 46 | } 47 | } 48 | 49 | override func viewWillDisappear() { 50 | super.viewWillDisappear() 51 | 52 | guard let provider = pageMapperProvider else { return } 53 | 54 | provider[activeURLChangedCallback: crawlerProviderListenerUUID] = nil 55 | } 56 | 57 | /*func createStackViews(_ url: URL?) -> [NSView] { 58 | guard let url = url else { 59 | return [] 60 | } 61 | 62 | if let crawler = pageMapperProvider?.pageMapper { 63 | if 64 | let resourceInfo = crawler.pageInfoForRequestedURL(url), 65 | let contentInfo = resourceInfo.contentInfo 66 | { 67 | let metaTagFields: [NSTextField] = contentInfo.metaElementAttributes.map { attributes in 68 | print(attributes) 69 | let textField = NSTextField(string: "\(attributes)") 70 | textField.translatesAutoresizingMaskIntoConstraints = false 71 | textField.setContentCompressionResistancePriority(NSLayoutPriorityDefaultHigh, for: .horizontal) 72 | return textField 73 | } 74 | 75 | print("metaTagFields \(metaTagFields)") 76 | 77 | return Array([ 78 | metaTagFields 79 | ].joined()) 80 | } 81 | } 82 | 83 | return [ 84 | NSTextField(labelWithString: "Loading…"), 85 | ] 86 | }*/ 87 | 88 | func findMetaElementAttributes(url: URL) -> [[String : String]]? { 89 | guard 90 | let crawler = pageMapperProvider?.pageMapper, 91 | let resourceInfo = crawler.pageInfoForRequestedURL(url), 92 | let contentInfo = resourceInfo.contentInfo 93 | else { return nil } 94 | 95 | return contentInfo.metaElementAttributes 96 | } 97 | 98 | var metaElementAttributes: [[String : String]] = [] 99 | 100 | func updateUI(url: URL?) { 101 | metaElementAttributes = url.flatMap{ findMetaElementAttributes(url: $0) } ?? [] 102 | tableView.reloadData() 103 | //stackView.setViews(createStackViews(url), in: NSStackViewGravity.center) 104 | } 105 | } 106 | 107 | extension MetaViewController : NSTableViewDataSource, NSTableViewDelegate { 108 | func numberOfRows(in tableView: NSTableView) -> Int { 109 | return metaElementAttributes.count 110 | } 111 | 112 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 113 | let attributes = metaElementAttributes[row] 114 | let identifier = convertFromNSUserInterfaceItemIdentifier(tableColumn!.identifier) 115 | let view = tableView.makeView(withIdentifier: convertToNSUserInterfaceItemIdentifier(identifier), owner: self) as! NSTableCellView 116 | 117 | var property = attributes["name"] ?? attributes["property"] ?? attributes["http-equiv"] 118 | var contentAttributeKey = "content" 119 | if property == nil && attributes["charset"] != nil { 120 | property = "charset" 121 | contentAttributeKey = "charset" 122 | } 123 | 124 | switch identifier { 125 | case "property": 126 | view.textField?.stringValue = property ?? attributes.keys.filter{ $0 != "content" }.joined(separator: " ") 127 | case "value": 128 | view.textField?.stringValue = attributes[contentAttributeKey] ?? "?" 129 | default: 130 | break 131 | } 132 | 133 | return view 134 | } 135 | } 136 | 137 | // Helper function inserted by Swift 4.2 migrator. 138 | fileprivate func convertFromNSUserInterfaceItemIdentifier(_ input: NSUserInterfaceItemIdentifier) -> String { 139 | return input.rawValue 140 | } 141 | 142 | // Helper function inserted by Swift 4.2 migrator. 143 | fileprivate func convertToNSUserInterfaceItemIdentifier(_ input: String) -> NSUserInterfaceItemIdentifier { 144 | return NSUserInterfaceItemIdentifier(rawValue: input) 145 | } 146 | -------------------------------------------------------------------------------- /Lantern/MultipleStringPreviewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultipleStringPreviewViewController.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 3/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import LanternModel 11 | 12 | 13 | class MultipleStringPreviewViewController: NSViewController { 14 | @IBOutlet var tableView: NSTableView! 15 | var measuringTableCellView: MultipleStringPreviewTableCellView! 16 | 17 | var itemMenu: NSMenu! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | tableView.tableColumns[0].minWidth = 100 23 | 24 | tableView.dataSource = self 25 | tableView.delegate = self 26 | 27 | measuringTableCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "stringValue"), owner: self) as! MultipleStringPreviewTableCellView 28 | 29 | view.appearance = NSAppearance(named: NSAppearance.Name.aqua) 30 | } 31 | 32 | func createRowMenu() { 33 | // Row Menu 34 | itemMenu = NSMenu(title: "Values Menu") 35 | 36 | let copyValueItem = itemMenu.addItem(withTitle: "Copy Value", action: #selector(MultipleStringPreviewViewController.copyValueForSelectedRow(_:)), keyEquivalent: "") 37 | copyValueItem.target = self 38 | } 39 | 40 | var validatedStringValues: [ValidatedStringValue] = [] { 41 | didSet { 42 | reloadValues() 43 | } 44 | } 45 | 46 | func reloadValues() { 47 | // Stupid NSViewController 48 | _ = self.view 49 | 50 | tableView.reloadData() 51 | } 52 | 53 | override func keyDown(with theEvent: NSEvent) { 54 | if theEvent.burnt_isSpaceKey { 55 | // Just like QuickLook, use space to dismiss. 56 | dismiss(nil) 57 | } 58 | } 59 | } 60 | 61 | extension MultipleStringPreviewViewController { 62 | class func instantiateFromStoryboard() -> MultipleStringPreviewViewController { 63 | return NSStoryboard.lantern_contentPreviewStoryboard.instantiateController(withIdentifier: "String Value View Controller") as! MultipleStringPreviewViewController 64 | } 65 | } 66 | 67 | extension MultipleStringPreviewViewController { 68 | @IBAction func copyValueForSelectedRow(_ menuItem: NSMenuItem) { 69 | let row = tableView.clickedRow 70 | if row != -1 { 71 | performCopyValueForItemAtRow(row) 72 | } 73 | } 74 | 75 | func performCopyValueForItemAtRow(_ row: Int) { 76 | switch validatedStringValues[row] { 77 | case .validString(let stringValue): 78 | let pasteboard = NSPasteboard.general 79 | pasteboard.clearContents() 80 | 81 | pasteboard.declareTypes([NSPasteboard.PasteboardType.string], owner: nil) 82 | pasteboard.setString(stringValue, forType: NSPasteboard.PasteboardType.string) 83 | default: 84 | break 85 | } 86 | } 87 | } 88 | 89 | extension MultipleStringPreviewViewController: NSTableViewDataSource, NSTableViewDelegate { 90 | func numberOfRows(in tableView: NSTableView) -> Int { 91 | return validatedStringValues.count 92 | } 93 | 94 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { 95 | switch validatedStringValues[row] { 96 | case .validString(let stringValue): 97 | return stringValue 98 | default: 99 | break 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func setUpTableCellView(_ view: MultipleStringPreviewTableCellView, tableColumn: NSTableColumn?, row: Int, visualsAndInteraction: Bool = true) { 106 | let validatedStringValue = validatedStringValues[row] 107 | 108 | let textField = view.textField! 109 | textField.stringValue = validatedStringValue.stringValueForPresentation 110 | 111 | var presentedIndex: String { 112 | switch validatedStringValue { 113 | case .validKeyValue(let key, _): return key 114 | default: return String(row + 1) // 1-based index 115 | } 116 | } 117 | 118 | let indexField = view.indexField! 119 | indexField.stringValue = presentedIndex 120 | indexField.sizeToFit() 121 | 122 | if visualsAndInteraction { 123 | let textColor = NSColor.textColor 124 | 125 | indexField.textColor = textColor.withAlphaComponent(0.3) 126 | 127 | textField.textColor = textColor.withAlphaComponent(validatedStringValue.alphaValueForPresentation) 128 | 129 | view.menu = itemMenu 130 | } 131 | } 132 | 133 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 134 | let cellView = measuringTableCellView 135 | 136 | setUpTableCellView(cellView!, tableColumn: nil, row: row, visualsAndInteraction: false) 137 | 138 | let tableColumn = tableView.tableColumns[0] 139 | let cellWidth = tableColumn.width 140 | cellView?.setFrameSize(NSSize(width: cellWidth, height: 100.0)) 141 | cellView?.layoutSubtreeIfNeeded() 142 | 143 | let textField = cellView?.textField! 144 | textField?.preferredMaxLayoutWidth = (textField?.bounds.width)! 145 | 146 | let extraPadding: CGFloat = 5.0 + 5.0 147 | 148 | let height = (textField?.intrinsicContentSize.height)! + extraPadding 149 | 150 | return height 151 | } 152 | 153 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 154 | if let view = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "stringValue"), owner: self) as? MultipleStringPreviewTableCellView { 155 | setUpTableCellView(view, tableColumn: tableColumn, row: row) 156 | 157 | return view 158 | } 159 | 160 | return nil 161 | } 162 | 163 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { 164 | return false 165 | } 166 | } 167 | 168 | extension MultipleStringPreviewViewController: NSPopoverDelegate { 169 | func popoverWillShow(_ notification: Notification) { 170 | //let popover = notification.object as! NSPopover 171 | //popover.appearance = NSAppearance(named: NSAppearanceNameVibrantDark) 172 | //popover.appearance = NSAppearance(named: NSAppearanceNameLightContent) 173 | //popover.appearance = .HUD 174 | } 175 | } 176 | 177 | 178 | class MultipleStringPreviewTableCellView: NSTableCellView { 179 | @IBOutlet var indexField: NSTextField! 180 | } 181 | -------------------------------------------------------------------------------- /Lantern/NSEvent+extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSViewController+extension.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 4/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | extension NSEvent { 13 | var burnt_firstCharacter: Character? { 14 | if let charactersIgnoringModifiers = charactersIgnoringModifiers { 15 | return charactersIgnoringModifiers[charactersIgnoringModifiers.startIndex] 16 | } 17 | 18 | return nil 19 | } 20 | 21 | var burnt_isSpaceKey: Bool { 22 | return burnt_firstCharacter == " " 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Lantern/NSViewController+PGWSConstraintConvenience.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSViewController+PGWSConstraintConvenience.h 3 | // BurntIcing 4 | // 5 | // Created by Patrick Smith on 28/02/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | @import Cocoa; 10 | 11 | 12 | @interface NSViewController (PGWSConstraintConvenience) 13 | 14 | - (NSLayoutConstraint *)layoutConstraintWithIdentifier:(NSString *)constraintIdentifier; 15 | 16 | + (NSString *)layoutConstraintIdentifierWithBaseIdentifier:(NSString *)baseIdentifier forChildView:(NSView *)innerView; 17 | - (NSLayoutConstraint *)layoutConstraintWithIdentifier:(NSString *)baseIdentifier forChildView:(NSView *)innerView; 18 | 19 | #pragma mark - 20 | 21 | - (NSLayoutConstraint *)addLayoutConstraintToMatchAttribute:(NSLayoutAttribute)attribute withChildView:(NSView *)innerView identifier:(NSString *)identifier priority:(NSLayoutPriority)priority; 22 | 23 | - (NSLayoutConstraint *)addLayoutConstraintToMatchAttribute:(NSLayoutAttribute)attribute withChildView:(NSView *)innerView identifier:(NSString *)identifier; 24 | 25 | - (void)fillViewWithChildView:(NSView *)innerView; 26 | - (void)fillWithChildViewController:(NSViewController *)childViewController; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Lantern/NSViewController+PGWSConstraintConvenience.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSViewController+PGWSConstraintConvenience.m 3 | // BurntIcing 4 | // 5 | // Created by Patrick Smith on 28/02/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | #import "NSViewController+PGWSConstraintConvenience.h" 10 | 11 | 12 | @implementation NSViewController (PGWSConstraintConvenience) 13 | 14 | - (NSLayoutConstraint *)layoutConstraintWithIdentifier:(NSString *)constraintIdentifier 15 | { 16 | NSArray *leadingConstraintInArray = [(self.view.constraints) filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"identifier = %@", constraintIdentifier]]; 17 | 18 | if (leadingConstraintInArray.count == 0) { 19 | return nil; 20 | } 21 | else { 22 | return leadingConstraintInArray[0]; 23 | } 24 | } 25 | 26 | + (NSString *)layoutConstraintIdentifierWithBaseIdentifier:(NSString *)baseIdentifier forChildView:(NSView *)innerView 27 | { 28 | return [NSString stringWithFormat:@"%@--%@", (innerView.identifier), baseIdentifier]; 29 | } 30 | 31 | - (NSLayoutConstraint *)layoutConstraintWithIdentifier:(NSString *)baseIdentifier forChildView:(NSView *)innerView 32 | { 33 | if (!innerView) { 34 | return nil; 35 | } 36 | 37 | NSString *constraintIdentifier = [(self.class) layoutConstraintIdentifierWithBaseIdentifier:baseIdentifier forChildView:innerView]; 38 | return [self layoutConstraintWithIdentifier:constraintIdentifier]; 39 | } 40 | 41 | #pragma mark - 42 | 43 | - (NSLayoutConstraint *)addLayoutConstraintToMatchAttribute:(NSLayoutAttribute)attribute withChildView:(NSView *)innerView identifier:(NSString *)identifier priority:(NSLayoutPriority)priority 44 | { 45 | NSParameterAssert(innerView != nil); 46 | NSParameterAssert(identifier != nil); 47 | 48 | NSView *holderView = (self.view); 49 | 50 | NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:innerView attribute:attribute relatedBy:NSLayoutRelationEqual toItem:holderView attribute:attribute multiplier:1.0 constant:0.0]; 51 | 52 | (constraint.identifier) = [(self.class) layoutConstraintIdentifierWithBaseIdentifier:identifier forChildView:innerView]; 53 | (constraint.priority) = priority; 54 | 55 | [holderView addConstraint:constraint]; 56 | 57 | return constraint; 58 | } 59 | 60 | - (NSLayoutConstraint *)addLayoutConstraintToMatchAttribute:(NSLayoutAttribute)attribute withChildView:(NSView *)innerView identifier:(NSString *)identifier 61 | { 62 | return [self addLayoutConstraintToMatchAttribute:attribute withChildView:innerView identifier:identifier priority:NSLayoutPriorityRequired]; 63 | } 64 | 65 | - (void)fillViewWithChildView:(NSView *)innerView 66 | { 67 | NSParameterAssert(innerView != nil); 68 | 69 | if (!(innerView.identifier)) { 70 | NSUUID *UUID = [NSUUID UUID]; 71 | (innerView.identifier) = [NSString stringWithFormat:@"(%@)", (UUID.UUIDString)]; 72 | } 73 | 74 | [(self.view) addSubview:innerView]; 75 | 76 | // Interface Builder's default is to have this on for new view controllers in 10.9 for some reason. 77 | // I have disabled it where I remember to in the xib file, but no harm in just setting it off here too. 78 | (innerView.translatesAutoresizingMaskIntoConstraints) = NO; 79 | 80 | // By setting width and height constraints, we can move the view around whilst keeping it the same size. 81 | [self addLayoutConstraintToMatchAttribute:NSLayoutAttributeWidth withChildView:innerView identifier:@"width"]; 82 | [self addLayoutConstraintToMatchAttribute:NSLayoutAttributeHeight withChildView:innerView identifier:@"height"]; 83 | [self addLayoutConstraintToMatchAttribute:NSLayoutAttributeLeading withChildView:innerView identifier:@"leading"]; 84 | [self addLayoutConstraintToMatchAttribute:NSLayoutAttributeTop withChildView:innerView identifier:@"top"]; 85 | } 86 | 87 | - (void)fillWithChildViewController:(NSViewController *)childViewController 88 | { 89 | NSParameterAssert(childViewController != nil); 90 | 91 | [self addChildViewController:childViewController]; 92 | [self fillViewWithChildView:(childViewController.view)]; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /Lantern/PageMapper+UI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageMapper+UI.swift 3 | // Lantern 4 | // 5 | // Created by Patrick Smith on 1/11/16. 6 | // Copyright © 2016 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import LanternModel 11 | 12 | 13 | protocol PageMapperProvider : class { 14 | var pageMapper: PageMapper? { get } 15 | 16 | func clearPageMapper() 17 | func createPageMapper(primaryURL: URL) -> PageMapper? 18 | subscript(pageMapperCreatedCallback uuid: UUID) -> ((PageMapper) -> ())? { get set } 19 | 20 | var activeURL: URL? { get set } 21 | subscript(activeURLChangedCallback uuid: UUID) -> ((URL?) -> ())? { get set } 22 | } 23 | 24 | extension NSResponder { 25 | var pageMapperProvider: PageMapperProvider? { 26 | return sequence(first: self, next: { $0.nextResponder }).reduce(PageMapperProvider?.none) { 27 | $0 ?? ($1 as? PageMapperProvider) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Lantern/SiteMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SiteMenuItem.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 1/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LanternModel 11 | import BurntCocoaUI 12 | 13 | 14 | enum SiteMenuItem { 15 | case choice(SiteChoice) 16 | case loadingSavedSites 17 | case noSavedSitesYet 18 | } 19 | 20 | extension SiteMenuItem: UIChoiceRepresentative { 21 | var title: String { 22 | switch self { 23 | case .choice(let siteChoice): 24 | switch siteChoice { 25 | case .savedSite(let site): 26 | return site.name 27 | case .custom: 28 | return "Custom" 29 | } 30 | case .loadingSavedSites: 31 | return "(Loading Saved Sites)" 32 | case .noSavedSitesYet: 33 | return "(No Saved Sites Yet)" 34 | } 35 | } 36 | 37 | var tag: Int? { 38 | return nil 39 | } 40 | 41 | typealias UniqueIdentifier = String 42 | 43 | var uniqueIdentifier: UniqueIdentifier { 44 | switch self { 45 | case .choice(let siteChoice): 46 | switch siteChoice { 47 | case .savedSite(let site): 48 | let url = site.homePageURL 49 | guard var urlComponents = URLComponents(url: site.homePageURL, resolvingAgainstBaseURL: true) else { 50 | return url.absoluteString 51 | } 52 | urlComponents.fragment = nil 53 | return urlComponents.string ?? url.absoluteString 54 | case .custom: 55 | return "Custom" 56 | } 57 | case .loadingSavedSites: 58 | return "LoadingSavedSites" 59 | case .noSavedSitesYet: 60 | return "NoSavedSitesYet" 61 | } 62 | } 63 | } 64 | 65 | extension SiteMenuItem: CustomDebugStringConvertible { 66 | var debugDescription: String { 67 | switch self { 68 | case .choice(let siteChoice): 69 | switch siteChoice { 70 | case .savedSite(let site): 71 | return "\(site.name) \(site.UUID.uuidString)" 72 | default: 73 | break 74 | } 75 | default: 76 | break 77 | } 78 | 79 | return uniqueIdentifier 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Lantern/SiteSettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SiteSettingsViewController.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 30/03/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import LanternModel 11 | 12 | 13 | class SiteSettingsViewController: NSViewController { 14 | 15 | var modelManager: ModelManager! 16 | var mainState: MainState! 17 | 18 | // MARK: Outlets 19 | @IBOutlet var homePageURLField: NSTextField! 20 | @IBOutlet var nameField: NSTextField! 21 | @IBOutlet var saveInFavoritesButton: NSButton! { 22 | didSet { 23 | saveInFavoritesButton.target = self 24 | saveInFavoritesButton.action = #selector(toggleSaveInFavorites(_:)) 25 | } 26 | } 27 | 28 | // MARK: Callbacks 29 | var favoriteNameForURL: ((_ url: URL) -> String?)? 30 | var onSaveSite: ((_ viewController: SiteSettingsViewController) -> ())? 31 | 32 | // MARK: Init 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | 36 | homePageURLField.delegate = self 37 | } 38 | 39 | func prepareForReuse() { 40 | homePageURLField.stringValue = "" 41 | nameField.stringValue = "" 42 | } 43 | 44 | // MARK: - 45 | 46 | var editingFavorite = false 47 | 48 | func editFavorite(url: URL, name: String) { 49 | self.updateUI(url: url, favoriteName: name, editingFavorite: true) 50 | } 51 | 52 | func editVisited(url: URL) { 53 | self.updateUI(url: url) 54 | } 55 | 56 | func reset() { 57 | self.updateUI() 58 | } 59 | 60 | private func updateUI(url: URL? = nil, favoriteName: String? = nil, editingFavorite: Bool = false) { 61 | // Make sure view has loaded 62 | _ = self.view 63 | 64 | let urlString = url?.absoluteString ?? "" 65 | var name = "" 66 | self.editingFavorite = false 67 | 68 | if let favoriteName = favoriteName { 69 | name = favoriteName 70 | self.editingFavorite = editingFavorite 71 | } 72 | 73 | homePageURLField.stringValue = urlString 74 | saveInFavoritesButton.state = editingFavorite ? NSControl.StateValue.on : NSControl.StateValue.off 75 | nameField.isEnabled = editingFavorite 76 | nameField.stringValue = name 77 | } 78 | 79 | struct Output { 80 | var siteValues: SiteValues 81 | var saveInFavorites: Bool 82 | } 83 | 84 | func read(siteUUID: UUID? = nil) throws -> Output? { 85 | // Make sure view has loaded 86 | _ = self.view 87 | 88 | let saveInFavorites = saveInFavoritesButton.state == NSControl.StateValue.on 89 | 90 | let name: String 91 | if saveInFavorites { 92 | name = try ValidationError.validateString(nameField.stringValue, identifier: "Name") 93 | } 94 | else { 95 | name = "" 96 | } 97 | 98 | let url: URL 99 | do { 100 | url = try ValidationError.validate(urlString: homePageURLField.stringValue, identifier: "Primary URL") 101 | } 102 | catch let error as ValidationError { 103 | switch (saveInFavorites, error) { 104 | case (false, ValidationError.stringIsEmpty): 105 | return nil 106 | default: 107 | throw error 108 | } 109 | } 110 | 111 | let siteValues = SiteValues(name: name, homePageURL: url, UUID: siteUUID ?? UUID()) 112 | 113 | return Output(siteValues: siteValues, saveInFavorites: saveInFavorites) 114 | } 115 | 116 | // MARK: - Actions 117 | 118 | @IBAction func createSite(_ sender: NSButton) { 119 | onSaveSite?(self) 120 | } 121 | 122 | @IBAction func toggleSaveInFavorites(_ sender: NSButton) { 123 | let hasFavorite = sender.state == NSControl.StateValue.on 124 | nameField.isEnabled = hasFavorite 125 | } 126 | } 127 | 128 | // MARK: - 129 | 130 | extension SiteSettingsViewController : NSTextFieldDelegate { // MARK: NSTextFieldDelegate 131 | func controlTextDidChange(_ notification: Notification) { 132 | guard !editingFavorite else { 133 | return 134 | } 135 | 136 | guard homePageURLField === (notification.object as! NSTextField) else { 137 | return 138 | } 139 | 140 | guard let url = try? ValidationError.validate(urlString: homePageURLField.stringValue, identifier: "Primary URL") else { 141 | return 142 | } 143 | 144 | guard url.absoluteString == homePageURLField.stringValue else { 145 | return 146 | } 147 | 148 | guard let favoriteNameForURL = self.favoriteNameForURL else { 149 | return 150 | } 151 | 152 | if let name = favoriteNameForURL(url) { 153 | self.updateUI(url: url, favoriteName: name) 154 | } else { 155 | self.updateUI(url: url) 156 | } 157 | } 158 | } 159 | 160 | extension SiteSettingsViewController : NSPopoverDelegate { // MARK: NSPopoverDelegate 161 | func popoverWillClose(_ notification: Notification) { 162 | onSaveSite?(self) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Lantern/SourcePreviewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourcePreviewViewController.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 27/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import LanternModel 11 | 12 | 13 | enum SourcePreviewTabItemSection: String { 14 | case Main = "Main" 15 | case HTMLHead = "HTMLHead" 16 | case HTMLBody = "HTMLBody" 17 | 18 | var stringValue: String { return rawValue } 19 | 20 | func titleWithPageInfo(_ pageInfo: PageInfo) -> String { 21 | switch self { 22 | case .Main: 23 | if let MIMEType = pageInfo.MIMEType { 24 | return MIMEType.stringValue 25 | } 26 | else { 27 | return "Main" 28 | } 29 | case .HTMLHead: 30 | return "" 31 | case .HTMLBody: 32 | return "" 33 | } 34 | } 35 | } 36 | 37 | 38 | class SourcePreviewTabViewController: NSTabViewController { 39 | override func viewDidLoad() { 40 | super.viewDidLoad() 41 | 42 | //tabView.delegate = self 43 | } 44 | 45 | var pageInfo: PageInfo! { 46 | didSet { 47 | switch pageInfo.baseContentType { 48 | case .localHTMLPage: 49 | updateForHTMLPreview() 50 | default: 51 | updateForGeneralPreview() 52 | } 53 | } 54 | } 55 | 56 | func updateWithSections(_ sections: [SourcePreviewTabItemSection]) { 57 | let tabViewItems = sections.map { section in 58 | self.newSourcePreviewTabViewItem(section) 59 | } 60 | 61 | // This crashes for some reason 62 | // self.tabViewItems = tabViewItems 63 | 64 | let existingItems = self.tabViewItems 65 | for existingItem in existingItems { 66 | removeTabViewItem(existingItem) 67 | } 68 | for tabViewItem in tabViewItems { 69 | addTabViewItem(tabViewItem) 70 | } 71 | 72 | //updateSourceTextForSection(sections[0], tabViewItem: tabViewItems[0]) 73 | } 74 | 75 | func updateForGeneralPreview() { 76 | updateWithSections([.Main]) 77 | } 78 | 79 | func updateForHTMLPreview() { 80 | updateWithSections([.HTMLHead, .HTMLBody]) 81 | } 82 | 83 | func newSourcePreviewTabViewItem(_ section: SourcePreviewTabItemSection) -> NSTabViewItem { 84 | let item = NSTabViewItem(identifier: section.stringValue) 85 | 86 | let vc = newSourcePreviewController() 87 | vc.wantsToDismiss = { 88 | self.dismiss(nil) 89 | } 90 | item.viewController = vc 91 | 92 | item.label = section.titleWithPageInfo(pageInfo) 93 | return item 94 | } 95 | 96 | func newSourcePreviewController() -> SourcePreviewViewController { 97 | return NSStoryboard.lantern_contentPreviewStoryboard.instantiateController(withIdentifier: "Source Preview View Controller") as! SourcePreviewViewController 98 | } 99 | 100 | func updateSourceTextForSection(_ section: SourcePreviewTabItemSection, tabViewItem: NSTabViewItem) { 101 | let vc = tabViewItem.viewController as! SourcePreviewViewController 102 | 103 | if let contentInfo = self.pageInfo.contentInfo { 104 | switch section { 105 | case .Main: 106 | setSourceText(contentInfo.stringContent, forSourcePreviewViewController: vc) 107 | case .HTMLHead: 108 | setSourceText(contentInfo.HTMLHeadStringContent, forSourcePreviewViewController: vc) 109 | case .HTMLBody: 110 | setSourceText(contentInfo.HTMLBodyStringContent, forSourcePreviewViewController: vc) 111 | } 112 | } 113 | else { 114 | setSourceText("(none)", forSourcePreviewViewController: vc) 115 | } 116 | } 117 | 118 | func setSourceText(_ sourceText: String?, forSourcePreviewViewController vc: SourcePreviewViewController) { 119 | vc.sourceText = sourceText ?? "(None)" 120 | } 121 | 122 | override func tabView(_ tabView: NSTabView, willSelect tabViewItem: NSTabViewItem?) { 123 | if let 124 | tabViewItem = tabViewItem, 125 | let identifier = tabViewItem.identifier as? String, 126 | let section = SourcePreviewTabItemSection(rawValue: identifier) 127 | { 128 | updateSourceTextForSection(section, tabViewItem: tabViewItem) 129 | } 130 | } 131 | 132 | override func keyDown(with theEvent: NSEvent) { 133 | if theEvent.burnt_isSpaceKey { 134 | // Just like QuickLook, use space to dismiss. 135 | dismiss(nil) 136 | } 137 | } 138 | } 139 | 140 | extension SourcePreviewTabViewController: NSPopoverDelegate { 141 | func popoverWillShow(_ notification: Notification) { 142 | let popover = notification.object as! NSPopover 143 | popover.appearance = NSAppearance(named: NSAppearance.Name.vibrantDark) 144 | //popover.appearance = NSAppearance(named: NSAppearanceNameLightContent) 145 | //popover.appearance = .HUD 146 | } 147 | 148 | func popoverDidShow(_ notification: Notification) { 149 | if let window = view.window , selectedTabViewItemIndex != -1 { 150 | let tabViewItem = tabViewItems[selectedTabViewItemIndex] 151 | let vc = tabViewItem.viewController as! SourcePreviewViewController 152 | window.makeFirstResponder(vc.textView) 153 | } 154 | 155 | view.layoutSubtreeIfNeeded() 156 | } 157 | } 158 | 159 | 160 | class SourcePreviewViewController: NSViewController { 161 | 162 | @IBOutlet var textView: SourcePreviewTextView! 163 | 164 | var wantsToDismiss: (() -> ())? 165 | 166 | override func viewDidLoad() { 167 | super.viewDidLoad() 168 | // Do view setup here. 169 | 170 | textView.wantsToDismiss = wantsToDismiss 171 | } 172 | 173 | let defaultTextAttributes: [String: AnyObject] = [ 174 | convertFromNSAttributedStringKey(NSAttributedString.Key.font): NSFont(name: "Menlo", size: 11.0)!, 175 | convertFromNSAttributedStringKey(NSAttributedString.Key.foregroundColor): NSColor.highlightColor 176 | ] 177 | 178 | var sourceText: String! { 179 | didSet { 180 | _ = self.view // Make sure view has loaded 181 | 182 | if let textStorage = textView.textStorage { 183 | let attributes = defaultTextAttributes 184 | let newAttributedString = NSAttributedString(string: sourceText, attributes:convertToOptionalNSAttributedStringKeyDictionary(attributes)) 185 | textStorage.replaceCharacters(in: NSMakeRange(0, textStorage.length), with: newAttributedString) 186 | } 187 | } 188 | } 189 | } 190 | 191 | 192 | class SourcePreviewTextView: NSTextView { 193 | 194 | var wantsToDismiss: (() -> ())? 195 | 196 | override func keyDown(with theEvent: NSEvent) { 197 | if theEvent.burnt_isSpaceKey { 198 | wantsToDismiss?() 199 | } 200 | else { 201 | super.keyDown(with: theEvent) 202 | } 203 | } 204 | } 205 | 206 | // Helper function inserted by Swift 4.2 migrator. 207 | fileprivate func convertFromNSAttributedStringKey(_ input: NSAttributedString.Key) -> String { 208 | return input.rawValue 209 | } 210 | 211 | // Helper function inserted by Swift 4.2 migrator. 212 | fileprivate func convertToOptionalNSAttributedStringKeyDictionary(_ input: [String: Any]?) -> [NSAttributedString.Key: Any]? { 213 | guard let input = input else { return nil } 214 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (NSAttributedString.Key(rawValue: key), value)}) 215 | } 216 | -------------------------------------------------------------------------------- /Lantern/TableView-OutlineView-Subclass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableCellView.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 4/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | // An extension lets us both subclass NSTableView and NSOutlineView with the same functionality 13 | extension NSTableView { 14 | // Find a cell view, or a row view, that has a menu. (e.g. NSResponder’s menu: NSMenu?) 15 | func burnt_menuForEventFromCellOrRowViews(_ event: NSEvent) -> NSMenu? { 16 | let point = convert(event.locationInWindow, from: nil) 17 | let row = self.row(at: point) 18 | if row != -1 { 19 | if let rowView = rowView(atRow: row, makeIfNecessary: true) { 20 | let column = self.column(at: point) 21 | if column != -1 { 22 | if let cellView = rowView.view(atColumn: column) as? NSTableCellView { 23 | if let cellMenu = cellView.menu(for: event) { 24 | return cellMenu 25 | } 26 | } 27 | } 28 | 29 | if let rowMenu = rowView.menu(for: event) { 30 | return rowMenu 31 | } 32 | } 33 | } 34 | 35 | return nil 36 | } 37 | } 38 | 39 | 40 | class OutlineView: NSOutlineView { 41 | override func menu(for event: NSEvent) -> NSMenu? { 42 | // Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found 43 | self.menu = burnt_menuForEventFromCellOrRowViews(event) 44 | 45 | return super.menu(for: event) 46 | } 47 | } 48 | 49 | class TableView: NSTableView { 50 | override func menu(for event: NSEvent) -> NSMenu? { 51 | // Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found 52 | self.menu = burnt_menuForEventFromCellOrRowViews(event) 53 | 54 | return super.menu(for: event) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Lantern/ValidatedStringValue+Presentation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValidatedStringValue+Presentation.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 3/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import LanternModel 11 | 12 | 13 | extension ValidatedStringValue { 14 | static var loadingAlphaValueForPresentation: CGFloat = 0.2 15 | 16 | var alphaValueForPresentation: CGFloat { 17 | switch self { 18 | case .validString: 19 | return 1.0 20 | default: 21 | return 0.3 22 | } 23 | } 24 | 25 | var stringValueForPresentation: String { 26 | switch self { 27 | case .validString(let stringValue): 28 | return stringValue 29 | case .validKeyValue(_, let value): 30 | return value 31 | case .notRequested: 32 | return "(not requested)" 33 | case .missing: 34 | return "(none)" 35 | case .empty: 36 | return "(empty)" 37 | case .multiple: 38 | return "(multiple)" 39 | case .invalid: 40 | return "(invalid)" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Lantern/ViewState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewState.swift 3 | // Lantern 4 | // 5 | // Created by Patrick Smith on 8/11/19. 6 | // Copyright © 2019 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | enum ToggleableViewIdentifier : String { 10 | case browser, meta 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Lantern/WKUserContentController+bundledScripts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WKUserContentController+bundledScripts.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 22/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import WebKit 11 | 12 | 13 | extension WKUserContentController { 14 | func addBundledUserScript(_ scriptNameInBundle: String, injectAtStart: Bool = false, injectAtEnd: Bool = false, forMainFrameOnly: Bool = true, sourceReplacements: [String:String]? = nil) { 15 | assert(injectAtStart || injectAtEnd, "User script must either be injected at start or at end. Add injectAtStart: true or injectAtEnd: true") 16 | 17 | let scriptURL = Bundle.main.url(forResource: scriptNameInBundle, withExtension: "js")! 18 | let scriptSource = try! NSMutableString(contentsOf: scriptURL, usedEncoding: nil) 19 | 20 | if let sourceReplacements = sourceReplacements { 21 | func replaceInTemplate(find target: String, replace replacement: String) { 22 | scriptSource.replaceOccurrences(of: target, with: replacement, options: NSString.CompareOptions(rawValue: 0), range: NSMakeRange(0, scriptSource.length)) 23 | } 24 | 25 | for (placeholderID, value) in sourceReplacements { 26 | replaceInTemplate(find: placeholderID, replace: value) 27 | } 28 | } 29 | 30 | if injectAtStart { 31 | let script = WKUserScript(source: scriptSource as String, injectionTime: .atDocumentStart, forMainFrameOnly: forMainFrameOnly) 32 | self.addUserScript(script) 33 | } 34 | 35 | if injectAtEnd { 36 | let script = WKUserScript(source: scriptSource as String, injectionTime: .atDocumentEnd, forMainFrameOnly: forMainFrameOnly) 37 | self.addUserScript(script) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Lantern/code-sign-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Source: http://stackoverflow.com/a/11284404/652615 4 | 5 | # WARNING: You may have to run Clean in Xcode after changing CODE_SIGN_IDENTITY! 6 | 7 | # Verify that $CODE_SIGN_IDENTITY is set 8 | if [ -z "${CODE_SIGN_IDENTITY}" ] ; then 9 | echo "CODE_SIGN_IDENTITY needs to be set for framework code-signing!" 10 | 11 | if [ "${CONFIGURATION}" = "Release" ] ; then 12 | exit 1 13 | else 14 | # Code-signing is optional for non-release builds. 15 | exit 0 16 | fi 17 | fi 18 | 19 | if [ -z "${CODE_SIGN_ENTITLEMENTS}" ] ; then 20 | echo "CODE_SIGN_ENTITLEMENTS needs to be set for framework code-signing!" 21 | 22 | if [ "${CONFIGURATION}" = "Release" ] ; then 23 | exit 1 24 | else 25 | # Code-signing is optional for non-release builds. 26 | exit 0 27 | fi 28 | fi 29 | 30 | ITEMS="" 31 | 32 | FRAMEWORK_DIR="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 33 | if [ -d "$FRAMEWORK_DIR" ] ; then 34 | FRAMEWORKS=$(find "${FRAMEWORK_DIR}" -depth -type d -name "*.framework" -or -name "*.dylib" -or -name "*.bundle" | sed -e "s/\(.*framework\)/\1\/Versions\/A\//") 35 | RESULT=$? 36 | if [[ $RESULT != 0 ]] ; then 37 | exit 1 38 | fi 39 | 40 | ITEMS="${FRAMEWORKS}" 41 | fi 42 | 43 | LOGINITEMS_DIR="${TARGET_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/Library/LoginItems/" 44 | if [ -d "$LOGINITEMS_DIR" ] ; then 45 | LOGINITEMS=$(find "${LOGINITEMS_DIR}" -depth -type d -name "*.app") 46 | RESULT=$? 47 | if [[ $RESULT != 0 ]] ; then 48 | exit 1 49 | fi 50 | 51 | ITEMS="${ITEMS}"$'\n'"${LOGINITEMS}" 52 | fi 53 | 54 | # Prefer the expanded name, if available. 55 | CODE_SIGN_IDENTITY_FOR_ITEMS="${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 56 | if [ "${CODE_SIGN_IDENTITY_FOR_ITEMS}" = "" ] ; then 57 | # Fall back to old behavior. 58 | CODE_SIGN_IDENTITY_FOR_ITEMS="${CODE_SIGN_IDENTITY}" 59 | fi 60 | 61 | echo "Identity:" 62 | echo "${CODE_SIGN_IDENTITY_FOR_ITEMS}" 63 | 64 | echo "Entitlements:" 65 | echo "${CODE_SIGN_ENTITLEMENTS}" 66 | 67 | echo "Found:" 68 | echo "${ITEMS}" 69 | 70 | # Change the Internal Field Separator (IFS) so that spaces in paths will not cause problems below. 71 | SAVEIFS=$IFS 72 | IFS=$(echo -en "\n\b") 73 | 74 | # Loop through all items. 75 | for ITEM in $ITEMS; 76 | do 77 | echo "Signing '${ITEM}'" 78 | codesign --force --verbose --sign "${CODE_SIGN_IDENTITY_FOR_ITEMS}" --entitlements "${CODE_SIGN_ENTITLEMENTS}" "${ITEM}" 79 | RESULT=$? 80 | if [[ $RESULT != 0 ]] ; then 81 | IFS=$SAVEIFS 82 | exit 1 83 | fi 84 | done 85 | 86 | # Restore $IFS. 87 | IFS=$SAVEIFS -------------------------------------------------------------------------------- /Lantern/console.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function trace() { 3 | try { 4 | throw new Error('myError'); 5 | } 6 | catch(e) { 7 | return e.stack; 8 | } 9 | 10 | return ''; 11 | } 12 | 13 | function consoleMessage(type, messageArguments) { 14 | window.webkit.messageHandlers.console.postMessage({ 15 | type: type, 16 | 'arguments': JSON.stringify(messageArguments), 17 | 'trace': trace() 18 | }); 19 | } 20 | 21 | consoleMessage('test', ['testing']); 22 | 23 | window.console.log = function() { 24 | consoleMessage('log', arguments); 25 | }; 26 | 27 | window.console.error = function() { 28 | consoleMessage('error', arguments); 29 | }; 30 | 31 | window.console.warn = function() { 32 | consoleMessage('warn', arguments); 33 | }; 34 | })(); 35 | -------------------------------------------------------------------------------- /Lantern/insertHoverlytics.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (window.top === window.self) { 3 | window.webkit.messageHandlers.googleAPIAuthorizationChanged.postMessage({ 4 | googleClientAPILoaded: false, 5 | loadingHoverlyticsScript: true 6 | }); 7 | 8 | var s = document.createElement('script'); 9 | s.src = ('http://hoverlytics.burntcaramel.com/go/v3/go.js?_='+Math.random()); 10 | document.body.appendChild(s); 11 | } 12 | })(); -------------------------------------------------------------------------------- /Lantern/panelAuthorizationChanged.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (window.location.hostname != "hoverlytics.burntcaramel.com") { 3 | return; 4 | } 5 | 6 | // Make sure Backbone app has been created by queuing in jQuery's ready. 7 | jQuery(document).ready(function() { 8 | var hoverlyticsApp = window.App; 9 | if (hoverlyticsApp) { 10 | var profile = hoverlyticsApp.profile; 11 | profile.on('change:isAuthorized', function() { 12 | var token = gapi.auth.getToken(); 13 | window.webkit.messageHandlers.googleAPIAuthorizationChanged.postMessage({ 14 | tokenJSONString: JSON.stringify(token) 15 | }); 16 | }, window); 17 | } 18 | }); 19 | })(); -------------------------------------------------------------------------------- /Lantern/setPanelAuthorizationToken.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (window.location.hostname != "hoverlytics.burntcaramel.com") { 3 | return; 4 | } 5 | 6 | window.webkit.messageHandlers.googleAPIAuthorizationChanged.postMessage({ 7 | googleClientAPILoaded: false, 8 | addGoogleClientAPIReadyCallback: (typeof window.addGoogleClientAPIReadyCallback) 9 | }); 10 | 11 | var token = __TOKEN__; 12 | 13 | window.addGoogleClientAPIReadyCallback(function() { 14 | gapi.auth.setToken(token); 15 | 16 | window.webkit.messageHandlers.googleAPIAuthorizationChanged.postMessage({ 17 | googleClientAPILoaded: true, 18 | setToken: (typeof gapi.auth.setToken), 19 | getToken: JSON.stringify(gapi.auth.getToken()) 20 | }); 21 | }); 22 | 23 | // Make sure Backbone app has been created by queuing in jQuery's ready. 24 | jQuery(document).ready(function() { 25 | var hoverlyticsApp = window.App; 26 | if (hoverlyticsApp) { 27 | var profile = hoverlyticsApp.profile; 28 | 29 | /* 30 | var oldCheckGoogleAuthorization = profile.checkGoogleAuthorization; 31 | 32 | profile.checkGoogleAuthorization = function() { 33 | window.webkit.messageHandlers.googleAPIAuthorizationChanged.postMessage({ 34 | googleClientAPILoaded: true, 35 | changeProfileMethod: true 36 | }); 37 | 38 | //oldCheckGoogleAuthorization({performAuthorization: false}); 39 | profile.handleGoogleAuthorizationResult(token); 40 | }; 41 | */ 42 | 43 | var oldHandleGoogleAuthorizationResult = profile.handleGoogleAuthorizationResult 44 | 45 | profile.handleGoogleAuthorizationResult = function(authorizationResult) { 46 | window.webkit.messageHandlers.googleAPIAuthorizationChanged.postMessage({ 47 | googleClientAPILoaded: true, 48 | authorizationResult: JSON.stringify(authorizationResult) 49 | }); 50 | 51 | oldHandleGoogleAuthorizationResult.call(profile, authorizationResult); 52 | } 53 | } 54 | }); 55 | })(); -------------------------------------------------------------------------------- /Lantern/userAgent.js: -------------------------------------------------------------------------------- 1 | window.navigator.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/600.4.10 (KHTML, like Gecko) Version/8.0.4 Safari/600.4.10"; -------------------------------------------------------------------------------- /Lantern/windowClose.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var oldWindowClose = window.close; 3 | window.close = function() { 4 | window.webkit.messageHandlers.windowDidClose.postMessage(true); 5 | 6 | oldWindowClose(); 7 | }; 8 | })(); -------------------------------------------------------------------------------- /LanternModel/Dictionary+UpdateClosure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+UpdateClosure.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 28/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension Dictionary { 13 | mutating func updateValueForKey(_ key: Key, updater: ((_ previousValue: Value?) -> Value)) { 14 | let previousValue = self[key] 15 | self[key] = updater(previousValue) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LanternModel/EnabledFeatures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnabledFeatures.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 1/06/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | let USE_CLOUD_KIT = true 13 | -------------------------------------------------------------------------------- /LanternModel/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 Burnt Caramel. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LanternModel/LanternModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // LanternModel.h 3 | // LanternModel 4 | // 5 | // Created by Patrick Smith on 30/03/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for LanternModel. 12 | FOUNDATION_EXPORT double LanternModelVersionNumber; 13 | 14 | //! Project version string for LanternModel. 15 | FOUNDATION_EXPORT const unsigned char LanternModelVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /LanternModel/ModelManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelManager.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 31/03/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | //import BurntFoundation 11 | import Syrup 12 | 13 | 14 | enum RecordType: String { 15 | case Site = "Site" 16 | 17 | var identifier: String { 18 | return self.rawValue 19 | } 20 | } 21 | 22 | 23 | public enum ModelManagerNotification: String { 24 | case allSitesDidChange = "LanternModel.ModelManager.AllSitesDidChangeNotification" 25 | 26 | public var notificationName: String { 27 | return self.rawValue 28 | } 29 | } 30 | 31 | 32 | public final class ErrorReceiver { 33 | public var errorCallback: ((_ error: NSError) -> ())? 34 | 35 | func receiveError(_ error: NSError) { 36 | errorCallback?(error) 37 | } 38 | } 39 | 40 | 41 | enum SitesLoadingProgression : Progression { 42 | case none 43 | case loadFromFile(fileURL: URL) 44 | case jsonData(Data) 45 | case json(Any) 46 | case sitesList(sitesList: [SiteValues], needsSaving: Bool) 47 | 48 | typealias Result = [SiteValues] 49 | 50 | enum ErrorKind : Error { 51 | case invalidJSON(json: Any) 52 | } 53 | 54 | mutating func updateOrDeferNext() throws -> Deferred? { 55 | switch self { 56 | case let .loadFromFile(fileURL): 57 | do { 58 | let data = try Data(contentsOf: fileURL, options: .mappedIfSafe) 59 | self = .jsonData(data) 60 | } 61 | catch let error as NSError { 62 | if error.domain == NSCocoaErrorDomain && error.code == NSFileReadNoSuchFileError { 63 | self = .sitesList(sitesList: [], needsSaving: false) 64 | } 65 | } 66 | case let .jsonData(data): 67 | self = try .json(JSONSerialization.jsonObject(with: data, options: [])) 68 | case let .json(json): 69 | guard 70 | let jsonDictionary = json as? [String: Any], 71 | let itemsJSON = jsonDictionary["items"] as? [Any] 72 | else { throw ErrorKind.invalidJSON(json: json) } 73 | let sitesList = try itemsJSON.map { (json: Any) -> SiteValues in 74 | guard let jsonObject = json as? [String: Any] 75 | else { throw ErrorKind.invalidJSON(json: json) } 76 | 77 | return SiteValues(fromJSON: jsonObject) 78 | } 79 | 80 | var seenUUIDs = Set() 81 | let uniqueSites = sitesList.filter { site in 82 | if seenUUIDs.contains(site.UUID) { 83 | return false 84 | } 85 | 86 | seenUUIDs.insert(site.UUID) 87 | return true 88 | } 89 | 90 | self = .sitesList(sitesList: uniqueSites, needsSaving: false) 91 | case .sitesList, .none: 92 | break 93 | } 94 | return nil 95 | } 96 | 97 | var result: Result? { 98 | guard case let .sitesList(result, _) = self else { return nil } 99 | return result 100 | } 101 | 102 | mutating func update(_ siteValues: SiteValues, uuid: Foundation.UUID) { 103 | switch self { 104 | case var .sitesList(sitesList, _): 105 | guard let index = sitesList.firstIndex(where: { $0.UUID == uuid }) 106 | else { return } 107 | sitesList[index] = siteValues 108 | self = .sitesList(sitesList: sitesList, needsSaving: true) 109 | default: 110 | break 111 | } 112 | } 113 | 114 | mutating func addOrUpdate(_ siteValues: SiteValues) { 115 | let url = siteValues.homePageURL.absoluteURL 116 | switch self { 117 | case var .sitesList(sitesList, _): 118 | if let index = sitesList.firstIndex(where: { $0.UUID == siteValues.UUID }) { 119 | sitesList[index] = siteValues 120 | } 121 | else if let index = sitesList.firstIndex(where: { $0.homePageURL.absoluteURL == url }) { 122 | sitesList[index] = siteValues 123 | } 124 | else { 125 | sitesList.append(siteValues) 126 | } 127 | self = .sitesList(sitesList: sitesList, needsSaving: true) 128 | default: 129 | break 130 | } 131 | } 132 | 133 | mutating func remove(url: URL) { 134 | switch self { 135 | case var .sitesList(sitesList, _): 136 | sitesList = sitesList.filter{ $0.homePageURL != url } 137 | self = .sitesList(sitesList: sitesList, needsSaving: true) 138 | default: 139 | break 140 | } 141 | } 142 | } 143 | 144 | enum SitesSavingProgression : Progression { 145 | case saveToFile(fileURL: URL, sites: [SiteValues]) 146 | case serializeJSON([String: Any], fileURL: URL) 147 | case writeData(Data, fileURL: URL) 148 | case savedFile(fileURL: URL) 149 | 150 | typealias Result = URL 151 | 152 | enum ErrorKind : Error { 153 | case invalidJSON 154 | case jsonSerialization(error: Error) 155 | case fileWriting(error: Error) 156 | } 157 | 158 | mutating func updateOrDeferNext() throws -> Deferred? { 159 | switch self { 160 | case let .saveToFile(fileURL, sites): 161 | let json = [ 162 | "items": sites.map{ $0.toJSON() } 163 | ] as [String: [[String: Any]]] 164 | self = .serializeJSON(json, fileURL: fileURL) 165 | case let .serializeJSON(json, fileURL): 166 | do { 167 | if !JSONSerialization.isValidJSONObject(json) { 168 | throw ErrorKind.invalidJSON 169 | } 170 | self = try .writeData(JSONSerialization.data(withJSONObject: json), fileURL: fileURL) 171 | } 172 | catch { 173 | throw ErrorKind.jsonSerialization(error: error) 174 | } 175 | case let .writeData(data, fileURL): 176 | try data.write(to: fileURL, options: .atomic) 177 | self = .savedFile(fileURL: fileURL) 178 | case .savedFile: 179 | break 180 | } 181 | return nil 182 | } 183 | 184 | var result: Result? { 185 | guard case let .savedFile(fileURL) = self else { return nil } 186 | return fileURL 187 | } 188 | } 189 | 190 | open class ModelManager { 191 | var isAvailable = false 192 | 193 | public let errorReceiver = ErrorReceiver() 194 | 195 | fileprivate var storeDirectory: SystemDirectory 196 | fileprivate var sitesURL: URL? 197 | 198 | // Change this value, by mutating it, and it will be saved to disk 199 | fileprivate var sitesLoadingProgression: SitesLoadingProgression = .none { 200 | didSet { 201 | let progression = sitesLoadingProgression 202 | notifyAllSitesDidChange() 203 | 204 | switch progression { 205 | case .loadFromFile: 206 | progression / .utility >>= { [weak self] useResult in 207 | guard let receiver = self else { return } 208 | do { 209 | let sites = try useResult() 210 | receiver.sitesLoadingProgression = .sitesList(sitesList: sites, needsSaving: false) 211 | } 212 | catch let error { 213 | print("Error loading local sites \(error)") 214 | } 215 | } 216 | // If changed, and needs saving, then save 217 | case let .sitesList(sites, needsSaving): 218 | if needsSaving, let sitesURL = sitesURL { 219 | sitesSavingProgression = .saveToFile(fileURL: sitesURL, sites: sites) 220 | } 221 | default: break 222 | } 223 | } 224 | } 225 | 226 | fileprivate var sitesSavingProgression: SitesSavingProgression? { 227 | didSet(newValue) { 228 | guard let progression = sitesSavingProgression else { return } 229 | progression / .utility >>= { useResult in 230 | do { 231 | let _ = try useResult() 232 | } 233 | catch let error { 234 | print("Error saving local sites \(error)") 235 | } 236 | } 237 | } 238 | } 239 | 240 | public var allSites: [SiteValues]? { 241 | return sitesLoadingProgression.result 242 | } 243 | 244 | public func siteWithURL(url: URL) -> SiteValues? { 245 | guard let sites = allSites else { return nil } 246 | return sites.first{ $0.homePageURL.absoluteURL == url.absoluteURL } 247 | } 248 | 249 | 250 | init() { 251 | storeDirectory = SystemDirectory(pathComponents: ["v1"], inUserDirectory: .applicationSupportDirectory, errorReceiver: errorReceiver.receiveError, useBundleIdentifier: true) 252 | storeDirectory.useOnQueue(DispatchQueue.main) { directoryURL in 253 | let jsonURL = directoryURL.appendingPathComponent("sites.json") 254 | self.sitesURL = jsonURL 255 | 256 | self.sitesLoadingProgression = SitesLoadingProgression.loadFromFile(fileURL: jsonURL) 257 | } 258 | 259 | } 260 | 261 | open class var sharedManager: ModelManager { 262 | struct Helper { 263 | static let sharedManager = ModelManager() 264 | } 265 | return Helper.sharedManager 266 | } 267 | 268 | func onSystemDirectoryError(_ error: NSError) { 269 | 270 | } 271 | 272 | func updateMainProperties() { 273 | 274 | } 275 | 276 | fileprivate func mainQueue_notify(_ identifier: ModelManagerNotification, userInfo: [String: Any]? = nil) { 277 | let nc = NotificationCenter.default 278 | nc.post(name: Notification.Name(rawValue: identifier.notificationName), object: self, userInfo: userInfo) 279 | } 280 | 281 | func notifyAllSitesDidChange() { 282 | self.mainQueue_notify(.allSitesDidChange) 283 | } 284 | 285 | public func addOrUpdateSite(values: SiteValues) { 286 | //sitesLoadingProgression.update(siteValues, uuid: uuid) 287 | sitesLoadingProgression.addOrUpdate(values) 288 | } 289 | 290 | public func removeSite(url: URL) { 291 | sitesLoadingProgression.remove(url: url) 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /LanternModel/NSData+ONOXMLDocument.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+ONOXMLDocument.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 29/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Ono 11 | 12 | 13 | extension Data { 14 | func stringRepresentationUsingONOXMLDocumentHints(_ document: ONOXMLDocument) -> String? { 15 | let stringEncoding = document.stringEncodingWithFallback() 16 | return NSString(data: self, encoding: stringEncoding.rawValue) as String? 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LanternModel/ONOXMLDocument+StringEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ONOXMLDocument+StringEncoding.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 29/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Ono 11 | 12 | 13 | extension ONOXMLDocument { 14 | func stringEncodingWithFallback(_ fallback: String.Encoding = String.Encoding.utf8) -> String.Encoding { 15 | var stringEncoding = self.stringEncoding 16 | if stringEncoding == 0 { 17 | stringEncoding = fallback.rawValue 18 | } 19 | return String.Encoding(rawValue: stringEncoding) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LanternModel/PageInfoRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageInfoRequest.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 2/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | import Ono 12 | import Syrup 13 | 14 | 15 | open class PageInfoRequest { 16 | typealias CompletionHandler = (_ info: PageInfo, _ infoRequest: PageInfoRequest) -> () 17 | 18 | public let URL: Foundation.URL 19 | public let includingContent: Bool 20 | public let method: Alamofire.HTTPMethod 21 | public let expectedBaseContentType: BaseContentType 22 | let completionHandler: CompletionHandler 23 | var request: Alamofire.Request? 24 | 25 | init(URL: Foundation.URL, expectedBaseContentType: BaseContentType, includingContent: Bool = false, completionHandler: @escaping CompletionHandler) { 26 | self.URL = URL 27 | self.expectedBaseContentType = expectedBaseContentType 28 | self.includingContent = includingContent 29 | self.method = includingContent ? Alamofire.HTTPMethod.get : Alamofire.HTTPMethod.head 30 | self.completionHandler = completionHandler 31 | } 32 | 33 | func copyNotIncludingContent() -> PageInfoRequest { 34 | return PageInfoRequest(URL: URL, expectedBaseContentType: expectedBaseContentType, includingContent: false, completionHandler: completionHandler) 35 | } 36 | } 37 | 38 | 39 | extension ResourceInfo { 40 | init(requestedURL: URL, includeContent: Bool, response: HTTPURLResponse, data: Data?) { 41 | let MIMEType = MIMETypeString(response.mimeType) 42 | let baseContentType: BaseContentType = MIMEType?.baseContentType ?? .unknown 43 | 44 | let byteCount: Int? 45 | let contentInfo: PageContentInfo? 46 | if let data = data , includeContent { 47 | byteCount = data.count 48 | contentInfo = PageContentInfo(data: data, localURL: requestedURL) 49 | } 50 | else { 51 | byteCount = nil 52 | contentInfo = nil 53 | } 54 | 55 | self.init(requestedURL: requestedURL, finalURL: response.url, statusCode: response.statusCode, baseContentType: baseContentType, MIMEType: MIMEType, byteCount: byteCount, contentInfo: contentInfo) 56 | } 57 | 58 | static func serializer(_ requestedURL: URL, includeContent: Bool) -> DataResponseSerializer { 59 | return DataResponseSerializer { request, response, data, error in 60 | guard error == nil else { return .failure(error!) } 61 | 62 | if let response = response { 63 | return .success( 64 | PageInfo(requestedURL: requestedURL, includeContent: includeContent, response: response, data: data) 65 | ) 66 | } 67 | else { 68 | let error = AFError.responseSerializationFailed(reason: .inputDataNil) 69 | return .failure(error) 70 | } 71 | } 72 | } 73 | } 74 | 75 | 76 | public enum ResourceInfoRetrievalStage : Progression { 77 | case getInfo(url: URL, requestManager: Alamofire.SessionManager) 78 | case getContent(url: URL, requestManager: Alamofire.SessionManager) 79 | 80 | case startedRequest(request: Alamofire.DataRequest, url: URL, includeContent: Bool) 81 | 82 | case success(info: PageInfo) 83 | 84 | public typealias Result = PageInfo 85 | 86 | enum ErrorKind: Error { 87 | case requestFailed(underlyingError: Error) 88 | } 89 | 90 | public mutating func updateOrDeferNext() throws -> Deferred? { 91 | switch self { 92 | case let .getInfo(url, requestManager): 93 | let request = requestManager.request(url, method: .head) 94 | self = .startedRequest(request: request, url: url, includeContent: false) 95 | 96 | case let .getContent(url, requestManager): 97 | let request = requestManager.request(url) 98 | self = .startedRequest(request: request, url: url, includeContent: true) 99 | 100 | case let .startedRequest(request, url, includeContent): 101 | return Deferred.future{ resolve in 102 | let serializer = ResourceInfo.serializer(url, includeContent: includeContent) 103 | request.response(responseSerializer: serializer) { response in 104 | switch response.result { 105 | case let .success(info): 106 | resolve{ .success(info: info) } 107 | case let .failure(error): 108 | resolve{ throw ErrorKind.requestFailed(underlyingError: error) } 109 | } 110 | } 111 | } 112 | case .success: break 113 | } 114 | return nil 115 | } 116 | 117 | public var result: Result? { 118 | guard case let .success(result) = self else { return nil } 119 | return result 120 | } 121 | } 122 | 123 | 124 | class PageInfoRequestQueue { 125 | let requestManager: Alamofire.SessionManager 126 | let maximumActiveRequests = 5 127 | var activeRequests = [PageInfoRequest]() 128 | var pendingRequests = [PageInfoRequest]() 129 | var willPerformHTTPRedirection: ((_ redirectionInfo: RequestRedirectionInfo) -> ())? 130 | var didFinishWithRequest: ((_ infoRequest: PageInfoRequest) -> ())? 131 | 132 | init() { 133 | let manager = Alamofire.SessionManager() 134 | self.requestManager = manager 135 | 136 | let managerDelegate = manager.delegate 137 | managerDelegate.taskWillPerformHTTPRedirection = { [weak self] session, task, response, request in 138 | #if DEBUG 139 | print("taskWillPerformHTTPRedirection") 140 | #endif 141 | 142 | if let 143 | willPerformHTTPRedirection = self?.willPerformHTTPRedirection, 144 | let originalRequest = task.originalRequest 145 | { 146 | let info = RequestRedirectionInfo(sourceRequest: originalRequest, nextRequest: request, statusCode: response.statusCode, MIMEType: MIMETypeString(response.mimeType)) 147 | willPerformHTTPRedirection(info) 148 | } 149 | 150 | return request 151 | } 152 | } 153 | 154 | func addRequestForURL(_ URL: Foundation.URL, expectedBaseContentType: BaseContentType, includingContent: Bool, highPriority: Bool = false, completionHandler: @escaping PageInfoRequest.CompletionHandler) -> PageInfoRequest? { 155 | let URL = URL.absoluteURL 156 | guard URL.host != nil else { 157 | return nil 158 | } 159 | 160 | let infoRequest = PageInfoRequest(URL: URL, expectedBaseContentType: expectedBaseContentType, includingContent: includingContent, completionHandler: completionHandler) 161 | 162 | if highPriority || activeRequests.count < maximumActiveRequests { 163 | performRequest(infoRequest) 164 | } 165 | else { 166 | pendingRequests.append(infoRequest) 167 | } 168 | 169 | return infoRequest 170 | } 171 | 172 | func downgradePendingRequestsToNotIncludeContent(_ decider: ((PageInfoRequest) -> Bool)) { 173 | pendingRequests = pendingRequests.map { request in 174 | if request.includingContent && decider(request) { 175 | return request.copyNotIncludingContent() 176 | } 177 | else { 178 | return request 179 | } 180 | } 181 | } 182 | 183 | func cancelRequestForURL(_ URL: Foundation.URL) { 184 | let URLAbsoluteString = URL.absoluteString 185 | 186 | func requestContainsURL(_ infoRequest: PageInfoRequest) -> Bool { 187 | return infoRequest.URL.absoluteString == URLAbsoluteString 188 | } 189 | 190 | for (index, request) in pendingRequests.enumerated() { 191 | if requestContainsURL(request) { 192 | pendingRequests.remove(at: index) 193 | break 194 | } 195 | } 196 | } 197 | 198 | fileprivate func performNextPendingRequest() { 199 | // Perform the request next at the top of the list. 200 | if let infoRequest = pendingRequests.first { 201 | pendingRequests.remove(at: 0) 202 | performRequest(infoRequest) 203 | } 204 | } 205 | 206 | fileprivate func activeRequestDidComplete(_ infoRequest: PageInfoRequest, withInfo info: PageInfo) { 207 | infoRequest.completionHandler(info, infoRequest) 208 | 209 | // Remove from active requests 210 | for (index, someRequest) in activeRequests.enumerated() { 211 | if someRequest === infoRequest { 212 | activeRequests.remove(at: index) 213 | break 214 | } 215 | } 216 | 217 | // Start the next request in queue going 218 | performNextPendingRequest() 219 | } 220 | 221 | var activeRetrievals = [ResourceInfoRetrievalStage]() 222 | var pendingRetrievals = [ResourceInfoRetrievalStage]() 223 | //let executionCustomizer = GCDExecutionCustomizer() 224 | 225 | fileprivate func performRetrieval(_ stage: ResourceInfoRetrievalStage) { 226 | stage / .utility >>= { useResult in 227 | do { 228 | let result = try useResult() 229 | } 230 | catch { 231 | // ERROR 232 | } 233 | } 234 | } 235 | 236 | fileprivate func performRequest(_ infoRequest: PageInfoRequest) { 237 | activeRequests.append(infoRequest) 238 | 239 | // An Alamofire serializer to perform the parsing etc on the request’s background queue. 240 | let serializer = DataResponseSerializer { URLRequest, response, data, error in 241 | guard error == nil else { return .failure(error!) } 242 | 243 | if let response = response { 244 | let requestedURL = infoRequest.URL 245 | let MIMEType = MIMETypeString(response.mimeType) 246 | let baseContentType: BaseContentType = MIMEType?.baseContentType ?? .unknown 247 | 248 | let byteCount: Int? 249 | let contentInfo: PageContentInfo? 250 | if let data = data , infoRequest.includingContent { 251 | byteCount = data.count 252 | contentInfo = PageContentInfo(data: data, localURL: requestedURL) 253 | } 254 | else { 255 | byteCount = nil 256 | contentInfo = nil 257 | } 258 | 259 | let info = PageInfo(requestedURL: requestedURL, finalURL: response.url, statusCode: response.statusCode, baseContentType: baseContentType, MIMEType: MIMEType, byteCount: byteCount, contentInfo: contentInfo) 260 | 261 | return .success(info) 262 | } 263 | else { 264 | let error = AFError.responseSerializationFailed(reason: .inputDataNil) 265 | return .failure(error) 266 | } 267 | } 268 | 269 | // Perform the request 270 | let requestedURL = infoRequest.URL 271 | infoRequest.request = requestManager 272 | .request(requestedURL, method: infoRequest.method) 273 | .response(responseSerializer: serializer, completionHandler: { response in 274 | if case let .success(info) = response.result { 275 | self.activeRequestDidComplete(infoRequest, withInfo: info) 276 | } 277 | }) 278 | } 279 | 280 | func cancelAll(_ clearAll: Bool = true) { 281 | // Cancel any requests being performed 282 | for infoRequest in activeRequests { 283 | infoRequest.request?.cancel() 284 | infoRequest.request = nil 285 | } 286 | 287 | if clearAll { 288 | pendingRequests.removeAll() 289 | } 290 | else { 291 | // Put imcomplete requests back on the queue 292 | pendingRequests.insert(contentsOf: activeRequests, at: 0) 293 | } 294 | 295 | activeRequests.removeAll() 296 | } 297 | } 298 | 299 | -------------------------------------------------------------------------------- /LanternModel/PageMapper+CSV.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageMapper+CSV.swift 3 | // LanternModel 4 | // 5 | // Created by Patrick Smith on 7/11/19. 6 | // Copyright © 2019 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CSV 11 | 12 | extension ValidatedStringValue { 13 | var stringForCSV: String { 14 | switch self { 15 | case .validString(let stringValue): 16 | return stringValue 17 | case .validKeyValue(let key, let value): 18 | return "\(key): \(value)" 19 | case .notRequested: 20 | return "(not requested)" 21 | case .missing: 22 | return "(none)" 23 | case .empty: 24 | return "(empty)" 25 | case .multiple(let values): 26 | return values.map { $0.stringForCSV }.joined(separator: "\t") 27 | case .invalid: 28 | return "(invalid)" 29 | } 30 | } 31 | } 32 | 33 | 34 | public struct CrawledResultsCSVCreator { 35 | public var baseContentType: BaseContentType = .localHTMLPage 36 | 37 | public init() {} 38 | 39 | public func csvData(pageMapper: PageMapper) throws -> Data { 40 | let csv = try CSVWriter(stream: .toMemory()) 41 | 42 | let columnIdentifiers: [PagePresentedInfoIdentifier] = [.requestedURL, .pageTitle, .h1, .statusCode, .MIMEType, .pageByteCount, .pageByteCountBeforeBodyTag, .pageByteCountAfterBodyTag, .internalLinks, .externalLinks] 43 | try csv.write(row: ["path", "title", "h1", "status_code", "mime_type", "page_byte_count", "head_byte_count", "body_byte_count", "internal_links", "external_links"]) 44 | 45 | let urls = pageMapper.copyURLsWithBaseContentType(baseContentType) 46 | for url in urls { 47 | if let resourceInfo = pageMapper.pageInfoForRequestedURL(url) { 48 | try csv.write(row: columnIdentifiers.map { identifier in 49 | identifier.validatedStringValueInPageInfo(resourceInfo, pageMapper: pageMapper).stringForCSV 50 | }) 51 | } 52 | } 53 | 54 | csv.stream.close() 55 | 56 | return csv.stream.property(forKey: .dataWrittenToMemoryStreamKey) as! Data 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /LanternModel/PageMapper+Filtering.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageMapper+Problems.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 28/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public extension PageMapper { 13 | func numberOfRequestedURLsWithBaseContentType(_ type: BaseContentType) -> Int { 14 | switch type { 15 | case .localHTMLPage: 16 | return requestedLocalPageURLsUnique.count 17 | case .image: 18 | return requestedImageURLsUnique.count 19 | case .feed: 20 | return requestedFeedURLsUnique.count 21 | default: 22 | return 0 23 | } 24 | } 25 | 26 | func numberOfLoadedURLsWithBaseContentType(_ baseContentType: BaseContentType, responseType: PageResponseType? = nil) -> UInt { 27 | if let responseType = responseType { 28 | return baseContentTypeToResponseTypeToURLCount[baseContentType]?[responseType] ?? 0 29 | } 30 | // Else any response type: tally them up. 31 | else if let responseTypeToURLCount = baseContentTypeToResponseTypeToURLCount[baseContentType] { 32 | return responseTypeToURLCount.reduce(UInt(0), { (totalSoFar, dictIndex) -> UInt in 33 | let (responseType, URLCount) = dictIndex 34 | return totalSoFar + URLCount 35 | }) 36 | } 37 | else { 38 | return 0 39 | } 40 | } 41 | 42 | func copyURLsWithBaseContentType(_ type: BaseContentType) -> [URL] { 43 | switch type { 44 | case .localHTMLPage: 45 | return localPageURLsOrdered as [URL] 46 | case .image: 47 | return imageURLsOrdered as [URL] 48 | case .feed: 49 | return feedURLsOrdered as [URL] 50 | default: 51 | return [] 52 | } 53 | } 54 | 55 | func copyURLsWithBaseContentType(_ type: BaseContentType, withResponseType responseType: PageResponseType) -> [URL] { 56 | let URLs: [URL] = copyURLsWithBaseContentType(type) 57 | 58 | return URLs.filter { (URL) in 59 | if 60 | let pageInfo = self.loadedURLToPageInfo[URL] 61 | { 62 | let responseTypeToCheck = PageResponseType(statusCode: pageInfo.statusCode) 63 | return responseTypeToCheck == responseType 64 | } 65 | 66 | return false 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /LanternModel/PageMapper+InfoPresenting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageMapper+InfoPresenting.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 28/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Ono 11 | 12 | 13 | extension URL { 14 | var burnt_pathWithQuery: String { 15 | if let query = query { 16 | return "\(path)?\(query)" 17 | } 18 | else { 19 | return path 20 | } 21 | } 22 | } 23 | 24 | 25 | public enum PagePresentedInfoIdentifier: String { 26 | case requestedURL = "requestedURL" 27 | case statusCode = "statusCode" 28 | case MIMEType = "MIMEType" 29 | case pageTitle = "pageTitle" 30 | case h1 = "h1" 31 | case metaDescription = "metaDescription" 32 | case openGraphTags = "openGraphTags" 33 | case openGraphImage = "openGraphImage" 34 | case pageByteCount = "pageByteCount" 35 | case pageByteCountBeforeBodyTag = "pageBeforeBodyTagBytes" 36 | case pageByteCountAfterBodyTag = "pageAfterBodyTagBytes" 37 | case internalLinkCount = "internalLinkCount" 38 | case internalLinks = "internalLinks" 39 | case externalLinkCount = "externalLinkCount" 40 | case externalLinks = "externalLinks" 41 | 42 | public var longerFormInformation: PagePresentedInfoIdentifier? { 43 | switch self { 44 | case .internalLinkCount: 45 | return .internalLinks 46 | case .externalLinkCount: 47 | return .externalLinks 48 | default: 49 | return nil 50 | } 51 | } 52 | 53 | public func titleForBaseContentType(_ baseContentType: BaseContentType?) -> String { 54 | switch self { 55 | case .requestedURL: 56 | if let baseContentType = baseContentType { 57 | switch baseContentType { 58 | case .localHTMLPage: 59 | return "Path" 60 | case .image: 61 | return "Image URL" 62 | case .feed: 63 | return "Feed URL" 64 | default: 65 | break 66 | } 67 | } 68 | 69 | return "URL" 70 | case .statusCode: 71 | return "Status Code" 72 | case .MIMEType: 73 | return "MIME Type" 74 | case .pageTitle: 75 | return "Title" 76 | case .h1: 77 | return "H1" 78 | case .metaDescription: 79 | return "Meta Description" 80 | case .openGraphTags: 81 | return "Open Graph Tags" 82 | case .openGraphImage: 83 | return "Open Graph Image" 84 | case .pageByteCount: 85 | return "Total Bytes" 86 | case .pageByteCountBeforeBodyTag: 87 | return " Bytes" 88 | case .pageByteCountAfterBodyTag: 89 | return " Bytes" 90 | case .internalLinkCount, .internalLinks: 91 | return "Internal Links" 92 | case .externalLinkCount, .externalLinks: 93 | return "External Links" 94 | } 95 | } 96 | 97 | fileprivate func stringValueForMultipleElementsContent(_ elements: [ONOXMLElement]) -> String? { 98 | switch elements.count { 99 | case 1: 100 | let element = elements[0] 101 | var stringValue = element.stringValue() ?? "" 102 | // Conforms spaces and new lines into single spaces 103 | stringValue = stringValue.replacingOccurrences(of: "[\\s]+", with: " ", options: .regularExpression, range: nil) 104 | // Trim whitespace from ends 105 | stringValue = stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 106 | return stringValue 107 | case 0: 108 | return nil 109 | default: 110 | return "(multiple)" 111 | } 112 | } 113 | 114 | fileprivate func stringValueForMultipleElements(_ elements: [ONOXMLElement], attribute: String) -> String? { 115 | switch elements.count { 116 | case 1: 117 | let element = elements[0] 118 | if let stringValue = element[attribute] as? String { 119 | return stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 120 | } 121 | else { 122 | return nil 123 | } 124 | case 0: 125 | return nil 126 | default: 127 | return "(multiple)" 128 | } 129 | } 130 | 131 | fileprivate static var byteFormatter: ByteCountFormatter = { 132 | let byteFormatter = ByteCountFormatter() 133 | byteFormatter.countStyle = .binary 134 | byteFormatter.isAdaptive = false 135 | return byteFormatter 136 | }() 137 | 138 | public func validatedStringValueForPendingURL(_ requestedURL: URL) -> ValidatedStringValue { 139 | switch self { 140 | case .requestedURL: 141 | #if DEBUG && false 142 | return ValidatedStringValue(string: "\(requestedURL)") 143 | #endif 144 | 145 | return ValidatedStringValue(string: requestedURL.burnt_pathWithQuery) 146 | default: 147 | break 148 | } 149 | 150 | return .missing 151 | } 152 | 153 | public func validatedStringValueInPageInfo(_ pageInfo: PageInfo, pageMapper: PageMapper) -> ValidatedStringValue { 154 | switch self { 155 | case .requestedURL: 156 | let requestedURL = pageInfo.requestedURL 157 | let requestedPath = requestedURL.burnt_pathWithQuery 158 | if 159 | let finalURL = pageInfo.finalURL, 160 | requestedURL.absoluteString != finalURL.absoluteString 161 | { 162 | let finalURLPath = finalURL.burnt_pathWithQuery 163 | if pageMapper.redirectedDestinationURLToInfo[finalURL] != nil { 164 | return ValidatedStringValue(string: "\(requestedPath) (\(finalURLPath))") 165 | } 166 | 167 | let requestedScheme = requestedURL.scheme 168 | let finalScheme = finalURL.scheme 169 | if requestedScheme != finalScheme { 170 | return ValidatedStringValue(string: "\(requestedPath) (\(finalURLPath) to \(String(describing: finalScheme)))") 171 | } 172 | 173 | return ValidatedStringValue(string: "\(requestedPath) (\(finalURLPath))") 174 | } 175 | 176 | #if DEBUG && false 177 | return ValidatedStringValue(string: "\(requestedURL) \(pageInfo.finalURL)") 178 | #endif 179 | 180 | return ValidatedStringValue(string: requestedPath) 181 | case .statusCode: 182 | return ValidatedStringValue(string: String(pageInfo.statusCode)) 183 | case .MIMEType: 184 | if let MIMEType = pageInfo.MIMEType?.stringValue { 185 | return ValidatedStringValue( 186 | string: MIMEType 187 | ) 188 | } 189 | case .pageTitle: 190 | if let pageTitleElements = pageInfo.contentInfo?.pageTitleElements { 191 | return ValidatedStringValue.validateContentOfElements(pageTitleElements) 192 | } 193 | case .h1: 194 | if let h1Elements = pageInfo.contentInfo?.h1Elements { 195 | return ValidatedStringValue.validateContentOfElements(h1Elements) 196 | } 197 | case .metaDescription: 198 | if let metaDescriptionElements = pageInfo.contentInfo?.metaDescriptionElements { 199 | return ValidatedStringValue.validateAttribute("content", ofElements: metaDescriptionElements) 200 | } 201 | case .openGraphTags: 202 | if let openGraphElements = pageInfo.contentInfo?.openGraphElements { 203 | return ValidatedStringValue.validate(keyAttribute: "property", valueAttribute: "content", of: openGraphElements) 204 | } 205 | case .openGraphImage: 206 | guard let openGraphElements = pageInfo.contentInfo?.openGraphElements else { return .missing } 207 | guard let imageElement = openGraphElements.first(where: { $0["property"] as? String == "og:image" }) else { return .missing } 208 | return ValidatedStringValue.validate(keyAttribute: "property", valueAttribute: "content", of: imageElement) 209 | case .pageByteCount: 210 | if let byteCount = pageInfo.byteCount { 211 | return ValidatedStringValue( 212 | string: PagePresentedInfoIdentifier.byteFormatter.string(fromByteCount: Int64(byteCount)) 213 | ) 214 | } 215 | else { 216 | return .notRequested 217 | } 218 | case .pageByteCountBeforeBodyTag: 219 | if let byteCountBeforeBody = pageInfo.contentInfo?.preBodyByteCount { 220 | return ValidatedStringValue( 221 | string: PagePresentedInfoIdentifier.byteFormatter.string(fromByteCount: Int64(byteCountBeforeBody)) 222 | ) 223 | } 224 | case .pageByteCountAfterBodyTag: 225 | if let 226 | byteCount = pageInfo.byteCount, 227 | let byteCountBeforeBody = pageInfo.contentInfo?.preBodyByteCount 228 | { 229 | return ValidatedStringValue( 230 | string: PagePresentedInfoIdentifier.byteFormatter.string(fromByteCount: Int64(byteCount - byteCountBeforeBody)) 231 | ) 232 | } 233 | case .internalLinkCount: 234 | if let localPageURLs = pageInfo.contentInfo?.localPageURLs { 235 | return ValidatedStringValue( 236 | string: String(localPageURLs.count) 237 | ) 238 | } 239 | case .internalLinks: 240 | if let localPageURLs = pageInfo.contentInfo?.localPageURLs { 241 | return ValidatedStringValue.multiple( 242 | localPageURLs.map { URL in 243 | ValidatedStringValue( 244 | string: URL.absoluteString 245 | ) 246 | } 247 | ) 248 | } 249 | case .externalLinkCount: 250 | if let externalPageURLs = pageInfo.contentInfo?.externalPageURLs { 251 | return ValidatedStringValue( 252 | string: String(externalPageURLs.count) 253 | ) 254 | } 255 | case .externalLinks: 256 | if let externalPageURLs = pageInfo.contentInfo?.externalPageURLs { 257 | return ValidatedStringValue.multiple( 258 | externalPageURLs.map { URL in 259 | ValidatedStringValue( 260 | string: URL.absoluteString 261 | ) 262 | } 263 | ) 264 | } 265 | } 266 | 267 | return .missing 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /LanternModel/PageMapper+LinkFiltering.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageMapper+LinkFiltering.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 3/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Ono 11 | 12 | 13 | public enum PageLinkFilter { 14 | case isLinkedByURL(URL) 15 | case containsLinkToURL(URL) 16 | } 17 | 18 | 19 | extension PageMapper { 20 | public func copyHTMLPageURLsFilteredBy(_ linkFilter: PageLinkFilter) -> [URL] { 21 | let URLs = copyURLsWithBaseContentType(.localHTMLPage, withResponseType: .successful) 22 | 23 | switch linkFilter { 24 | case .isLinkedByURL(let linkedByURL): 25 | if let contentInfo = self.pageInfoForRequestedURL(linkedByURL)?.contentInfo { 26 | return URLs.filter { (URLToCheck) in 27 | return contentInfo.containsLocalPageURL(URLToCheck) 28 | } 29 | } 30 | else { 31 | return [] 32 | } 33 | case .containsLinkToURL(let childURL): 34 | return URLs.filter { (URLToCheck) in 35 | if let contentInfo = self.pageInfoForRequestedURL(URLToCheck)?.contentInfo { 36 | return contentInfo.containsLocalPageURL(childURL) 37 | } 38 | 39 | return false 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LanternModel/PageMapper+Validating.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageInfoValidationResult.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 28/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Ono 11 | 12 | 13 | public enum PageInfoValidationResult { 14 | case valid 15 | case notRequested 16 | case missing 17 | case empty 18 | case multiple 19 | case invalid 20 | } 21 | 22 | private let whitespaceCharacterSet = CharacterSet.whitespacesAndNewlines 23 | private let nonWhitespaceCharacterSet = whitespaceCharacterSet.inverted 24 | 25 | private func stringIsJustWhitespace(_ string: String) -> Bool { 26 | // Return range if non-whitespace characters are present, nil if no non-whitespace characters are present. 27 | return string.rangeOfCharacter(from: nonWhitespaceCharacterSet, options: [], range: nil) == nil 28 | } 29 | 30 | extension PageInfoValidationResult { 31 | init(validatedStringValue: ValidatedStringValue) { 32 | switch validatedStringValue{ 33 | case .validString, .validKeyValue: 34 | self = .valid 35 | case .notRequested: 36 | self = .notRequested 37 | case .missing: 38 | self = .missing 39 | case .empty: 40 | self = .empty 41 | case .multiple: 42 | self = .multiple 43 | case .invalid: 44 | self = .invalid 45 | } 46 | } 47 | 48 | static func validateContentsOfElements(_ elements: [ONOXMLElement]) -> PageInfoValidationResult { 49 | let validatedStringValue = ValidatedStringValue.validateContentOfElements(elements) 50 | return self.init(validatedStringValue: validatedStringValue) 51 | } 52 | 53 | static func validateMIMEType(_ MIMEType: String) -> PageInfoValidationResult { 54 | if stringIsJustWhitespace(MIMEType) { 55 | return .missing 56 | } 57 | 58 | return .valid 59 | } 60 | } 61 | 62 | 63 | public enum PageInfoValidationArea: Int { 64 | case mimeType = 1 65 | case Title = 2 66 | case h1 = 3 67 | case metaDescription = 4 68 | 69 | var title: String { 70 | switch self { 71 | case .mimeType: 72 | return "MIME Type" 73 | case .Title: 74 | return "Title" 75 | case .h1: 76 | return "H1" 77 | case .metaDescription: 78 | return "Meta Description" 79 | } 80 | } 81 | 82 | var isRequired: Bool { 83 | return true 84 | } 85 | 86 | static var allAreas = Set([.mimeType, .Title, .h1, .metaDescription]) 87 | } 88 | 89 | extension PageInfo { 90 | public func validateArea(_ validationArea: PageInfoValidationArea) -> PageInfoValidationResult { 91 | switch validationArea { 92 | case .mimeType: 93 | if let MIMEType = MIMEType { 94 | return PageInfoValidationResult.validateMIMEType(MIMEType.stringValue) 95 | } 96 | case .Title: 97 | if let pageTitleElements = contentInfo?.pageTitleElements { 98 | return PageInfoValidationResult.validateContentsOfElements(pageTitleElements) 99 | } 100 | case .h1: 101 | if let h1Elements = contentInfo?.h1Elements { 102 | return PageInfoValidationResult.validateContentsOfElements(h1Elements) 103 | } 104 | case .metaDescription: 105 | if let metaDescriptionElements = self.contentInfo?.metaDescriptionElements { 106 | switch metaDescriptionElements.count { 107 | case 1: 108 | let element = metaDescriptionElements[0] 109 | let validatedStringValue = ValidatedStringValue.validateAttribute("content", ofElement: element) 110 | return PageInfoValidationResult(validatedStringValue: validatedStringValue) 111 | case 0: 112 | return .missing 113 | default: 114 | return .multiple 115 | } 116 | } 117 | } 118 | 119 | return .missing 120 | } 121 | } 122 | 123 | 124 | public extension PageMapper { 125 | func copyHTMLPageURLsWhichCompletelyValidateForType(_ type: BaseContentType) -> [URL] { 126 | let validationAreas = PageInfoValidationArea.allAreas 127 | let URLs = copyURLsWithBaseContentType(type, withResponseType: .successful) 128 | 129 | return URLs.filter { URL in 130 | guard let pageInfo = self.loadedURLToPageInfo[URL] else { return false } 131 | 132 | let containsInvalidResult = validationAreas.contains { validationArea in 133 | return pageInfo.validateArea(validationArea) != .valid 134 | } 135 | 136 | return !containsInvalidResult 137 | } 138 | } 139 | 140 | func copyHTMLPageURLsForType(_ type: BaseContentType, failingToValidateInArea validationArea: PageInfoValidationArea) -> [URL] { 141 | let URLs = copyURLsWithBaseContentType(type, withResponseType: .successful) 142 | 143 | return URLs.filter { URL in 144 | guard let pageInfo = self.loadedURLToPageInfo[URL] else { return false } 145 | 146 | switch pageInfo.validateArea(validationArea) { 147 | case .valid: 148 | return false 149 | case .missing: 150 | return validationArea.isRequired // Only invalid if it is required 151 | default: 152 | return true 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /LanternModel/Site.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Site.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 30/03/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | private enum Error: Int { 13 | case nameIsEmpty = 10 14 | 15 | case homePageURLIsEmpty = 20 16 | case homePageURLIsInvalid 17 | 18 | static let domain = "LanternApp.SiteSettingsViewController.errorDomain" 19 | 20 | var errorCode: Int { 21 | return rawValue 22 | } 23 | 24 | var description: String { 25 | switch self { 26 | case .nameIsEmpty: 27 | return NSLocalizedString("Please enter a name for your site", comment: "Site NameIsEmpty error description") 28 | case .homePageURLIsEmpty: 29 | return NSLocalizedString("Please enter a URL for your site’s home page", comment: "Site HomePageURLIsEmpty error description") 30 | case .homePageURLIsInvalid: 31 | return NSLocalizedString("Please enter a valid URL for your site’s home page", comment: "Site HomePageURLIsInvalid error description") 32 | } 33 | } 34 | 35 | var cocoaError: NSError { 36 | let userInfo = [ 37 | NSLocalizedDescriptionKey: self.description 38 | ] 39 | return NSError(domain: Error.domain, code: errorCode, userInfo: userInfo) 40 | } 41 | } 42 | 43 | 44 | public struct SiteValues: Equatable { 45 | public let UUID: Foundation.UUID 46 | 47 | public var name: String 48 | public var homePageURL: URL 49 | 50 | public init(name: String, homePageURL: URL, UUID: Foundation.UUID = Foundation.UUID()) { 51 | self.name = name 52 | self.homePageURL = homePageURL 53 | self.UUID = UUID 54 | } 55 | } 56 | 57 | public func ==(lhs: SiteValues, rhs: SiteValues) -> Bool { 58 | return 59 | lhs.name == rhs.name && 60 | lhs.homePageURL == rhs.homePageURL 61 | } 62 | 63 | extension SiteValues { 64 | fileprivate init(fromStoredValues values: ValueStorable) { 65 | name = values["name"] as! String 66 | homePageURL = URL(string: values["homePageURL"] as! String)! 67 | UUID = Foundation.UUID(uuidString: values["UUID"] as! String)! 68 | } 69 | 70 | fileprivate func updateStoredValues(_ values: ValueStorable) -> ValueStorable { 71 | var values = values 72 | values["name"] = name 73 | values["homePageURL"] = homePageURL.absoluteString 74 | values["UUID"] = UUID.uuidString 75 | 76 | return values 77 | } 78 | } 79 | 80 | extension SiteValues { 81 | init(fromJSON json: [String: Any]) { 82 | let jsonValues = RecordJSON(dictionary: json) 83 | self.init(fromStoredValues: jsonValues) 84 | } 85 | 86 | func toJSON() -> [String: Any] { 87 | return [ 88 | "name": name, 89 | "homePageURL": homePageURL.absoluteString, 90 | "UUID": UUID.uuidString 91 | ] 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /LanternModel/SiteEssentialsInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SiteEssentialsInfo.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 7/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public enum SiteEssentialType { 13 | case siteMapXML 14 | case robotsTxt 15 | case valid404Page 16 | //case WWWRedirecting(hasWWW: Bool) 17 | //case HTTPS 18 | //case HTTPToHTTPSRedirection 19 | case favIconAtRoot 20 | //case TouchIcon 21 | 22 | 23 | public enum Need { 24 | case required 25 | case recommended 26 | case optional 27 | } 28 | 29 | 30 | public var need: Need { 31 | switch self { 32 | case .favIconAtRoot: //, .TouchIcon: 33 | return .recommended 34 | //case .HTTPS: 35 | // return .Optional 36 | default: 37 | return .required 38 | } 39 | } 40 | } 41 | 42 | 43 | public struct SiteEssentialInfo { 44 | public let type: SiteEssentialType 45 | 46 | public let wasFound: Bool? 47 | public let isValid: Bool? 48 | 49 | public let resourceInfos: [PageInfo] 50 | } 51 | 52 | 53 | open class SiteEssentialInfoRequest { 54 | public typealias CompletionHandler = (_ info: SiteEssentialInfo) -> () 55 | 56 | public let type: SiteEssentialType 57 | public let completionHandler: CompletionHandler 58 | 59 | var resourceRequests: [String: PageInfoRequest] 60 | var resourceInfos: [String: PageInfo] 61 | 62 | init(type: SiteEssentialType, baseURL: URL, completionHandler: @escaping CompletionHandler) { 63 | self.type = type 64 | self.completionHandler = completionHandler 65 | 66 | resourceRequests = [String: PageInfoRequest]() 67 | resourceInfos = [String: PageInfo]() 68 | 69 | func addResourceRequest(_ pathComponent: String, identifier: String? = nil) { 70 | let identifier = identifier ?? pathComponent 71 | 72 | func requestCompletionHandler(_ info: PageInfo) { 73 | 74 | } 75 | 76 | #if false 77 | var infoRequest = PageInfoRequest(URL: baseURL.URLByAppendingPathComponent(pathComponent)) { [weak self] (resourceInfo, infoRequest) in 78 | self?.didCompleteResourceInfoRequest(resourceInfo, identifier: identifier) 79 | } 80 | resourceRequests[identifier] = infoRequest 81 | #endif 82 | } 83 | 84 | switch type { 85 | case .siteMapXML: 86 | addResourceRequest("sitemap.xml") 87 | case .robotsTxt: 88 | addResourceRequest("robots.txt") 89 | case .valid404Page: 90 | // Use UUID to create a unique request every time. 91 | addResourceRequest(UUID().uuidString, identifier: "UUID") 92 | case .favIconAtRoot: 93 | addResourceRequest("favicon.ico") 94 | } 95 | } 96 | 97 | func didCompleteResourceInfoRequest(_ resourceInfo: PageInfo, identifier: String) { 98 | resourceInfos[identifier] = resourceInfo 99 | } 100 | 101 | open var currentInfo: SiteEssentialInfo { 102 | var finished = resourceRequests.count == resourceInfos.count 103 | return SiteEssentialInfo(type: type, wasFound: nil, isValid: nil, resourceInfos: []) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /LanternModel/SystemDirectory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemDirectory.swift 3 | // BurntFoundation 4 | // 5 | // Created by Patrick Smith on 26/07/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | public class SystemDirectory { 14 | public typealias ErrorReceiver = (NSError) -> () 15 | 16 | public let pathComponents: [String] 17 | public let domainMask: FileManager.SearchPathDomainMask 18 | public let directoryBase: FileManager.SearchPathDirectory 19 | public let errorReceiver: ErrorReceiver 20 | fileprivate let group: DispatchGroup 21 | fileprivate var createdDirectoryURL: URL? 22 | 23 | public init(pathComponents: [String], inUserDirectory directoryBase: FileManager.SearchPathDirectory, errorReceiver: @escaping ErrorReceiver, useBundleIdentifier: Bool = true) { 24 | var pathComponents = pathComponents 25 | if useBundleIdentifier { 26 | if let bundleIdentifier = Bundle.main.bundleIdentifier { 27 | pathComponents.insert(bundleIdentifier, at: 0) 28 | } 29 | } 30 | 31 | self.pathComponents = pathComponents 32 | self.domainMask = [.userDomainMask] 33 | self.directoryBase = directoryBase 34 | self.errorReceiver = errorReceiver 35 | 36 | group = DispatchGroup() 37 | 38 | createDirectory() 39 | } 40 | 41 | fileprivate func createDirectory() { 42 | let queue = DispatchQueue.global(qos: .default) 43 | queue.async(group: group) { 44 | let fm = FileManager.default 45 | 46 | do { 47 | let baseDirectoryURL = try fm.url(for: self.directoryBase, in: self.domainMask, appropriateFor: nil, create: true) 48 | 49 | // Convert path to its components, so we can add more components 50 | // and convert back into a URL. 51 | var pathComponents = baseDirectoryURL.pathComponents 52 | pathComponents.append(contentsOf: self.pathComponents) 53 | 54 | // Convert components back into a URL. 55 | guard let directoryURL = NSURL.fileURL(withPathComponents: pathComponents) 56 | else { return } 57 | 58 | try fm.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) 59 | 60 | self.createdDirectoryURL = directoryURL 61 | } 62 | catch let error as NSError { 63 | self.errorReceiver(error) 64 | } 65 | } 66 | } 67 | 68 | public func useOnQueue(_ queue: DispatchQueue, closure: @escaping (_ directoryURL: URL) -> ()) { 69 | group.notify(queue: queue) { 70 | if let createdDirectoryURL = self.createdDirectoryURL { 71 | closure(createdDirectoryURL) 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /LanternModel/UniqueURLArray.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UniqueURLArray.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 28/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | func conformURL(_ URL: Foundation.URL, requireHost: Bool = true) -> Foundation.URL? { 13 | if var urlComponents = URLComponents(url: URL, resolvingAgainstBaseURL: true) { 14 | if requireHost && urlComponents.host == nil { 15 | return nil 16 | } 17 | // Remove #fragments 18 | urlComponents.fragment = nil 19 | // Home page should always have trailing slash 20 | if urlComponents.path == "" { 21 | urlComponents.path = "/" 22 | } 23 | // Return adjusted URL 24 | return urlComponents.url 25 | } 26 | else { 27 | return nil 28 | } 29 | } 30 | 31 | 32 | class UniqueURLArray: Sequence { 33 | fileprivate var uniqueURLs = Set() 34 | var orderedURLs = [URL]() 35 | 36 | typealias Iterator = Array.Iterator 37 | func makeIterator() -> Iterator { 38 | return orderedURLs.makeIterator() 39 | } 40 | 41 | typealias Index = Array.Index 42 | subscript(position: Index) -> Iterator.Element { 43 | return orderedURLs[position] 44 | } 45 | 46 | var count: Int { 47 | return uniqueURLs.count 48 | } 49 | 50 | func contains(_ URL: Foundation.URL) -> Bool { 51 | if let URL = conformURL(URL) { 52 | return uniqueURLs.contains(URL) 53 | } 54 | 55 | return false 56 | } 57 | 58 | func insertReturningConformedURLIfNew(_ URL: Foundation.URL) -> Foundation.URL? { 59 | if let URL = conformURL(URL) { 60 | if !uniqueURLs.contains(URL) { 61 | uniqueURLs.insert(URL) 62 | orderedURLs.append(URL) 63 | return URL 64 | } 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func remove(_ URL: Foundation.URL) { 71 | if let URL = conformURL(URL) { 72 | if let setIndex = uniqueURLs.firstIndex(of: URL) { 73 | uniqueURLs.remove(at: setIndex) 74 | 75 | if let arrayIndex = orderedURLs.firstIndex(of: URL) { 76 | orderedURLs.remove(at: arrayIndex) 77 | } 78 | } 79 | } 80 | } 81 | 82 | func removeAll() { 83 | uniqueURLs.removeAll() 84 | orderedURLs.removeAll() 85 | } 86 | } 87 | 88 | extension UniqueURLArray: CustomStringConvertible { 89 | var description: String { 90 | return uniqueURLs.description 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /LanternModel/ValidatedStringValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ElementConvenience.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 28/04/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Ono 11 | 12 | 13 | 14 | public enum ValidatedStringValue { 15 | case validString(string: String) 16 | case validKeyValue(key: String, value: String) 17 | case missing 18 | case empty 19 | case notRequested 20 | case multiple([ValidatedStringValue]) 21 | case invalid 22 | 23 | init(string: String?) { 24 | if let string = string { 25 | if string == "" { 26 | self = .empty 27 | } 28 | else { 29 | self = .validString(string: string) 30 | } 31 | } 32 | else { 33 | self = .missing 34 | } 35 | } 36 | } 37 | 38 | private let whitespaceCharacterSet = CharacterSet.whitespacesAndNewlines 39 | 40 | extension ValidatedStringValue { 41 | init(string: String, trimmingSpace: Bool, combineSpaces: Bool = false) { 42 | var string = string 43 | if combineSpaces { 44 | string = string.replacingOccurrences(of: "[\\s]+", with: " ", options: .regularExpression, range: nil) 45 | } 46 | // Trim whitespace from ends 47 | string = string.trimmingCharacters(in: whitespaceCharacterSet) 48 | 49 | self.init(string: string) 50 | } 51 | } 52 | 53 | extension ValidatedStringValue { 54 | static func validateContentOfElement(_ element: ONOXMLElement) -> ValidatedStringValue { 55 | let stringValue = element.stringValue() 56 | 57 | return self.init(string: stringValue!, trimmingSpace: true, combineSpaces: true) 58 | } 59 | 60 | static func validateContentOfElements(_ elements: [ONOXMLElement]) -> ValidatedStringValue { 61 | switch elements.count { 62 | case 1: 63 | let element = elements[0] 64 | return validateContentOfElement(element) 65 | case 0: 66 | return .missing 67 | default: 68 | return .multiple(elements.map { element in 69 | return self.validateContentOfElement(element) 70 | }) 71 | } 72 | } 73 | 74 | static func validateAttribute(_ attribute: String, ofElement element: ONOXMLElement) -> ValidatedStringValue { 75 | if let stringValue = element[attribute] as? String { 76 | return self.init(string: stringValue, trimmingSpace: true) 77 | } 78 | else { 79 | return .missing 80 | } 81 | } 82 | 83 | static func validate(keyAttribute: String, valueAttribute: String, of element: ONOXMLElement) -> ValidatedStringValue { 84 | let key = element[keyAttribute] as? String ?? "" 85 | let value = element[valueAttribute] as? String ?? "" 86 | return .validKeyValue(key: key, value: value) 87 | } 88 | 89 | static func validateAttribute(_ attribute: String, ofElements elements: [ONOXMLElement]) -> ValidatedStringValue { 90 | switch elements.count { 91 | case 1: 92 | let element = elements[0] 93 | return validateAttribute(attribute, ofElement: element) 94 | case 0: 95 | return .missing 96 | default: 97 | let values: [ValidatedStringValue] = elements.map { element in 98 | return self.validateAttribute(attribute, ofElement: element) 99 | } 100 | return .multiple(values) 101 | } 102 | } 103 | 104 | static func validate(keyAttribute: String, valueAttribute: String, of elements: [ONOXMLElement]) -> ValidatedStringValue { 105 | switch elements.count { 106 | case 1: 107 | return validate(keyAttribute: keyAttribute, valueAttribute: valueAttribute, of: elements[0]) 108 | case 0: 109 | return .missing 110 | default: 111 | return .multiple(elements.map { 112 | validate(keyAttribute: keyAttribute, valueAttribute: valueAttribute, of: $0) 113 | }) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /LanternModel/ValueStorable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValueStorable.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 26/07/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | protocol ValueStorable { 13 | subscript(key: String) -> Any? { get set } 14 | } 15 | 16 | internal protocol ValueStorableUpdater { 17 | init?(fromStorable storable: ValueStorable) 18 | 19 | func updateStorable(_ storable: inout ValueStorable) 20 | } 21 | 22 | 23 | 24 | struct RecordJSON: ValueStorable { 25 | typealias Dictionary = [String: Any] 26 | var dictionary: Dictionary 27 | 28 | subscript(key: String) -> Any? { 29 | get { 30 | return dictionary[key] 31 | } 32 | set { 33 | dictionary[key] = newValue 34 | } 35 | } 36 | } 37 | 38 | extension RecordJSON { 39 | init() { 40 | self.init(dictionary: Dictionary()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LanternModel/ValueValidation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValueValidation.swift 3 | // Hoverlytics 4 | // 5 | // Created by Patrick Smith on 31/03/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | private let whitespaceCharacterSet = CharacterSet.whitespacesAndNewlines 13 | 14 | public enum ValidationError : Error, LocalizedError, CustomNSError { 15 | case stringIsEmpty(string: String, identifier: String) 16 | 17 | case urlStringIsInvalid(string: String, identifier: String) 18 | 19 | public static let errorDomain = "LanternModel.ValueValidation.errorDomain" 20 | 21 | fileprivate enum ErrorCode : Int { 22 | case stringIsEmpty = 10 23 | 24 | case urlStringIsInvalid = 20 25 | } 26 | 27 | public var errorCode: Int { 28 | switch self { 29 | case .stringIsEmpty: 30 | return ErrorCode.stringIsEmpty.rawValue 31 | case .urlStringIsInvalid: 32 | return ErrorCode.urlStringIsInvalid.rawValue 33 | } 34 | } 35 | 36 | public var errorDescription: String? { 37 | switch self { 38 | case .stringIsEmpty(_, let identifier): 39 | return "Please enter something for \"\(identifier)\"" 40 | case .urlStringIsInvalid(_, let identifier): 41 | return "Please enter a valid URL for \"\(identifier)\"" 42 | } 43 | } 44 | 45 | public static func validateString(_ string: String, identifier: String) throws -> String { 46 | let string = string.trimmingCharacters(in: whitespaceCharacterSet) 47 | if string.isEmpty { 48 | throw self.stringIsEmpty(string: string, identifier: identifier) 49 | } 50 | 51 | return string 52 | } 53 | 54 | public static func validate(urlString: String, identifier: String) throws -> URL { 55 | do { 56 | let urlString = try validateString(urlString, identifier: identifier) 57 | 58 | guard let url = detectWebURL(fromString: urlString) else { 59 | throw self.urlStringIsInvalid(string: urlString, identifier: identifier) 60 | } 61 | 62 | return url 63 | } 64 | } 65 | } 66 | 67 | 68 | public func detectWebURL(fromString URLString: String) -> URL? { 69 | let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) 70 | 71 | if let result = dataDetector?.firstMatch(in: URLString, options: NSRegularExpression.MatchingOptions(), range: NSMakeRange(0, (URLString as NSString).length)) { 72 | return result.url 73 | } 74 | else { 75 | return nil 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /LanternModelTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.burntcaramel.Lantern.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LanternModelTests/LanternModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LanternModelTests.swift 3 | // LanternModelTests 4 | // 5 | // Created by Patrick Smith on 30/03/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import XCTest 11 | import LanternModel 12 | 13 | class LanternModelTests: XCTestCase { 14 | 15 | override func setUp() { 16 | super.setUp() 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testDetectWebURL() { 26 | XCTAssertEqual(detectWebURL(fromString: "https://www.burntcaramel.com/"), URL(string: "https://www.burntcaramel.com/")) 27 | 28 | XCTAssertEqual(detectWebURL(fromString: "https://www.burntcaramel.com"), URL(string: "https://www.burntcaramel.com")) 29 | 30 | XCTAssertEqual(detectWebURL(fromString: "www.burntcaramel.com"), URL(string: "http://www.burntcaramel.com")) 31 | 32 | XCTAssertEqual(detectWebURL(fromString: "burntcaramel.com"), URL(string: "http://burntcaramel.com")) 33 | } 34 | 35 | func testPerformanceExample() { 36 | // This is an example of a performance test case. 37 | self.measure() { 38 | // Put the code you want to measure the time of here. 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /LanternTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LanternTests/LanternTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LanternTests.swift 3 | // LanternTests 4 | // 5 | // Created by Patrick Smith on 2/05/2015. 6 | // Copyright (c) 2015 Burnt Caramel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import XCTest 11 | 12 | class LanternTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: update 2 | update: 3 | carthage update --platform OSX 4 | 5 | .PHONY: test 6 | test: 7 | xcodebuild test -scheme "Lantern Model" 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lantern 2 | 3 | Crawl and audit websites. Written in Swift 5. 4 | 5 | ## Links 6 | 7 | [Download on the Mac App Store](https://itunes.apple.com/us/app/lantern-website-crawler-for/id991526452?ls=1&mt=12) 8 | 9 | [Home Website](https://icing.space/tools/lantern/) 10 | 11 | [Back on Open Collective](https://opencollective.com/lantern) 12 | 13 | ## Libraries 14 | 15 | Uses: 16 | - [Grain data flow for Swift](https://github.com/BurntCaramel/Grain) 17 | - [BurntCocoaUI](https://github.com/BurntCaramel/BurntCocoaUI) 18 | - [BurntFoundation](https://github.com/BurntCaramel/BurntFoundation) 19 | - [WebKit](https://www.webkit.org) 20 | - [Alamofire](https://github.com/Alamofire/Alamofire) 21 | - [Ono](https://github.com/mattt/Ono) 22 | --------------------------------------------------------------------------------