├── .gitignore ├── .gitmodules ├── Cartfile ├── Cartfile.resolved ├── CloudyTabs.xcodeproj └── project.pbxproj ├── CloudyTabs ├── CloudyTabs-Info.plist ├── CloudyTabs-Prefix.pch ├── Icon.pxm ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon_1024.png │ │ ├── Icon_128.png │ │ ├── Icon_16.png │ │ ├── Icon_256-1.png │ │ ├── Icon_256.png │ │ ├── Icon_32-1.png │ │ ├── Icon_32.png │ │ ├── Icon_512-1.png │ │ ├── Icon_512.png │ │ └── Icon_64.png │ ├── Contents.json │ ├── Globe.imageset │ │ ├── Contents.json │ │ ├── globe.png │ │ ├── globe@2x.png │ │ └── globe@3x.png │ └── StatusIcon.imageset │ │ ├── Contents.json │ │ └── StatusIcon.pdf ├── JPAppDelegate.h ├── JPAppDelegate.m ├── JPCloudTabsDBReader.h ├── JPCloudTabsDBReader.m ├── JPLaunchAtLoginManager.h ├── JPLaunchAtLoginManager.m ├── JPLoadingMenuItem.h ├── JPLoadingMenuItem.m ├── JPReadingListReader.h ├── JPReadingListReader.m ├── JPTabCSVExporter.h ├── JPTabCSVExporter.m ├── JPUserDefaultsController.h ├── JPUserDefaultsController.m ├── NSData+ZlibInflate.h ├── NSData+ZlibInflate.m ├── NSMenuItem+ItemCreation.h ├── NSMenuItem+ItemCreation.m ├── NSURL+Extensions.h ├── NSURL+Extensions.m ├── ca.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── de.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── dsa_pub.pem ├── en.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── es.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── fr.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── it.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── main.m ├── nl.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── pt_BR.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── ru.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── zh_CN.lproj │ ├── InfoPlist.strings │ └── Localizable.strings └── zh_TW.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── CloudyTabsHelper ├── AppDelegate.h ├── AppDelegate.m ├── CloudyTabsHelper.entitlements ├── Info.plist ├── NSBundle+ApplicationPath.h ├── NSBundle+ApplicationPath.m └── main.m ├── CloudyTabsTests ├── CloudyTabsTests.m ├── Info.plist └── NSURL+DecodeTests.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Carthage/ 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "CloudyTabs/DSFavIconManager"] 2 | path = CloudyTabs/DSFavIconManager 3 | url = https://github.com/josh-/DSFavIconManager 4 | [submodule "CloudyTabs/VDKQueue"] 5 | path = CloudyTabs/VDKQueue 6 | url = https://github.com/beatjunky99/VDKQueue.git 7 | [submodule "CloudyTabs/CHCSVParser"] 8 | path = CloudyTabs/CHCSVParser 9 | url = https://github.com/davedelong/CHCSVParser.git 10 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "sparkle-project/Sparkle" 2 | github "ccgus/fmdb" -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "ccgus/fmdb" "2.7.7" 2 | github "sparkle-project/Sparkle" "v1.23.0" 3 | -------------------------------------------------------------------------------- /CloudyTabs.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0308ABDB18D676ED00AB4C54 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0308ABD918D676ED00AB4C54 /* Localizable.strings */; }; 11 | 0308B3242504BFC80090D507 /* NSMenuItem+ItemCreation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0308B3232504BFC80090D507 /* NSMenuItem+ItemCreation.m */; }; 12 | 0308B32F250D95590090D507 /* FMDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0308B32C250D950B0090D507 /* FMDB.framework */; }; 13 | 0308B330250D95590090D507 /* FMDB.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0308B32C250D950B0090D507 /* FMDB.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 0308B331250D955C0090D507 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0308B32B250D950B0090D507 /* Sparkle.framework */; }; 15 | 0308B332250D955C0090D507 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0308B32B250D950B0090D507 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 16 | 0308B336250DDFD10090D507 /* NSData+ZlibInflate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0308B335250DDFD10090D507 /* NSData+ZlibInflate.m */; }; 17 | 0308B338250DE0DC0090D507 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0308B337250DE0D30090D507 /* libz.tbd */; }; 18 | 0308B348250E49240090D507 /* CHCSVParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 0308B346250E49240090D507 /* CHCSVParser.m */; }; 19 | 0308B34B250E49470090D507 /* JPTabCSVExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 0308B34A250E49470090D507 /* JPTabCSVExporter.m */; }; 20 | 030D99822172BBAB00A9DE75 /* JPLoadingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 030D99802172BBAA00A9DE75 /* JPLoadingMenuItem.m */; }; 21 | 030D99852172BBFD00A9DE75 /* JPReadingListReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 030D99832172BBFD00A9DE75 /* JPReadingListReader.m */; }; 22 | 03120E52202FE2F300BE3891 /* JPUserDefaultsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 03120E51202FE2F300BE3891 /* JPUserDefaultsController.m */; }; 23 | 03225D4C18C6A40D000778DF /* JPLaunchAtLoginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0375994118C5F967000E8C33 /* JPLaunchAtLoginManager.m */; }; 24 | 03225D5418C6AA5A000778DF /* DSFavIconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 03225D4E18C6AA5A000778DF /* DSFavIconCache.m */; }; 25 | 03225D5518C6AA5A000778DF /* DSFavIconManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 03225D5018C6AA5A000778DF /* DSFavIconManager.m */; }; 26 | 03225D5618C6AA5A000778DF /* DSFavIconOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 03225D5218C6AA5A000778DF /* DSFavIconOperation.m */; }; 27 | 03225D5E18C6B18A000778DF /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = 03225D5D18C6B18A000778DF /* dsa_pub.pem */; }; 28 | 0350635A24FB233300E71269 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0350635924FB233300E71269 /* AppDelegate.m */; }; 29 | 0350636224FB233500E71269 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0350636124FB233500E71269 /* main.m */; }; 30 | 0350636824FB23BB00E71269 /* CloudyTabsHelper.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0350635624FB233200E71269 /* CloudyTabsHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 31 | 0350636B24FB245700E71269 /* NSBundle+ApplicationPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 0350636A24FB245700E71269 /* NSBundle+ApplicationPath.m */; }; 32 | 0350636D24FB29BB00E71269 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0350636C24FB29BB00E71269 /* ServiceManagement.framework */; }; 33 | 0350637124FB398600E71269 /* JPCloudTabsDBReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 0350636F24FB360900E71269 /* JPCloudTabsDBReader.m */; }; 34 | 03648D7D1FA4434B004D3078 /* NSURL+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 03648D7C1FA4434B004D3078 /* NSURL+Extensions.m */; }; 35 | 03648D851FA445FA004D3078 /* CloudyTabsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03648D841FA445FA004D3078 /* CloudyTabsTests.m */; }; 36 | 03648D8D1FA44629004D3078 /* NSURL+DecodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03648D8C1FA44629004D3078 /* NSURL+DecodeTests.m */; }; 37 | 0375994218C5F967000E8C33 /* JPAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0375993F18C5F967000E8C33 /* JPAppDelegate.m */; }; 38 | 03977FD318C3F98A00DC499C /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03977FD218C3F98A00DC499C /* Cocoa.framework */; }; 39 | 03977FDD18C3F98A00DC499C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 03977FDB18C3F98A00DC499C /* InfoPlist.strings */; }; 40 | 03977FDF18C3F98A00DC499C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 03977FDE18C3F98A00DC499C /* main.m */; }; 41 | 03977FEB18C3F98A00DC499C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03977FEA18C3F98A00DC499C /* Images.xcassets */; }; 42 | 0397802318C590F700DC499C /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0397802218C590F700DC499C /* CoreServices.framework */; }; 43 | 039C6EAC1F935D2E00C3B3E1 /* VDKQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 039C6EAB1F935CE900C3B3E1 /* VDKQueue.m */; }; 44 | /* End PBXBuildFile section */ 45 | 46 | /* Begin PBXContainerItemProxy section */ 47 | 03648D871FA445FA004D3078 /* PBXContainerItemProxy */ = { 48 | isa = PBXContainerItemProxy; 49 | containerPortal = 03977FC718C3F98A00DC499C /* Project object */; 50 | proxyType = 1; 51 | remoteGlobalIDString = 03977FCE18C3F98A00DC499C; 52 | remoteInfo = CloudyTabs; 53 | }; 54 | /* End PBXContainerItemProxy section */ 55 | 56 | /* Begin PBXCopyFilesBuildPhase section */ 57 | 03225D4A18C5FED1000778DF /* CopyFiles */ = { 58 | isa = PBXCopyFilesBuildPhase; 59 | buildActionMask = 2147483647; 60 | dstPath = ""; 61 | dstSubfolderSpec = 10; 62 | files = ( 63 | 0308B330250D95590090D507 /* FMDB.framework in CopyFiles */, 64 | 0308B332250D955C0090D507 /* Sparkle.framework in CopyFiles */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | 0350636724FB239D00E71269 /* CopyFiles */ = { 69 | isa = PBXCopyFilesBuildPhase; 70 | buildActionMask = 2147483647; 71 | dstPath = Contents/Library/LoginItems; 72 | dstSubfolderSpec = 1; 73 | files = ( 74 | 0350636824FB23BB00E71269 /* CloudyTabsHelper.app in CopyFiles */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXCopyFilesBuildPhase section */ 79 | 80 | /* Begin PBXFileReference section */ 81 | 0308ABDA18D676ED00AB4C54 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 82 | 0308ABDC18D67A1300AB4C54 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 83 | 0308B3222504BFC80090D507 /* NSMenuItem+ItemCreation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSMenuItem+ItemCreation.h"; sourceTree = ""; }; 84 | 0308B3232504BFC80090D507 /* NSMenuItem+ItemCreation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSMenuItem+ItemCreation.m"; sourceTree = ""; }; 85 | 0308B32B250D950B0090D507 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Carthage/Build/Mac/Sparkle.framework; sourceTree = ""; }; 86 | 0308B32C250D950B0090D507 /* FMDB.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FMDB.framework; path = Carthage/Build/Mac/FMDB.framework; sourceTree = ""; }; 87 | 0308B334250DDFD10090D507 /* NSData+ZlibInflate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+ZlibInflate.h"; sourceTree = ""; }; 88 | 0308B335250DDFD10090D507 /* NSData+ZlibInflate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+ZlibInflate.m"; sourceTree = ""; }; 89 | 0308B337250DE0D30090D507 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 90 | 0308B345250E49240090D507 /* CHCSVParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHCSVParser.h; sourceTree = ""; }; 91 | 0308B346250E49240090D507 /* CHCSVParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHCSVParser.m; sourceTree = ""; }; 92 | 0308B349250E49470090D507 /* JPTabCSVExporter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JPTabCSVExporter.h; sourceTree = ""; }; 93 | 0308B34A250E49470090D507 /* JPTabCSVExporter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JPTabCSVExporter.m; sourceTree = ""; }; 94 | 030D99802172BBAA00A9DE75 /* JPLoadingMenuItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JPLoadingMenuItem.m; sourceTree = ""; }; 95 | 030D99812172BBAA00A9DE75 /* JPLoadingMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JPLoadingMenuItem.h; sourceTree = ""; }; 96 | 030D99832172BBFD00A9DE75 /* JPReadingListReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JPReadingListReader.m; sourceTree = ""; }; 97 | 030D99842172BBFD00A9DE75 /* JPReadingListReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JPReadingListReader.h; sourceTree = ""; }; 98 | 030F11BB18E3958C00B2DFB0 /* pt_BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt_BR; path = pt_BR.lproj/Localizable.strings; sourceTree = ""; }; 99 | 030F11BC18E3959100B2DFB0 /* pt_BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt_BR; path = pt_BR.lproj/InfoPlist.strings; sourceTree = ""; }; 100 | 03120E50202FE2F300BE3891 /* JPUserDefaultsController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JPUserDefaultsController.h; sourceTree = ""; }; 101 | 03120E51202FE2F300BE3891 /* JPUserDefaultsController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JPUserDefaultsController.m; sourceTree = ""; }; 102 | 0319CFB118D67AF70007A9FF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 103 | 0319CFB218D67B080007A9FF /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; 104 | 0319CFB318D67B110007A9FF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 105 | 03225D4D18C6AA5A000778DF /* DSFavIconCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DSFavIconCache.h; path = DSFavIconManager/Classes/DSFavIconCache.h; sourceTree = ""; }; 106 | 03225D4E18C6AA5A000778DF /* DSFavIconCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DSFavIconCache.m; path = DSFavIconManager/Classes/DSFavIconCache.m; sourceTree = ""; }; 107 | 03225D4F18C6AA5A000778DF /* DSFavIconManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DSFavIconManager.h; path = DSFavIconManager/Classes/DSFavIconManager.h; sourceTree = ""; }; 108 | 03225D5018C6AA5A000778DF /* DSFavIconManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DSFavIconManager.m; path = DSFavIconManager/Classes/DSFavIconManager.m; sourceTree = ""; }; 109 | 03225D5118C6AA5A000778DF /* DSFavIconOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DSFavIconOperation.h; path = DSFavIconManager/Classes/DSFavIconOperation.h; sourceTree = ""; }; 110 | 03225D5218C6AA5A000778DF /* DSFavIconOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DSFavIconOperation.m; path = DSFavIconManager/Classes/DSFavIconOperation.m; sourceTree = ""; }; 111 | 03225D5318C6AA5A000778DF /* UINSImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UINSImage.h; path = DSFavIconManager/Classes/UINSImage.h; sourceTree = ""; }; 112 | 03225D5D18C6B18A000778DF /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = ""; }; 113 | 0350635624FB233200E71269 /* CloudyTabsHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CloudyTabsHelper.app; sourceTree = BUILT_PRODUCTS_DIR; }; 114 | 0350635824FB233200E71269 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 115 | 0350635924FB233300E71269 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 116 | 0350636024FB233500E71269 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 117 | 0350636124FB233500E71269 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 118 | 0350636324FB233500E71269 /* CloudyTabsHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CloudyTabsHelper.entitlements; sourceTree = ""; }; 119 | 0350636924FB245700E71269 /* NSBundle+ApplicationPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSBundle+ApplicationPath.h"; sourceTree = ""; }; 120 | 0350636A24FB245700E71269 /* NSBundle+ApplicationPath.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+ApplicationPath.m"; sourceTree = ""; }; 121 | 0350636C24FB29BB00E71269 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; 122 | 0350636E24FB360100E71269 /* JPCloudTabsDBReader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JPCloudTabsDBReader.h; sourceTree = ""; }; 123 | 0350636F24FB360900E71269 /* JPCloudTabsDBReader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JPCloudTabsDBReader.m; sourceTree = ""; }; 124 | 03648D7B1FA4434B004D3078 /* NSURL+Extensions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSURL+Extensions.h"; sourceTree = ""; }; 125 | 03648D7C1FA4434B004D3078 /* NSURL+Extensions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+Extensions.m"; sourceTree = ""; }; 126 | 03648D821FA445FA004D3078 /* CloudyTabsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CloudyTabsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 127 | 03648D841FA445FA004D3078 /* CloudyTabsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CloudyTabsTests.m; sourceTree = ""; }; 128 | 03648D861FA445FA004D3078 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 129 | 03648D8C1FA44629004D3078 /* NSURL+DecodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+DecodeTests.m"; sourceTree = ""; }; 130 | 0375993E18C5F967000E8C33 /* JPAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JPAppDelegate.h; sourceTree = ""; }; 131 | 0375993F18C5F967000E8C33 /* JPAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JPAppDelegate.m; sourceTree = ""; }; 132 | 0375994018C5F967000E8C33 /* JPLaunchAtLoginManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JPLaunchAtLoginManager.h; sourceTree = ""; }; 133 | 0375994118C5F967000E8C33 /* JPLaunchAtLoginManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JPLaunchAtLoginManager.m; sourceTree = ""; }; 134 | 03822D4418DA61D20065F586 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 135 | 03822D4518DA61D70065F586 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; 136 | 03822D4618DA62010065F586 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 137 | 03822D4718DA62050065F586 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 138 | 03822D4818DA62400065F586 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; 139 | 03822D4918DA62440065F586 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/InfoPlist.strings; sourceTree = ""; }; 140 | 03822D4A18DA62880065F586 /* zh_CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_CN; path = zh_CN.lproj/Localizable.strings; sourceTree = ""; }; 141 | 03822D4B18DA62950065F586 /* zh_CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_CN; path = zh_CN.lproj/InfoPlist.strings; sourceTree = ""; }; 142 | 03822D4C18DA62B90065F586 /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_TW; path = zh_TW.lproj/Localizable.strings; sourceTree = ""; }; 143 | 03822D4D18DA62BD0065F586 /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_TW; path = zh_TW.lproj/InfoPlist.strings; sourceTree = ""; }; 144 | 03822D4E18DA873E0065F586 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 145 | 03822D4F18DA87420065F586 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; 146 | 03977FCF18C3F98A00DC499C /* CloudyTabs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CloudyTabs.app; sourceTree = BUILT_PRODUCTS_DIR; }; 147 | 03977FD218C3F98A00DC499C /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 148 | 03977FD518C3F98A00DC499C /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 149 | 03977FD618C3F98A00DC499C /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 150 | 03977FD718C3F98A00DC499C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 151 | 03977FDA18C3F98A00DC499C /* CloudyTabs-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CloudyTabs-Info.plist"; sourceTree = ""; }; 152 | 03977FDC18C3F98A00DC499C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 153 | 03977FDE18C3F98A00DC499C /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 154 | 03977FE018C3F98A00DC499C /* CloudyTabs-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CloudyTabs-Prefix.pch"; sourceTree = ""; }; 155 | 03977FEA18C3F98A00DC499C /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 156 | 0397802218C590F700DC499C /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 157 | 039C6E9D1F935BF900C3B3E1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 158 | 039C6E9E1F935BF900C3B3E1 /* VDKQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VDKQueue.h; sourceTree = ""; }; 159 | 039C6E9F1F935BF900C3B3E1 /* VDKQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VDKQueue.m; sourceTree = ""; }; 160 | 039C6EA21F935BFC00C3B3E1 /* VDKQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VDKQueue.h; sourceTree = ""; }; 161 | 039C6EA31F935BFC00C3B3E1 /* VDKQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VDKQueue.m; sourceTree = ""; }; 162 | 039C6EAA1F935CE900C3B3E1 /* VDKQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VDKQueue.h; sourceTree = ""; }; 163 | 039C6EAB1F935CE900C3B3E1 /* VDKQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VDKQueue.m; sourceTree = ""; }; 164 | 7735FBA120BCBA1700F30E6F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 165 | 7735FBA220BCBA1700F30E6F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 166 | /* End PBXFileReference section */ 167 | 168 | /* Begin PBXFrameworksBuildPhase section */ 169 | 0350635324FB233200E71269 /* Frameworks */ = { 170 | isa = PBXFrameworksBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | 03648D7F1FA445FA004D3078 /* Frameworks */ = { 177 | isa = PBXFrameworksBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | 03977FCC18C3F98A00DC499C /* Frameworks */ = { 184 | isa = PBXFrameworksBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | 0397802318C590F700DC499C /* CoreServices.framework in Frameworks */, 188 | 0308B338250DE0DC0090D507 /* libz.tbd in Frameworks */, 189 | 03977FD318C3F98A00DC499C /* Cocoa.framework in Frameworks */, 190 | 0308B331250D955C0090D507 /* Sparkle.framework in Frameworks */, 191 | 0350636D24FB29BB00E71269 /* ServiceManagement.framework in Frameworks */, 192 | 0308B32F250D95590090D507 /* FMDB.framework in Frameworks */, 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | /* End PBXFrameworksBuildPhase section */ 197 | 198 | /* Begin PBXGroup section */ 199 | 0308B343250E49240090D507 /* CHCSVParser */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | 0308B345250E49240090D507 /* CHCSVParser.h */, 203 | 0308B346250E49240090D507 /* CHCSVParser.m */, 204 | ); 205 | name = CHCSVParser; 206 | path = CHCSVParser/CHCSVParser/CHCSVParser; 207 | sourceTree = ""; 208 | }; 209 | 03225D5718C6AA7A000778DF /* DSFavIconManager */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 039C6E9C1F935BF900C3B3E1 /* VDKQueue */, 213 | 03225D4D18C6AA5A000778DF /* DSFavIconCache.h */, 214 | 03225D4E18C6AA5A000778DF /* DSFavIconCache.m */, 215 | 03225D4F18C6AA5A000778DF /* DSFavIconManager.h */, 216 | 03225D5018C6AA5A000778DF /* DSFavIconManager.m */, 217 | 03225D5118C6AA5A000778DF /* DSFavIconOperation.h */, 218 | 03225D5218C6AA5A000778DF /* DSFavIconOperation.m */, 219 | 03225D5318C6AA5A000778DF /* UINSImage.h */, 220 | ); 221 | name = DSFavIconManager; 222 | sourceTree = ""; 223 | }; 224 | 03225D5C18C6AB2A000778DF /* Classes */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | 0308B343250E49240090D507 /* CHCSVParser */, 228 | 039C6EA01F935BFC00C3B3E1 /* VDKQueue */, 229 | 0375993E18C5F967000E8C33 /* JPAppDelegate.h */, 230 | 0375993F18C5F967000E8C33 /* JPAppDelegate.m */, 231 | 0375994018C5F967000E8C33 /* JPLaunchAtLoginManager.h */, 232 | 0375994118C5F967000E8C33 /* JPLaunchAtLoginManager.m */, 233 | 030D99842172BBFD00A9DE75 /* JPReadingListReader.h */, 234 | 030D99832172BBFD00A9DE75 /* JPReadingListReader.m */, 235 | 0350636E24FB360100E71269 /* JPCloudTabsDBReader.h */, 236 | 0350636F24FB360900E71269 /* JPCloudTabsDBReader.m */, 237 | 030D99812172BBAA00A9DE75 /* JPLoadingMenuItem.h */, 238 | 030D99802172BBAA00A9DE75 /* JPLoadingMenuItem.m */, 239 | 03120E50202FE2F300BE3891 /* JPUserDefaultsController.h */, 240 | 03120E51202FE2F300BE3891 /* JPUserDefaultsController.m */, 241 | 0308B349250E49470090D507 /* JPTabCSVExporter.h */, 242 | 0308B34A250E49470090D507 /* JPTabCSVExporter.m */, 243 | 03648D7B1FA4434B004D3078 /* NSURL+Extensions.h */, 244 | 03648D7C1FA4434B004D3078 /* NSURL+Extensions.m */, 245 | 0308B3222504BFC80090D507 /* NSMenuItem+ItemCreation.h */, 246 | 0308B3232504BFC80090D507 /* NSMenuItem+ItemCreation.m */, 247 | 0308B334250DDFD10090D507 /* NSData+ZlibInflate.h */, 248 | 0308B335250DDFD10090D507 /* NSData+ZlibInflate.m */, 249 | ); 250 | name = Classes; 251 | sourceTree = ""; 252 | }; 253 | 0350635724FB233200E71269 /* CloudyTabsHelper */ = { 254 | isa = PBXGroup; 255 | children = ( 256 | 0350635824FB233200E71269 /* AppDelegate.h */, 257 | 0350635924FB233300E71269 /* AppDelegate.m */, 258 | 0350636924FB245700E71269 /* NSBundle+ApplicationPath.h */, 259 | 0350636A24FB245700E71269 /* NSBundle+ApplicationPath.m */, 260 | 0350636024FB233500E71269 /* Info.plist */, 261 | 0350636124FB233500E71269 /* main.m */, 262 | 0350636324FB233500E71269 /* CloudyTabsHelper.entitlements */, 263 | ); 264 | path = CloudyTabsHelper; 265 | sourceTree = ""; 266 | }; 267 | 03648D831FA445FA004D3078 /* CloudyTabsTests */ = { 268 | isa = PBXGroup; 269 | children = ( 270 | 03648D8C1FA44629004D3078 /* NSURL+DecodeTests.m */, 271 | 03648D841FA445FA004D3078 /* CloudyTabsTests.m */, 272 | 03648D861FA445FA004D3078 /* Info.plist */, 273 | ); 274 | path = CloudyTabsTests; 275 | sourceTree = ""; 276 | }; 277 | 03977FC618C3F98A00DC499C = { 278 | isa = PBXGroup; 279 | children = ( 280 | 03977FD818C3F98A00DC499C /* CloudyTabs */, 281 | 03648D831FA445FA004D3078 /* CloudyTabsTests */, 282 | 0350635724FB233200E71269 /* CloudyTabsHelper */, 283 | 03977FD118C3F98A00DC499C /* Frameworks */, 284 | 03977FD018C3F98A00DC499C /* Products */, 285 | ); 286 | sourceTree = ""; 287 | }; 288 | 03977FD018C3F98A00DC499C /* Products */ = { 289 | isa = PBXGroup; 290 | children = ( 291 | 03977FCF18C3F98A00DC499C /* CloudyTabs.app */, 292 | 03648D821FA445FA004D3078 /* CloudyTabsTests.xctest */, 293 | 0350635624FB233200E71269 /* CloudyTabsHelper.app */, 294 | ); 295 | name = Products; 296 | sourceTree = ""; 297 | }; 298 | 03977FD118C3F98A00DC499C /* Frameworks */ = { 299 | isa = PBXGroup; 300 | children = ( 301 | 0308B337250DE0D30090D507 /* libz.tbd */, 302 | 0308B32C250D950B0090D507 /* FMDB.framework */, 303 | 0308B32B250D950B0090D507 /* Sparkle.framework */, 304 | 0350636C24FB29BB00E71269 /* ServiceManagement.framework */, 305 | 03977FD218C3F98A00DC499C /* Cocoa.framework */, 306 | 0397802218C590F700DC499C /* CoreServices.framework */, 307 | 03977FD418C3F98A00DC499C /* Other Frameworks */, 308 | ); 309 | name = Frameworks; 310 | sourceTree = ""; 311 | }; 312 | 03977FD418C3F98A00DC499C /* Other Frameworks */ = { 313 | isa = PBXGroup; 314 | children = ( 315 | 03977FD518C3F98A00DC499C /* AppKit.framework */, 316 | 03977FD618C3F98A00DC499C /* CoreData.framework */, 317 | 03977FD718C3F98A00DC499C /* Foundation.framework */, 318 | ); 319 | name = "Other Frameworks"; 320 | sourceTree = ""; 321 | }; 322 | 03977FD818C3F98A00DC499C /* CloudyTabs */ = { 323 | isa = PBXGroup; 324 | children = ( 325 | 039C6EA81F935CE900C3B3E1 /* VDKQueue */, 326 | 03225D5C18C6AB2A000778DF /* Classes */, 327 | 03225D5718C6AA7A000778DF /* DSFavIconManager */, 328 | 03977FEA18C3F98A00DC499C /* Images.xcassets */, 329 | 03977FD918C3F98A00DC499C /* Supporting Files */, 330 | ); 331 | path = CloudyTabs; 332 | sourceTree = ""; 333 | }; 334 | 03977FD918C3F98A00DC499C /* Supporting Files */ = { 335 | isa = PBXGroup; 336 | children = ( 337 | 03977FDA18C3F98A00DC499C /* CloudyTabs-Info.plist */, 338 | 03977FDB18C3F98A00DC499C /* InfoPlist.strings */, 339 | 0308ABD918D676ED00AB4C54 /* Localizable.strings */, 340 | 03977FDE18C3F98A00DC499C /* main.m */, 341 | 03977FE018C3F98A00DC499C /* CloudyTabs-Prefix.pch */, 342 | 03225D5D18C6B18A000778DF /* dsa_pub.pem */, 343 | ); 344 | name = "Supporting Files"; 345 | sourceTree = ""; 346 | }; 347 | 039C6E9C1F935BF900C3B3E1 /* VDKQueue */ = { 348 | isa = PBXGroup; 349 | children = ( 350 | 039C6E9D1F935BF900C3B3E1 /* README.md */, 351 | 039C6E9E1F935BF900C3B3E1 /* VDKQueue.h */, 352 | 039C6E9F1F935BF900C3B3E1 /* VDKQueue.m */, 353 | ); 354 | path = VDKQueue; 355 | sourceTree = ""; 356 | }; 357 | 039C6EA01F935BFC00C3B3E1 /* VDKQueue */ = { 358 | isa = PBXGroup; 359 | children = ( 360 | 039C6EA21F935BFC00C3B3E1 /* VDKQueue.h */, 361 | 039C6EA31F935BFC00C3B3E1 /* VDKQueue.m */, 362 | ); 363 | path = VDKQueue; 364 | sourceTree = ""; 365 | }; 366 | 039C6EA81F935CE900C3B3E1 /* VDKQueue */ = { 367 | isa = PBXGroup; 368 | children = ( 369 | 039C6EAA1F935CE900C3B3E1 /* VDKQueue.h */, 370 | 039C6EAB1F935CE900C3B3E1 /* VDKQueue.m */, 371 | ); 372 | path = VDKQueue; 373 | sourceTree = ""; 374 | }; 375 | /* End PBXGroup section */ 376 | 377 | /* Begin PBXNativeTarget section */ 378 | 0350635524FB233200E71269 /* CloudyTabsHelper */ = { 379 | isa = PBXNativeTarget; 380 | buildConfigurationList = 0350636424FB233500E71269 /* Build configuration list for PBXNativeTarget "CloudyTabsHelper" */; 381 | buildPhases = ( 382 | 0350635224FB233200E71269 /* Sources */, 383 | 0350635324FB233200E71269 /* Frameworks */, 384 | 0350635424FB233200E71269 /* Resources */, 385 | ); 386 | buildRules = ( 387 | ); 388 | dependencies = ( 389 | ); 390 | name = CloudyTabsHelper; 391 | productName = CloudyTabsHelper; 392 | productReference = 0350635624FB233200E71269 /* CloudyTabsHelper.app */; 393 | productType = "com.apple.product-type.application"; 394 | }; 395 | 03648D811FA445FA004D3078 /* CloudyTabsTests */ = { 396 | isa = PBXNativeTarget; 397 | buildConfigurationList = 03648D891FA445FA004D3078 /* Build configuration list for PBXNativeTarget "CloudyTabsTests" */; 398 | buildPhases = ( 399 | 03648D7E1FA445FA004D3078 /* Sources */, 400 | 03648D7F1FA445FA004D3078 /* Frameworks */, 401 | 03648D801FA445FA004D3078 /* Resources */, 402 | ); 403 | buildRules = ( 404 | ); 405 | dependencies = ( 406 | 03648D881FA445FA004D3078 /* PBXTargetDependency */, 407 | ); 408 | name = CloudyTabsTests; 409 | productName = CloudyTabsTests; 410 | productReference = 03648D821FA445FA004D3078 /* CloudyTabsTests.xctest */; 411 | productType = "com.apple.product-type.bundle.unit-test"; 412 | }; 413 | 03977FCE18C3F98A00DC499C /* CloudyTabs */ = { 414 | isa = PBXNativeTarget; 415 | buildConfigurationList = 0397800018C3F98A00DC499C /* Build configuration list for PBXNativeTarget "CloudyTabs" */; 416 | buildPhases = ( 417 | 03977FCB18C3F98A00DC499C /* Sources */, 418 | 03977FCC18C3F98A00DC499C /* Frameworks */, 419 | 03977FCD18C3F98A00DC499C /* Resources */, 420 | 03225D4A18C5FED1000778DF /* CopyFiles */, 421 | 0350636724FB239D00E71269 /* CopyFiles */, 422 | 035B7F9A18DF0B6F00D2D945 /* ShellScript */, 423 | ); 424 | buildRules = ( 425 | ); 426 | dependencies = ( 427 | ); 428 | name = CloudyTabs; 429 | productName = CloudyTabs; 430 | productReference = 03977FCF18C3F98A00DC499C /* CloudyTabs.app */; 431 | productType = "com.apple.product-type.application"; 432 | }; 433 | /* End PBXNativeTarget section */ 434 | 435 | /* Begin PBXProject section */ 436 | 03977FC718C3F98A00DC499C /* Project object */ = { 437 | isa = PBXProject; 438 | attributes = { 439 | CLASSPREFIX = JP; 440 | LastUpgradeCheck = 1200; 441 | ORGANIZATIONNAME = "Josh Parnham"; 442 | TargetAttributes = { 443 | 0350635524FB233200E71269 = { 444 | CreatedOnToolsVersion = 11.6; 445 | DevelopmentTeam = 3C7269R37R; 446 | ProvisioningStyle = Automatic; 447 | }; 448 | 03648D811FA445FA004D3078 = { 449 | CreatedOnToolsVersion = 9.0.1; 450 | DevelopmentTeam = 3C7269R37R; 451 | ProvisioningStyle = Automatic; 452 | TestTargetID = 03977FCE18C3F98A00DC499C; 453 | }; 454 | 03977FCE18C3F98A00DC499C = { 455 | DevelopmentTeam = 3C7269R37R; 456 | ProvisioningStyle = Automatic; 457 | }; 458 | }; 459 | }; 460 | buildConfigurationList = 03977FCA18C3F98A00DC499C /* Build configuration list for PBXProject "CloudyTabs" */; 461 | compatibilityVersion = "Xcode 3.2"; 462 | developmentRegion = en; 463 | hasScannedForEncodings = 0; 464 | knownRegions = ( 465 | en, 466 | Base, 467 | fr, 468 | de, 469 | nl, 470 | es, 471 | ca, 472 | zh_CN, 473 | zh_TW, 474 | it, 475 | pt_BR, 476 | ru, 477 | ); 478 | mainGroup = 03977FC618C3F98A00DC499C; 479 | productRefGroup = 03977FD018C3F98A00DC499C /* Products */; 480 | projectDirPath = ""; 481 | projectRoot = ""; 482 | targets = ( 483 | 03977FCE18C3F98A00DC499C /* CloudyTabs */, 484 | 0350635524FB233200E71269 /* CloudyTabsHelper */, 485 | 03648D811FA445FA004D3078 /* CloudyTabsTests */, 486 | ); 487 | }; 488 | /* End PBXProject section */ 489 | 490 | /* Begin PBXResourcesBuildPhase section */ 491 | 0350635424FB233200E71269 /* Resources */ = { 492 | isa = PBXResourcesBuildPhase; 493 | buildActionMask = 2147483647; 494 | files = ( 495 | ); 496 | runOnlyForDeploymentPostprocessing = 0; 497 | }; 498 | 03648D801FA445FA004D3078 /* Resources */ = { 499 | isa = PBXResourcesBuildPhase; 500 | buildActionMask = 2147483647; 501 | files = ( 502 | ); 503 | runOnlyForDeploymentPostprocessing = 0; 504 | }; 505 | 03977FCD18C3F98A00DC499C /* Resources */ = { 506 | isa = PBXResourcesBuildPhase; 507 | buildActionMask = 2147483647; 508 | files = ( 509 | 03225D5E18C6B18A000778DF /* dsa_pub.pem in Resources */, 510 | 0308ABDB18D676ED00AB4C54 /* Localizable.strings in Resources */, 511 | 03977FDD18C3F98A00DC499C /* InfoPlist.strings in Resources */, 512 | 03977FEB18C3F98A00DC499C /* Images.xcassets in Resources */, 513 | ); 514 | runOnlyForDeploymentPostprocessing = 0; 515 | }; 516 | /* End PBXResourcesBuildPhase section */ 517 | 518 | /* Begin PBXShellScriptBuildPhase section */ 519 | 035B7F9A18DF0B6F00D2D945 /* ShellScript */ = { 520 | isa = PBXShellScriptBuildPhase; 521 | buildActionMask = 2147483647; 522 | files = ( 523 | ); 524 | inputPaths = ( 525 | ); 526 | outputPaths = ( 527 | ); 528 | runOnlyForDeploymentPostprocessing = 0; 529 | shellPath = /bin/sh; 530 | shellScript = "LOCATION=\"${BUILT_PRODUCTS_DIR}\"/\"${FRAMEWORKS_FOLDER_PATH}\"\nIDENTITY=\"${EXPANDED_CODE_SIGN_IDENTITY}\"\n\ncodesign --verbose --force --deep -o runtime --sign \"$IDENTITY\" \"$LOCATION/Sparkle.framework/Versions/A/Resources/AutoUpdate.app\"\ncodesign --verbose --force -o runtime --sign \"$IDENTITY\" \"$LOCATION/Sparkle.framework/Versions/A\"\n"; 531 | }; 532 | /* End PBXShellScriptBuildPhase section */ 533 | 534 | /* Begin PBXSourcesBuildPhase section */ 535 | 0350635224FB233200E71269 /* Sources */ = { 536 | isa = PBXSourcesBuildPhase; 537 | buildActionMask = 2147483647; 538 | files = ( 539 | 0350636224FB233500E71269 /* main.m in Sources */, 540 | 0350635A24FB233300E71269 /* AppDelegate.m in Sources */, 541 | 0350636B24FB245700E71269 /* NSBundle+ApplicationPath.m in Sources */, 542 | ); 543 | runOnlyForDeploymentPostprocessing = 0; 544 | }; 545 | 03648D7E1FA445FA004D3078 /* Sources */ = { 546 | isa = PBXSourcesBuildPhase; 547 | buildActionMask = 2147483647; 548 | files = ( 549 | 03648D8D1FA44629004D3078 /* NSURL+DecodeTests.m in Sources */, 550 | 03648D851FA445FA004D3078 /* CloudyTabsTests.m in Sources */, 551 | ); 552 | runOnlyForDeploymentPostprocessing = 0; 553 | }; 554 | 03977FCB18C3F98A00DC499C /* Sources */ = { 555 | isa = PBXSourcesBuildPhase; 556 | buildActionMask = 2147483647; 557 | files = ( 558 | 0350637124FB398600E71269 /* JPCloudTabsDBReader.m in Sources */, 559 | 039C6EAC1F935D2E00C3B3E1 /* VDKQueue.m in Sources */, 560 | 03225D4C18C6A40D000778DF /* JPLaunchAtLoginManager.m in Sources */, 561 | 0308B34B250E49470090D507 /* JPTabCSVExporter.m in Sources */, 562 | 0308B336250DDFD10090D507 /* NSData+ZlibInflate.m in Sources */, 563 | 030D99852172BBFD00A9DE75 /* JPReadingListReader.m in Sources */, 564 | 03225D5518C6AA5A000778DF /* DSFavIconManager.m in Sources */, 565 | 0308B348250E49240090D507 /* CHCSVParser.m in Sources */, 566 | 0308B3242504BFC80090D507 /* NSMenuItem+ItemCreation.m in Sources */, 567 | 03120E52202FE2F300BE3891 /* JPUserDefaultsController.m in Sources */, 568 | 030D99822172BBAB00A9DE75 /* JPLoadingMenuItem.m in Sources */, 569 | 0375994218C5F967000E8C33 /* JPAppDelegate.m in Sources */, 570 | 03648D7D1FA4434B004D3078 /* NSURL+Extensions.m in Sources */, 571 | 03977FDF18C3F98A00DC499C /* main.m in Sources */, 572 | 03225D5418C6AA5A000778DF /* DSFavIconCache.m in Sources */, 573 | 03225D5618C6AA5A000778DF /* DSFavIconOperation.m in Sources */, 574 | ); 575 | runOnlyForDeploymentPostprocessing = 0; 576 | }; 577 | /* End PBXSourcesBuildPhase section */ 578 | 579 | /* Begin PBXTargetDependency section */ 580 | 03648D881FA445FA004D3078 /* PBXTargetDependency */ = { 581 | isa = PBXTargetDependency; 582 | target = 03977FCE18C3F98A00DC499C /* CloudyTabs */; 583 | targetProxy = 03648D871FA445FA004D3078 /* PBXContainerItemProxy */; 584 | }; 585 | /* End PBXTargetDependency section */ 586 | 587 | /* Begin PBXVariantGroup section */ 588 | 0308ABD918D676ED00AB4C54 /* Localizable.strings */ = { 589 | isa = PBXVariantGroup; 590 | children = ( 591 | 0308ABDA18D676ED00AB4C54 /* en */, 592 | 0308ABDC18D67A1300AB4C54 /* fr */, 593 | 0319CFB118D67AF70007A9FF /* de */, 594 | 03822D4418DA61D20065F586 /* nl */, 595 | 03822D4618DA62010065F586 /* es */, 596 | 03822D4818DA62400065F586 /* ca */, 597 | 03822D4A18DA62880065F586 /* zh_CN */, 598 | 03822D4C18DA62B90065F586 /* zh_TW */, 599 | 03822D4E18DA873E0065F586 /* it */, 600 | 030F11BB18E3958C00B2DFB0 /* pt_BR */, 601 | 7735FBA220BCBA1700F30E6F /* ru */, 602 | ); 603 | name = Localizable.strings; 604 | sourceTree = ""; 605 | }; 606 | 03977FDB18C3F98A00DC499C /* InfoPlist.strings */ = { 607 | isa = PBXVariantGroup; 608 | children = ( 609 | 03977FDC18C3F98A00DC499C /* en */, 610 | 0319CFB218D67B080007A9FF /* fr */, 611 | 0319CFB318D67B110007A9FF /* de */, 612 | 03822D4518DA61D70065F586 /* nl */, 613 | 03822D4718DA62050065F586 /* es */, 614 | 03822D4918DA62440065F586 /* ca */, 615 | 03822D4B18DA62950065F586 /* zh_CN */, 616 | 03822D4D18DA62BD0065F586 /* zh_TW */, 617 | 03822D4F18DA87420065F586 /* it */, 618 | 030F11BC18E3959100B2DFB0 /* pt_BR */, 619 | 7735FBA120BCBA1700F30E6F /* ru */, 620 | ); 621 | name = InfoPlist.strings; 622 | sourceTree = ""; 623 | }; 624 | /* End PBXVariantGroup section */ 625 | 626 | /* Begin XCBuildConfiguration section */ 627 | 0350636524FB233500E71269 /* Debug */ = { 628 | isa = XCBuildConfiguration; 629 | buildSettings = { 630 | CLANG_ANALYZER_NONNULL = YES; 631 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 632 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 633 | CLANG_ENABLE_MODULES = YES; 634 | CLANG_ENABLE_OBJC_WEAK = YES; 635 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 636 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 637 | CODE_SIGN_ENTITLEMENTS = CloudyTabsHelper/CloudyTabsHelper.entitlements; 638 | CODE_SIGN_IDENTITY = "Apple Development"; 639 | CODE_SIGN_STYLE = Automatic; 640 | COMBINE_HIDPI_IMAGES = YES; 641 | CURRENT_PROJECT_VERSION = 2.0.0; 642 | DEBUG_INFORMATION_FORMAT = dwarf; 643 | DEVELOPMENT_TEAM = 3C7269R37R; 644 | ENABLE_HARDENED_RUNTIME = YES; 645 | GCC_C_LANGUAGE_STANDARD = gnu11; 646 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 647 | INFOPLIST_FILE = CloudyTabsHelper/Info.plist; 648 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 649 | MACOSX_DEPLOYMENT_TARGET = 10.13.0; 650 | MARKETING_VERSION = 2.0.0; 651 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 652 | MTL_FAST_MATH = YES; 653 | PRODUCT_BUNDLE_IDENTIFIER = com.joshparnham.CloudyTabsHelper; 654 | PRODUCT_NAME = "$(TARGET_NAME)"; 655 | SKIP_INSTALL = YES; 656 | }; 657 | name = Debug; 658 | }; 659 | 0350636624FB233500E71269 /* Release */ = { 660 | isa = XCBuildConfiguration; 661 | buildSettings = { 662 | CLANG_ANALYZER_NONNULL = YES; 663 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 664 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 665 | CLANG_ENABLE_MODULES = YES; 666 | CLANG_ENABLE_OBJC_WEAK = YES; 667 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 668 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 669 | CODE_SIGN_ENTITLEMENTS = CloudyTabsHelper/CloudyTabsHelper.entitlements; 670 | CODE_SIGN_IDENTITY = "Apple Development"; 671 | CODE_SIGN_STYLE = Automatic; 672 | COMBINE_HIDPI_IMAGES = YES; 673 | COPY_PHASE_STRIP = NO; 674 | CURRENT_PROJECT_VERSION = 2.0.0; 675 | DEVELOPMENT_TEAM = 3C7269R37R; 676 | ENABLE_HARDENED_RUNTIME = YES; 677 | GCC_C_LANGUAGE_STANDARD = gnu11; 678 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 679 | INFOPLIST_FILE = CloudyTabsHelper/Info.plist; 680 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 681 | MACOSX_DEPLOYMENT_TARGET = 10.13.0; 682 | MARKETING_VERSION = 2.0.0; 683 | MTL_ENABLE_DEBUG_INFO = NO; 684 | MTL_FAST_MATH = YES; 685 | PRODUCT_BUNDLE_IDENTIFIER = com.joshparnham.CloudyTabsHelper; 686 | PRODUCT_NAME = "$(TARGET_NAME)"; 687 | SKIP_INSTALL = YES; 688 | }; 689 | name = Release; 690 | }; 691 | 03648D8A1FA445FA004D3078 /* Debug */ = { 692 | isa = XCBuildConfiguration; 693 | buildSettings = { 694 | BUNDLE_LOADER = "$(TEST_HOST)"; 695 | CLANG_ANALYZER_NONNULL = YES; 696 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 697 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 698 | CLANG_ENABLE_MODULES = YES; 699 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 700 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 701 | CODE_SIGN_IDENTITY = "Mac Developer"; 702 | CODE_SIGN_STYLE = Automatic; 703 | COMBINE_HIDPI_IMAGES = YES; 704 | DEBUG_INFORMATION_FORMAT = dwarf; 705 | DEVELOPMENT_TEAM = 3C7269R37R; 706 | GCC_C_LANGUAGE_STANDARD = gnu11; 707 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 708 | INFOPLIST_FILE = CloudyTabsTests/Info.plist; 709 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 710 | MACOSX_DEPLOYMENT_TARGET = 10.12; 711 | MTL_ENABLE_DEBUG_INFO = YES; 712 | PRODUCT_BUNDLE_IDENTIFIER = com.joshparnham.CloudyTabsTests; 713 | PRODUCT_NAME = "$(TARGET_NAME)"; 714 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CloudyTabs.app/Contents/MacOS/CloudyTabs"; 715 | }; 716 | name = Debug; 717 | }; 718 | 03648D8B1FA445FA004D3078 /* Release */ = { 719 | isa = XCBuildConfiguration; 720 | buildSettings = { 721 | BUNDLE_LOADER = "$(TEST_HOST)"; 722 | CLANG_ANALYZER_NONNULL = YES; 723 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 724 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 725 | CLANG_ENABLE_MODULES = YES; 726 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 727 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 728 | CODE_SIGN_IDENTITY = "Mac Developer"; 729 | CODE_SIGN_STYLE = Automatic; 730 | COMBINE_HIDPI_IMAGES = YES; 731 | COPY_PHASE_STRIP = NO; 732 | DEVELOPMENT_TEAM = 3C7269R37R; 733 | GCC_C_LANGUAGE_STANDARD = gnu11; 734 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 735 | INFOPLIST_FILE = CloudyTabsTests/Info.plist; 736 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 737 | MACOSX_DEPLOYMENT_TARGET = 10.12; 738 | MTL_ENABLE_DEBUG_INFO = NO; 739 | PRODUCT_BUNDLE_IDENTIFIER = com.joshparnham.CloudyTabsTests; 740 | PRODUCT_NAME = "$(TARGET_NAME)"; 741 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CloudyTabs.app/Contents/MacOS/CloudyTabs"; 742 | }; 743 | name = Release; 744 | }; 745 | 03977FFE18C3F98A00DC499C /* Debug */ = { 746 | isa = XCBuildConfiguration; 747 | buildSettings = { 748 | ALWAYS_SEARCH_USER_PATHS = NO; 749 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 750 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 751 | CLANG_CXX_LIBRARY = "libc++"; 752 | CLANG_ENABLE_OBJC_ARC = YES; 753 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 754 | CLANG_WARN_BOOL_CONVERSION = YES; 755 | CLANG_WARN_COMMA = YES; 756 | CLANG_WARN_CONSTANT_CONVERSION = YES; 757 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 758 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 759 | CLANG_WARN_EMPTY_BODY = YES; 760 | CLANG_WARN_ENUM_CONVERSION = YES; 761 | CLANG_WARN_INFINITE_RECURSION = YES; 762 | CLANG_WARN_INT_CONVERSION = YES; 763 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 764 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 765 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 766 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 767 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 768 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 769 | CLANG_WARN_STRICT_PROTOTYPES = YES; 770 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 771 | CLANG_WARN_UNREACHABLE_CODE = YES; 772 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 773 | COPY_PHASE_STRIP = NO; 774 | ENABLE_STRICT_OBJC_MSGSEND = YES; 775 | ENABLE_TESTABILITY = YES; 776 | GCC_C_LANGUAGE_STANDARD = gnu99; 777 | GCC_DYNAMIC_NO_PIC = NO; 778 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 779 | GCC_NO_COMMON_BLOCKS = YES; 780 | GCC_OPTIMIZATION_LEVEL = 0; 781 | GCC_PREPROCESSOR_DEFINITIONS = ( 782 | "DEBUG=1", 783 | "$(inherited)", 784 | ); 785 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 786 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 787 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 788 | GCC_WARN_UNDECLARED_SELECTOR = YES; 789 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 790 | GCC_WARN_UNUSED_FUNCTION = YES; 791 | GCC_WARN_UNUSED_VARIABLE = YES; 792 | MACOSX_DEPLOYMENT_TARGET = 10.13.0; 793 | ONLY_ACTIVE_ARCH = YES; 794 | SDKROOT = macosx; 795 | }; 796 | name = Debug; 797 | }; 798 | 03977FFF18C3F98A00DC499C /* Release */ = { 799 | isa = XCBuildConfiguration; 800 | buildSettings = { 801 | ALWAYS_SEARCH_USER_PATHS = NO; 802 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 803 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 804 | CLANG_CXX_LIBRARY = "libc++"; 805 | CLANG_ENABLE_OBJC_ARC = YES; 806 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 807 | CLANG_WARN_BOOL_CONVERSION = YES; 808 | CLANG_WARN_COMMA = YES; 809 | CLANG_WARN_CONSTANT_CONVERSION = YES; 810 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 811 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 812 | CLANG_WARN_EMPTY_BODY = YES; 813 | CLANG_WARN_ENUM_CONVERSION = YES; 814 | CLANG_WARN_INFINITE_RECURSION = YES; 815 | CLANG_WARN_INT_CONVERSION = YES; 816 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 817 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 818 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 819 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 820 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 821 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 822 | CLANG_WARN_STRICT_PROTOTYPES = YES; 823 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 824 | CLANG_WARN_UNREACHABLE_CODE = YES; 825 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 826 | COPY_PHASE_STRIP = YES; 827 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 828 | ENABLE_NS_ASSERTIONS = NO; 829 | ENABLE_STRICT_OBJC_MSGSEND = YES; 830 | GCC_C_LANGUAGE_STANDARD = gnu99; 831 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 832 | GCC_NO_COMMON_BLOCKS = YES; 833 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 834 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 835 | GCC_WARN_UNDECLARED_SELECTOR = YES; 836 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 837 | GCC_WARN_UNUSED_FUNCTION = YES; 838 | GCC_WARN_UNUSED_VARIABLE = YES; 839 | MACOSX_DEPLOYMENT_TARGET = 10.13.0; 840 | SDKROOT = macosx; 841 | }; 842 | name = Release; 843 | }; 844 | 0397800118C3F98A00DC499C /* Debug */ = { 845 | isa = XCBuildConfiguration; 846 | buildSettings = { 847 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 848 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; 849 | CODE_SIGN_IDENTITY = "Mac Developer"; 850 | COMBINE_HIDPI_IMAGES = YES; 851 | CURRENT_PROJECT_VERSION = 2.0.0; 852 | DEVELOPMENT_TEAM = 3C7269R37R; 853 | ENABLE_HARDENED_RUNTIME = YES; 854 | FRAMEWORK_SEARCH_PATHS = ( 855 | "$(PROJECT_DIR)/CloudyTabs", 856 | "$(PROJECT_DIR)/Carthage/Build/Mac", 857 | ); 858 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 859 | GCC_PREFIX_HEADER = "CloudyTabs/CloudyTabs-Prefix.pch"; 860 | INFOPLIST_FILE = "CloudyTabs/CloudyTabs-Info.plist"; 861 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 862 | MACOSX_DEPLOYMENT_TARGET = 10.13.0; 863 | MARKETING_VERSION = 2.0.0; 864 | PRODUCT_BUNDLE_IDENTIFIER = "com.joshparnham.${PRODUCT_NAME:rfc1034identifier}"; 865 | PRODUCT_NAME = CloudyTabs; 866 | WRAPPER_EXTENSION = app; 867 | }; 868 | name = Debug; 869 | }; 870 | 0397800218C3F98A00DC499C /* Release */ = { 871 | isa = XCBuildConfiguration; 872 | buildSettings = { 873 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 874 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; 875 | CODE_SIGN_IDENTITY = "Mac Developer"; 876 | COMBINE_HIDPI_IMAGES = YES; 877 | CURRENT_PROJECT_VERSION = 2.0.0; 878 | DEVELOPMENT_TEAM = 3C7269R37R; 879 | ENABLE_HARDENED_RUNTIME = YES; 880 | FRAMEWORK_SEARCH_PATHS = ( 881 | "$(PROJECT_DIR)/CloudyTabs", 882 | "$(PROJECT_DIR)/Carthage/Build/Mac", 883 | ); 884 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 885 | GCC_PREFIX_HEADER = "CloudyTabs/CloudyTabs-Prefix.pch"; 886 | INFOPLIST_FILE = "CloudyTabs/CloudyTabs-Info.plist"; 887 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 888 | MACOSX_DEPLOYMENT_TARGET = 10.13.0; 889 | MARKETING_VERSION = 2.0.0; 890 | PRODUCT_BUNDLE_IDENTIFIER = "com.joshparnham.${PRODUCT_NAME:rfc1034identifier}"; 891 | PRODUCT_NAME = CloudyTabs; 892 | WRAPPER_EXTENSION = app; 893 | }; 894 | name = Release; 895 | }; 896 | /* End XCBuildConfiguration section */ 897 | 898 | /* Begin XCConfigurationList section */ 899 | 0350636424FB233500E71269 /* Build configuration list for PBXNativeTarget "CloudyTabsHelper" */ = { 900 | isa = XCConfigurationList; 901 | buildConfigurations = ( 902 | 0350636524FB233500E71269 /* Debug */, 903 | 0350636624FB233500E71269 /* Release */, 904 | ); 905 | defaultConfigurationIsVisible = 0; 906 | defaultConfigurationName = Release; 907 | }; 908 | 03648D891FA445FA004D3078 /* Build configuration list for PBXNativeTarget "CloudyTabsTests" */ = { 909 | isa = XCConfigurationList; 910 | buildConfigurations = ( 911 | 03648D8A1FA445FA004D3078 /* Debug */, 912 | 03648D8B1FA445FA004D3078 /* Release */, 913 | ); 914 | defaultConfigurationIsVisible = 0; 915 | defaultConfigurationName = Release; 916 | }; 917 | 03977FCA18C3F98A00DC499C /* Build configuration list for PBXProject "CloudyTabs" */ = { 918 | isa = XCConfigurationList; 919 | buildConfigurations = ( 920 | 03977FFE18C3F98A00DC499C /* Debug */, 921 | 03977FFF18C3F98A00DC499C /* Release */, 922 | ); 923 | defaultConfigurationIsVisible = 0; 924 | defaultConfigurationName = Release; 925 | }; 926 | 0397800018C3F98A00DC499C /* Build configuration list for PBXNativeTarget "CloudyTabs" */ = { 927 | isa = XCConfigurationList; 928 | buildConfigurations = ( 929 | 0397800118C3F98A00DC499C /* Debug */, 930 | 0397800218C3F98A00DC499C /* Release */, 931 | ); 932 | defaultConfigurationIsVisible = 0; 933 | defaultConfigurationName = Release; 934 | }; 935 | /* End XCConfigurationList section */ 936 | }; 937 | rootObject = 03977FC718C3F98A00DC499C /* Project object */; 938 | } 939 | -------------------------------------------------------------------------------- /CloudyTabs/CloudyTabs-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.utilities 27 | LSMinimumSystemVersion 28 | ${MACOSX_DEPLOYMENT_TARGET} 29 | LSUIElement 30 | 31 | NSAllowsArbitraryLoads 32 | 33 | NSHumanReadableCopyright 34 | Copyright © 2018 Josh Parnham. All rights reserved. 35 | NSPrincipalClass 36 | NSApplication 37 | SUFeedURL 38 | https://joshparnham.com/projects/cloudytabs/appcast.xml 39 | SUPublicDSAKeyFile 40 | dsa_pub.pem 41 | 42 | 43 | -------------------------------------------------------------------------------- /CloudyTabs/CloudyTabs-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /CloudyTabs/Icon.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Icon.pxm -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "Icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "Icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "Icon_32-1.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "Icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "Icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "Icon_256-1.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "Icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "Icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "Icon_512-1.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "Icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_1024.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_128.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_16.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_256-1.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_256.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_32-1.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_32.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_512-1.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_512.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/AppIcon.appiconset/Icon_64.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/Globe.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "globe.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "globe@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "globe@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/Globe.imageset/globe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/Globe.imageset/globe.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/Globe.imageset/globe@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/Globe.imageset/globe@2x.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/Globe.imageset/globe@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/Globe.imageset/globe@3x.png -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/StatusIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "StatusIcon.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /CloudyTabs/Images.xcassets/StatusIcon.imageset/StatusIcon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/CloudyTabs/3e495a10c00661f2c2edf068868cc928174d76c4/CloudyTabs/Images.xcassets/StatusIcon.imageset/StatusIcon.pdf -------------------------------------------------------------------------------- /CloudyTabs/JPAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // JPAppDelegate.h 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 3/03/2014. 6 | // Copyright (c) 2014 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "VDKQueue.h" 12 | 13 | @interface JPAppDelegate : NSObject 14 | 15 | @property (strong, nonatomic) NSStatusItem *statusItem; 16 | @property (strong, nonatomic) NSMenu *menu; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CloudyTabs/JPAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // JPAppDelegate.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 3/03/2014. 6 | // Copyright (c) 2014 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "JPAppDelegate.h" 10 | 11 | #import 12 | 13 | #import "JPLaunchAtLoginManager.h" 14 | #import "JPUserDefaultsController.h" 15 | #import "DSFavIconManager.h" 16 | 17 | #import "JPCloudTabsDBReader.h" 18 | #import "JPReadingListReader.h" 19 | 20 | #import "JPLoadingMenuItem.h" 21 | #import "JPTabCSVExporter.h" 22 | #import "NSURL+Extensions.h" 23 | #import "NSMenuItem+ItemCreation.h" 24 | 25 | @interface JPAppDelegate () 26 | 27 | @property (strong, nonatomic) NSDate *lastUpdateDate; 28 | 29 | @property (strong, nonatomic) JPCloudTabsDBReader *cloudTabsDBReader; 30 | @property (strong, nonatomic) JPReadingListReader *readingListReader; 31 | 32 | @property (strong, nonatomic) NSArray *tabData; 33 | @property (strong, nonatomic) NSArray *readingListItems; 34 | 35 | @end 36 | 37 | NSString *const HELPER_BUNDLE_ID = @"com.joshparnham.CloudyTabsHelper"; 38 | 39 | @implementation JPAppDelegate 40 | 41 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 42 | { 43 | self.cloudTabsDBReader = [[JPCloudTabsDBReader alloc] init]; 44 | self.readingListReader = [[JPReadingListReader alloc] init]; 45 | 46 | [DSFavIconManager sharedInstance].placeholder = [NSImage imageNamed:@"Globe"]; 47 | [JPUserDefaultsController registerUserDefaults]; 48 | 49 | [self createStatusItem]; 50 | [self updateMenu]; 51 | [self setupQueue]; 52 | [self setupSparkle]; 53 | [self updateUserInterface]; 54 | } 55 | 56 | #pragma mark - Menu actions 57 | 58 | - (void)tabMenuItemClicked:(id)sender 59 | { 60 | NSURL *URL = [(NSMenuItem *)sender representedObject]; 61 | 62 | if ([NSEvent modifierFlags] == NSEventModifierFlagCommand) { 63 | [[NSWorkspace sharedWorkspace] openURLs:@[URL] withAppBundleIdentifier:nil options:NSWorkspaceLaunchWithoutActivation additionalEventParamDescriptor:nil launchIdentifiers:nil]; 64 | } 65 | else if ([NSEvent modifierFlags] == NSEventModifierFlagOption) { 66 | NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; 67 | [pasteboard clearContents]; 68 | 69 | NSURL *URL = [[self.menu highlightedItem] representedObject]; 70 | if (![pasteboard writeObjects:@[URL]]) { 71 | NSLog(@"Unable to copy URL to the pasteboard."); 72 | } 73 | } 74 | else { 75 | [[NSWorkspace sharedWorkspace] openURL:URL]; 76 | } 77 | } 78 | 79 | - (void)openAllFromClicked:(NSMenuItem *)sender 80 | { 81 | NSUInteger launch; 82 | if ([NSEvent modifierFlags] == NSEventModifierFlagCommand) { 83 | launch = NSWorkspaceLaunchWithoutActivation; 84 | } 85 | else { 86 | launch = NSWorkspaceLaunchDefault; 87 | } 88 | 89 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 90 | NSString *deviceID = sender.representedObject; 91 | NSDictionary *tabData = self.tabData[[self.tabData indexOfObjectPassingTest:^BOOL(id _Nonnull dictionary, NSUInteger index, BOOL * _Nonnull stop) { 92 | return [dictionary[@"DeviceName"] isEqualToString:deviceID]; 93 | }]]; 94 | 95 | if (!tabData) { 96 | return; 97 | } 98 | 99 | NSArray *tabs = tabData[@"Tabs"]; 100 | 101 | for (NSDictionary *tabDictionary in tabs) { 102 | NSURL *url = [NSURL decodeURL:tabDictionary[@"URL"]]; 103 | 104 | [[NSWorkspace sharedWorkspace] openURLs:@[url] withAppBundleIdentifier:nil options:launch additionalEventParamDescriptor:nil launchIdentifiers:nil]; 105 | usleep(500000); 106 | } 107 | }); 108 | } 109 | 110 | - (void)exportAllFromClicked:(NSMenuItem *)sender 111 | { 112 | NSString *deviceID = sender.representedObject; 113 | NSDictionary *tabData = self.tabData[[self.tabData indexOfObjectPassingTest:^BOOL(id _Nonnull dictionary, NSUInteger index, BOOL * _Nonnull stop) { 114 | return [dictionary[@"DeviceName"] isEqualToString:deviceID]; 115 | }]]; 116 | 117 | if (!tabData) { 118 | return; 119 | } 120 | 121 | NSArray *tabs = tabData[@"Tabs"]; 122 | NSString *deviceName = tabData[@"DeviceName"]; 123 | 124 | [JPTabCSVExporter exportTabs:tabs deviceName:deviceName]; 125 | } 126 | 127 | - (void)openAtLoginToggled:(id)sender 128 | { 129 | [self setStartAtLogin:(![self startAtLogin])]; 130 | } 131 | 132 | - (void)promptFullDiskAccess:(id)sender 133 | { 134 | NSAlert *alert = [[NSAlert alloc] init]; 135 | alert.messageText = NSLocalizedString(@"Enable Full Disk Access", @""); 136 | alert.informativeText = NSLocalizedString(@"CloudyTabs requies Full Disk Access to access your Safari data. Click \"Enable\" to open System Prefences and then click the checkbox next to \"CloudyTabs\".", @""); 137 | alert.alertStyle = NSAlertStyleInformational; 138 | 139 | [alert addButtonWithTitle:NSLocalizedString(@"Enable", @"")]; 140 | [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"")]; 141 | 142 | NSInteger alertResult = [alert runModal]; 143 | if (alertResult == NSAlertFirstButtonReturn) { 144 | [[NSWorkspace sharedWorkspace] openURL:[NSURL privacyAllFilesSystemPreferencesURL]]; 145 | } 146 | } 147 | 148 | #pragma mark - Queue delegate 149 | 150 | -(void)VDKQueue:(VDKQueue *)queue receivedNotification:(NSString*)noteName forPath:(NSString*)fpath 151 | { 152 | [self.readingListReader fetchReadingListModificationDate:^(NSDate *readingListModificationDate) { 153 | // Only update menu if the data has been updated since last refresh 154 | BOOL newReadingListData = [readingListModificationDate laterDate:self.lastUpdateDate] == readingListModificationDate; 155 | if (newReadingListData) { 156 | dispatch_async(dispatch_get_main_queue(), ^{ 157 | [self updateUserInterface]; 158 | }); 159 | } 160 | }]; 161 | 162 | [self.cloudTabsDBReader fetchDatabaseModificationDate:^(NSDate *cloudTabsModificationDate) { 163 | // Only update menu if the data has been updated since last refresh 164 | BOOL newCloudTabData = [cloudTabsModificationDate laterDate:self.lastUpdateDate] == cloudTabsModificationDate; 165 | if (newCloudTabData) { 166 | dispatch_async(dispatch_get_main_queue(), ^{ 167 | [self updateUserInterface]; 168 | }); 169 | } 170 | }]; 171 | } 172 | 173 | #pragma mark - Methods 174 | 175 | - (NSString *)appBundleName 176 | { 177 | return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; 178 | } 179 | 180 | - (NSString *)appBundleVersion 181 | { 182 | return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; 183 | } 184 | 185 | - (void)setupSparkle 186 | { 187 | SUUpdater *updater = [SUUpdater sharedUpdater]; 188 | [updater checkForUpdatesInBackground]; 189 | } 190 | 191 | - (void)checkForUpdates:(id)sender 192 | { 193 | SUUpdater *updater = [SUUpdater sharedUpdater]; 194 | [updater checkForUpdates:self]; 195 | } 196 | 197 | - (void)setupQueue 198 | { 199 | VDKQueue *queue = [[VDKQueue alloc] init]; 200 | 201 | [queue addPath:[self.cloudTabsDBReader cloudTabsDBFile] notifyingAbout:VDKQueueNotifyDefault]; 202 | [queue addPath:[self.readingListReader syncedBookmarksFile] notifyingAbout:VDKQueueNotifyDefault]; 203 | 204 | queue.delegate = self; 205 | } 206 | 207 | - (void)updateUserInterface 208 | { 209 | [self.cloudTabsDBReader fetchTabData:^(NSArray *tabData) { 210 | self.tabData = tabData; 211 | 212 | dispatch_async(dispatch_get_main_queue(), ^{ 213 | [self updateMenu]; 214 | }); 215 | }]; 216 | 217 | [self.readingListReader fetchReadingListItems:^(NSArray *readingListItems) { 218 | self.readingListItems = readingListItems; 219 | 220 | dispatch_async(dispatch_get_main_queue(), ^{ 221 | [self updateMenu]; 222 | }); 223 | }]; 224 | } 225 | 226 | - (BOOL)requiresFullDiskAccess 227 | { 228 | return ![self.cloudTabsDBReader permissionsToReadFile]; 229 | } 230 | 231 | - (void)createStatusItem 232 | { 233 | self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; 234 | [self.statusItem setHighlightMode:YES]; 235 | [self.statusItem setImage:[NSImage imageNamed:@"StatusIcon"]]; 236 | [self.statusItem setMenu:self.menu]; 237 | [self.statusItem setEnabled:YES]; 238 | 239 | NSString *toolTip = [NSString stringWithFormat:NSLocalizedString(@"%@ (%@)", @""), [self appBundleName], [self appBundleVersion]]; 240 | self.statusItem.toolTip = toolTip; 241 | } 242 | 243 | - (void)updateMenu 244 | { 245 | [self.menu removeAllItems]; 246 | 247 | if (self.tabData) { 248 | NSMenu *openAllTabsSubMenu = [[NSMenu alloc] initWithTitle:@"Open All Tabs From"]; 249 | openAllTabsSubMenu.autoenablesItems = NO; 250 | 251 | NSMenu *exportTabsSubMenu = [[NSMenu alloc] initWithTitle:@"Export All Tabs From"]; 252 | exportTabsSubMenu.autoenablesItems = NO; 253 | 254 | for (NSDictionary *deviceTabs in self.tabData) { 255 | 256 | // Hide devices that don't have any tabs 257 | if (((NSArray *)deviceTabs[@"Tabs"]).count <= 0) { 258 | continue; 259 | } 260 | 261 | NSString *localisedTabCount = [NSNumberFormatter localizedStringFromNumber:[NSNumber numberWithInteger:((NSArray *)deviceTabs[@"Tabs"]).count] numberStyle:NSNumberFormatterNoStyle]; 262 | NSString *deviceMenuItemTitle = [NSString stringWithFormat:@"%@ (%@)", deviceTabs[@"DeviceName"], localisedTabCount]; 263 | 264 | // Add device to "Export All Tabs From" submenu 265 | NSMenuItem *exportAllTabsFromDeviceMenuItem = [[NSMenuItem alloc] initWithTitle:deviceMenuItemTitle action:@selector(exportAllFromClicked:) keyEquivalent:@""]; 266 | exportAllTabsFromDeviceMenuItem.representedObject = deviceTabs[@"DeviceName"]; 267 | if ([deviceTabs[@"Tabs"] count] < 1) { 268 | [exportAllTabsFromDeviceMenuItem setEnabled:NO]; 269 | } 270 | [exportTabsSubMenu addItem:exportAllTabsFromDeviceMenuItem]; 271 | 272 | // Hide tabs from Mac where CloudyTabs is currently running on, unless the user has expliclty set the user default 273 | if ([JPUserDefaultsController shouldListAllDevices] == false && [[NSHost currentHost].localizedName isEqualToString:deviceTabs[@"DeviceName"]]) { 274 | continue; 275 | } 276 | 277 | // Add a seperator if this device isn't the first in the list 278 | if (self.menu.itemArray.count > 0) { 279 | NSMenuItem *seperatorItem = [NSMenuItem separatorItem]; 280 | [self.menu addItem:seperatorItem]; 281 | } 282 | 283 | NSMenuItem *deviceMenuItem = [[NSMenuItem alloc] initWithTitle:deviceTabs[@"DeviceName"] action:nil keyEquivalent:@""]; 284 | deviceMenuItem.enabled = NO; 285 | [self.menu addItem:deviceMenuItem]; 286 | 287 | // Add device to "Open All Tabs From" submenu 288 | NSMenuItem *openAllTabsFromDeviceMenuItem = [[NSMenuItem alloc] initWithTitle:deviceMenuItemTitle action:@selector(openAllFromClicked:) keyEquivalent:@""]; 289 | openAllTabsFromDeviceMenuItem.representedObject = deviceTabs[@"DeviceName"]; 290 | if ([deviceTabs[@"Tabs"] count] < 1) { 291 | [openAllTabsFromDeviceMenuItem setEnabled:NO]; 292 | } 293 | [openAllTabsSubMenu addItem:openAllTabsFromDeviceMenuItem]; 294 | 295 | for (NSDictionary *tabDictionary in deviceTabs[@"Tabs"]) { 296 | [self.menu addItem:[NSMenuItem menuItemWithTitle:tabDictionary[@"Title"] URLPath:tabDictionary[@"URL"] action:@selector(tabMenuItemClicked:)]]; 297 | } 298 | } 299 | 300 | if ([self readingListItems].count > 0) { 301 | if (self.menu.itemArray.count > 0) { 302 | [self.menu addItem:[NSMenuItem separatorItem]]; 303 | } 304 | 305 | NSMenuItem *readingListTitle = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reading List", @"") action:nil keyEquivalent:@""]; 306 | readingListTitle.enabled = NO; 307 | [self.menu addItem:readingListTitle]; 308 | 309 | for (NSDictionary *bookmarkDictionary in self.readingListItems) { 310 | [self.menu addItem:[NSMenuItem menuItemWithTitle:bookmarkDictionary[@"URIDictionary"][@"title"] URLPath:bookmarkDictionary[@"URLString"] action:@selector(tabMenuItemClicked:)]]; 311 | } 312 | } 313 | 314 | if (self.menu.itemArray.count > 0) { 315 | [self.menu addItem:[NSMenuItem separatorItem]]; 316 | } 317 | 318 | if (self.tabData.count >= 1) { 319 | NSMenuItem *openAllTabsMenu = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Open All Tabs From", @"") action:nil keyEquivalent:@""]; 320 | [self.menu addItem:openAllTabsMenu]; 321 | openAllTabsMenu.submenu = openAllTabsSubMenu; 322 | openAllTabsMenu.enabled = openAllTabsSubMenu.itemArray.count >= 1; 323 | 324 | NSMenuItem *exportAllTabsMenu = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Export All Tabs From", @"") action:nil keyEquivalent:@""]; 325 | [self.menu addItem:exportAllTabsMenu]; 326 | exportAllTabsMenu.submenu = exportTabsSubMenu; 327 | exportAllTabsMenu.enabled = exportTabsSubMenu.itemArray.count >= 1; 328 | } 329 | } else { 330 | if ([self requiresFullDiskAccess]) { 331 | NSMenuItem *fullDiskAccessItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Requires Full Disk Access…", @"") action:@selector(promptFullDiskAccess:) keyEquivalent:@""]; 332 | [self.menu addItem:fullDiskAccessItem]; 333 | [self.menu addItem:[NSMenuItem separatorItem]]; 334 | } else { 335 | JPLoadingMenuItem *loadingMenuItem = [[JPLoadingMenuItem alloc] initWithTitle:@"Loading" action:nil keyEquivalent:@""]; 336 | [self.menu addItem:loadingMenuItem]; 337 | } 338 | } 339 | 340 | NSMenuItem *checkForUpdatesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check For Updates…", @"") action:@selector(checkForUpdates:) keyEquivalent:@""]; 341 | [self.menu addItem:checkForUpdatesItem]; 342 | 343 | NSMenuItem *openAtLoginItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Launch %@ At Login", @""), [self appBundleName]] action:@selector(openAtLoginToggled:) keyEquivalent:@""]; 344 | openAtLoginItem.state = [self startAtLogin]; 345 | [self.menu addItem:openAtLoginItem]; 346 | 347 | NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Quit %@", @""), [self appBundleName]] action:@selector(quit:) keyEquivalent:@"q"]; 348 | [self.menu addItem:quitMenuItem]; 349 | 350 | self.lastUpdateDate = [NSDate date]; 351 | } 352 | 353 | - (void)quit:(id)sender 354 | { 355 | [NSApp terminate:self]; 356 | } 357 | 358 | #pragma mark - Launch at login 359 | 360 | - (NSURL *)appURL 361 | { 362 | return [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; 363 | } 364 | 365 | - (BOOL)startAtLogin 366 | { 367 | return [JPLaunchAtLoginManager willStartAtLogin:HELPER_BUNDLE_ID]; 368 | } 369 | 370 | - (void)setStartAtLogin:(BOOL)enabled 371 | { 372 | [self willChangeValueForKey:@"startAtLogin"]; 373 | [JPLaunchAtLoginManager setStartAtLogin:HELPER_BUNDLE_ID enabled:enabled]; 374 | [self didChangeValueForKey:@"startAtLogin"]; 375 | } 376 | 377 | #pragma mark - Menu delegate 378 | 379 | - (void)menuWillOpen:(NSMenu *)menu 380 | { 381 | [self updateUserInterface]; 382 | } 383 | 384 | #pragma mark - Getters 385 | 386 | - (NSMenu *)menu 387 | { 388 | if (!_menu) { 389 | _menu = [[NSMenu alloc] init]; 390 | _menu.delegate = self; 391 | } 392 | return _menu; 393 | } 394 | 395 | @end 396 | -------------------------------------------------------------------------------- /CloudyTabs/JPCloudTabsDBReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // JPCloudTabsDBReader.h 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 9/10/17. 6 | // Copyright © 2017 Josh Parnham. All rights reserved. 7 | // 8 | 9 | @interface JPCloudTabsDBReader: NSObject 10 | 11 | - (BOOL)permissionsToReadFile; 12 | - (NSString *_Nonnull)cloudTabsDBFile; 13 | - (void)fetchTabData:(void (^_Nonnull)(NSArray *_Nullable))completionHandler; 14 | - (void)fetchDatabaseModificationDate:(void (^_Nonnull)(NSDate *_Nullable))completionHandler; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /CloudyTabs/JPCloudTabsDBReader.m: -------------------------------------------------------------------------------- 1 | // 2 | // JPCloudTabsDBReader.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 9/10/17. 6 | // Copyright © 2017 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "JPCloudTabsDBReader.h" 12 | 13 | #import "NSData+ZlibInflate.h" 14 | 15 | @interface JPCloudTabsDBReader () 16 | 17 | @property (strong, nonatomic) FMDatabase *cloudTabsDatabase; 18 | 19 | @end 20 | 21 | @implementation JPCloudTabsDBReader 22 | 23 | - (NSString *)cloudTabsDBFile { 24 | return [[JPCloudTabsDBReader safariLibraryDirectory] stringByAppendingPathComponent:@"CloudTabs.db"]; 25 | } 26 | 27 | - (BOOL)permissionsToReadFile { 28 | NSString *filePath = [self cloudTabsDBFile]; 29 | 30 | BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filePath]; 31 | NSData *data = [NSData dataWithContentsOfFile:filePath]; 32 | 33 | return fileExists && data != nil; 34 | } 35 | 36 | - (void)fetchTabData:(void (^_Nonnull)(NSArray *_Nullable))completionHandler { 37 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 38 | 39 | NSMutableArray *items = [[NSMutableArray alloc] init]; 40 | 41 | for (NSString *deviceID in self.deviceIDs) { 42 | NSString *name = [self deviceNameForID:deviceID]; 43 | 44 | NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init]; 45 | [dictionary setValue:name forKey:@"DeviceName"]; 46 | 47 | [dictionary setValue:[self tabsForDeviceID:deviceID] forKey:@"Tabs"]; 48 | 49 | [items addObject:dictionary.copy]; 50 | } 51 | 52 | if (items.count > 0) { 53 | completionHandler(items.copy); 54 | } else { 55 | completionHandler(nil); 56 | } 57 | }); 58 | } 59 | 60 | - (void)fetchDatabaseModificationDate:(void (^_Nonnull)(NSDate *_Nullable))completionHandler { 61 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 62 | completionHandler(self.modificationDate); 63 | }); 64 | } 65 | 66 | # pragma mark - Private 67 | 68 | + (NSString *)safariLibraryDirectory { 69 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); 70 | return [[paths firstObject] stringByAppendingPathComponent:@"Safari"]; 71 | } 72 | 73 | - (FMDatabase *)cloudTabsDatabase { 74 | if (!_cloudTabsDatabase) { 75 | FMDatabase *db = [FMDatabase databaseWithPath:[self cloudTabsDBFile]]; 76 | 77 | if ([db open]) { 78 | return db; 79 | } else { 80 | db = nil; 81 | return nil; 82 | } 83 | } 84 | return _cloudTabsDatabase; 85 | } 86 | 87 | - (NSArray *)deviceIDs { 88 | if (self.cloudTabsDatabase == nil) { 89 | return nil; 90 | } 91 | 92 | NSMutableArray *deviceIDs = [NSMutableArray new]; 93 | 94 | FMResultSet *resultSet = [self.cloudTabsDatabase executeQuery:@"SELECT device_uuid from cloud_tab_devices GROUP BY device_uuid"]; 95 | while ([resultSet next]) { 96 | [deviceIDs addObject:[resultSet stringForColumn:@"device_uuid"]]; 97 | } 98 | 99 | return [deviceIDs copy]; 100 | } 101 | 102 | - (NSString *)deviceNameForID:(NSString *)deviceID { 103 | if (self.cloudTabsDatabase == nil) { 104 | return nil; 105 | } 106 | 107 | FMResultSet *resultSet = [self.cloudTabsDatabase executeQuery:@"SELECT device_name from cloud_tab_devices WHERE device_uuid = ?", deviceID]; 108 | while ([resultSet next]) { 109 | return [resultSet stringForColumn:@"device_name"]; 110 | } 111 | 112 | return nil; 113 | } 114 | 115 | - (NSArray *)tabsForDeviceID:(NSString *)deviceID { 116 | if (self.cloudTabsDatabase == nil) { 117 | return nil; 118 | } 119 | 120 | NSMutableArray *tabs = [NSMutableArray new]; 121 | 122 | NSString *query = @"SELECT url, title, position from cloud_tabs WHERE device_uuid = ?"; 123 | FMResultSet *resultSet = [self.cloudTabsDatabase executeQuery:query, deviceID]; 124 | while ([resultSet next]) { 125 | NSString *URL = [resultSet stringForColumn:@"url"]; 126 | NSString *title = [resultSet stringForColumn:@"title"]; 127 | 128 | NSData *position = [resultSet dataForColumn:@"position"]; 129 | NSNumber *sortValue = [self sortValueForData:position] ?: [NSNumber numberWithInt:0]; 130 | 131 | if (URL != nil && title != nil) { 132 | [tabs addObject:@{@"URL": URL, @"Title": (title ? title : URL), @"SortValue": sortValue}]; 133 | } 134 | } 135 | 136 | [tabs sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"SortValue" ascending:YES]]]; 137 | 138 | return [tabs copy]; 139 | } 140 | 141 | - (NSDate * _Nullable)modificationDate { 142 | NSURL *preferencesURL = [NSURL fileURLWithPath:[[JPCloudTabsDBReader safariLibraryDirectory] stringByAppendingPathComponent:@"CloudTabs.db"]]; 143 | 144 | NSDate *modificationDate; 145 | if ([preferencesURL getResourceValue:&modificationDate forKey:NSURLContentModificationDateKey error:nil]) { 146 | return modificationDate; 147 | } 148 | else { 149 | return nil; 150 | } 151 | } 152 | 153 | - (NSNumber *)sortValueForData:(NSData *)data { 154 | NSData *inflated = [data ZlibInflate]; 155 | 156 | NSError *error; 157 | NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:inflated options:NSJSONReadingMutableContainers error:&error]; 158 | if (error != nil) { 159 | return nil; 160 | } 161 | 162 | NSArray *sortValues = jsonObject[@"sortValues"]; 163 | NSDictionary *sortValueDictionary = sortValues.lastObject; 164 | if (sortValueDictionary == nil) { 165 | return nil; 166 | } 167 | 168 | NSNumber *sortValue = sortValueDictionary[@"sortValue"]; 169 | if ([sortValue isKindOfClass:[NSNumber class]]) { 170 | return sortValueDictionary[@"sortValue"]; 171 | } 172 | 173 | return nil; 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /CloudyTabs/JPLaunchAtLoginManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // JPLaunchAtLoginManager.h 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 30/8/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | @interface JPLaunchAtLoginManager : NSObject 10 | 11 | + (BOOL)willStartAtLogin:(NSString *)helperBundleId; 12 | + (void)setStartAtLogin:(NSString *)helperBundleId enabled:(BOOL)enabled; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /CloudyTabs/JPLaunchAtLoginManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // JPLaunchAtLoginManager.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 30/8/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "JPLaunchAtLoginManager.h" 10 | 11 | #import 12 | 13 | @implementation JPLaunchAtLoginManager 14 | 15 | + (BOOL)willStartAtLogin:(NSString *)helperBundleId 16 | { 17 | // Despite being deprecated, still the preffered API 18 | // https://github.com/alexzielenski/StartAtLoginController/issues/12#issuecomment-307525807 19 | #pragma clang diagnostic push 20 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 21 | CFArrayRef jobDictionariesRef = SMCopyAllJobDictionaries(kSMDomainUserLaunchd); 22 | #pragma clang diagnostic pop 23 | 24 | NSArray *jobDictionaries = CFBridgingRelease(jobDictionariesRef); 25 | 26 | if (jobDictionaries == nil) { 27 | return false; 28 | } 29 | 30 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"Label = %@", helperBundleId]; 31 | NSArray *filterdArray = [jobDictionaries filteredArrayUsingPredicate:predicate]; 32 | if (filterdArray.count == 0) { 33 | return false; 34 | } 35 | 36 | NSDictionary *launchItem = filterdArray[0]; 37 | return [[launchItem objectForKey:@"OnDemand"] boolValue]; 38 | } 39 | 40 | + (void)setStartAtLogin:(NSString *)helperBundleId enabled:(BOOL)enabled 41 | { 42 | SMLoginItemSetEnabled((__bridge CFStringRef)(helperBundleId), enabled); 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /CloudyTabs/JPLoadingMenuItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // JPLoadingMenuItem.h 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 1/10/18. 6 | // Copyright © 2018 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface JPLoadingMenuItem : NSMenuItem 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CloudyTabs/JPLoadingMenuItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // JPLoadingMenuItem.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 1/10/18. 6 | // Copyright © 2018 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "JPLoadingMenuItem.h" 10 | 11 | @implementation JPLoadingMenuItem 12 | 13 | - (instancetype)initWithTitle:(NSString *)string action:(SEL)selector keyEquivalent:(NSString *)charCode { 14 | self = [super initWithTitle:string action:selector keyEquivalent:charCode]; 15 | if (self) { 16 | NSProgressIndicator *progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, 20, 20)]; 17 | progressIndicator.bezeled = NO; 18 | progressIndicator.translatesAutoresizingMaskIntoConstraints = NO; 19 | progressIndicator.controlSize = NSControlSizeSmall; 20 | progressIndicator.style = NSProgressIndicatorSpinningStyle; 21 | [progressIndicator sizeToFit]; 22 | self.view = progressIndicator; 23 | 24 | [progressIndicator startAnimation:self]; 25 | } 26 | return self; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /CloudyTabs/JPReadingListReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // JPReadingListReader.h 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 29/9/18. 6 | // Copyright © 2018 Josh Parnham. All rights reserved. 7 | // 8 | 9 | @interface JPReadingListReader : NSObject 10 | 11 | - (NSString *_Nullable)syncedBookmarksFile; 12 | - (void)fetchReadingListItems:(void (^_Nonnull)(NSArray *_Nullable))completionHandler; 13 | - (void)fetchReadingListModificationDate:(void (^_Nonnull)(NSDate *_Nullable))completionHandler; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CloudyTabs/JPReadingListReader.m: -------------------------------------------------------------------------------- 1 | // 2 | // JPReadingListReader.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 29/9/18. 6 | // Copyright © 2018 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "JPReadingListReader.h" 10 | 11 | @implementation JPReadingListReader 12 | 13 | - (void)fetchReadingListItems:(void (^_Nonnull)(NSArray *_Nullable))completionHandler { 14 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 15 | NSArray *readingListItems = [self readingListBookmarks]; 16 | completionHandler(readingListItems); 17 | }); 18 | } 19 | 20 | - (void)fetchReadingListModificationDate:(void (^_Nonnull)(NSDate *_Nullable))completionHandler { 21 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 22 | NSDate *modificationDate = [self syncedBookmarksModificationDate]; 23 | completionHandler(modificationDate); 24 | }); 25 | } 26 | 27 | - (NSString *)syncedBookmarksFile { 28 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); 29 | NSString *syncedPreferencesPath = [paths[0] stringByAppendingPathComponent:@"Safari"]; 30 | return [syncedPreferencesPath stringByAppendingPathComponent:@"Bookmarks.plist"]; 31 | } 32 | 33 | 34 | # pragma mark - Private 35 | 36 | - (NSArray *_Nullable)readingListBookmarks { 37 | NSDictionary *syncedBookmarksDictionary = [[NSDictionary alloc] initWithContentsOfFile:[self syncedBookmarksFile]]; 38 | 39 | if (syncedBookmarksDictionary == nil) { 40 | return nil; 41 | } 42 | 43 | NSArray *bookmarks = syncedBookmarksDictionary[@"Children"]; 44 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"Title = 'com.apple.ReadingList'"]; 45 | NSDictionary* readingList = [[bookmarks filteredArrayUsingPredicate:predicate] firstObject]; 46 | 47 | if (readingList == nil) { 48 | return nil; 49 | } 50 | 51 | return readingList[@"Children"]; 52 | } 53 | 54 | - (NSDate *)syncedBookmarksModificationDate { 55 | NSDate *modificationDate; 56 | [[NSURL fileURLWithPath:[self syncedBookmarksFile]] getResourceValue:&modificationDate forKey:NSURLContentModificationDateKey error:nil]; 57 | return modificationDate; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /CloudyTabs/JPTabCSVExporter.h: -------------------------------------------------------------------------------- 1 | // 2 | // JPTabCSVExporter.h 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 13/9/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface JPTabCSVExporter : NSObject 14 | 15 | + (void)exportTabs:(NSArray *)tabData deviceName:(NSString *)deviceName; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /CloudyTabs/JPTabCSVExporter.m: -------------------------------------------------------------------------------- 1 | // 2 | // JPTabCSVExporter.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 13/9/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "JPTabCSVExporter.h" 10 | 11 | #import "CHCSVParser.h" 12 | 13 | @implementation JPTabCSVExporter 14 | 15 | + (void)exportTabs:(NSArray *)tabData deviceName:(NSString *)deviceName { 16 | NSString *temporaryFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]]; 17 | 18 | CHCSVWriter *writer = [[CHCSVWriter alloc] initForWritingToCSVFile:temporaryFilePath]; 19 | [writer writeLineOfFields:@[@"URL", @"Title"]]; 20 | 21 | for (NSDictionary *tab in tabData) { 22 | [writer writeLineWithDictionary:tab]; 23 | } 24 | 25 | NSSavePanel *savePanel = [NSSavePanel savePanel]; 26 | savePanel.nameFieldStringValue = [deviceName stringByAppendingString:@".csv"]; 27 | savePanel.allowedFileTypes = @[@"csv"]; 28 | [savePanel beginWithCompletionHandler:^(NSModalResponse result) { 29 | if (result == NSModalResponseOK) { 30 | NSURL *url = savePanel.URL; 31 | 32 | if (url == nil) { 33 | return; 34 | } 35 | 36 | NSURL *temporaryFileURL = [NSURL fileURLWithPath:temporaryFilePath isDirectory:NO]; 37 | 38 | NSFileManager *fileManager = [NSFileManager defaultManager]; 39 | [fileManager copyItemAtURL:temporaryFileURL toURL:url error:nil]; 40 | } 41 | }]; 42 | [savePanel orderFrontRegardless]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /CloudyTabs/JPUserDefaultsController.h: -------------------------------------------------------------------------------- 1 | // 2 | // JPUserDefaultsController.h 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 11/2/18. 6 | // Copyright © 2018 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface JPUserDefaultsController : NSObject 12 | 13 | + (void)registerUserDefaults; 14 | 15 | + (BOOL)shouldListAllDevices; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CloudyTabs/JPUserDefaultsController.m: -------------------------------------------------------------------------------- 1 | // 2 | // JPUserDefaultsController.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 11/2/18. 6 | // Copyright © 2018 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "JPUserDefaultsController.h" 10 | 11 | @implementation JPUserDefaultsController 12 | 13 | static NSString *const LIST_ALL_DEVICES_KEY = @"ListAllDevices"; 14 | 15 | + (void)registerUserDefaults { 16 | NSDictionary *defaultValues = @{LIST_ALL_DEVICES_KEY: @NO}; 17 | [[NSUserDefaults standardUserDefaults] registerDefaults:defaultValues]; 18 | } 19 | 20 | + (BOOL)shouldListAllDevices { 21 | NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults]; 22 | 23 | return [standardUserDefaults boolForKey:LIST_ALL_DEVICES_KEY]; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /CloudyTabs/NSData+ZlibInflate.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+ZlibInflate.h 3 | // CloudyTabs 4 | // 5 | // Based off NSData+Compression from CocoaGit 6 | // https://github.com/geoffgarside/cocoagit/blob/master/Source/Categories/NSData+Compression.h 7 | // Copyright 2008 ManicPanda.com. All rights reserved. 8 | // 9 | 10 | #import 11 | 12 | @interface NSData (ZlibInflate) 13 | 14 | - (NSData *)ZlibInflate; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /CloudyTabs/NSData+ZlibInflate.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+ZlibInflate.m 3 | // CloudyTabs 4 | // 5 | // Based off NSData+Compression from CocoaGit 6 | // https://github.com/geoffgarside/cocoagit/blob/master/Source/Categories/NSData+Compression.m 7 | // Copyright 2008 ManicPanda.com. All rights reserved. 8 | // 9 | 10 | #import "NSData+ZlibInflate.h" 11 | #include 12 | 13 | @implementation NSData (ZlibInflate) 14 | 15 | - (NSData *)ZlibInflate { 16 | NSUInteger length = self.length; 17 | 18 | if (length == 0) { 19 | return self; 20 | } 21 | 22 | NSUInteger halfLength = length / 2; 23 | 24 | NSMutableData *decompressed = [NSMutableData dataWithLength:length + halfLength]; 25 | BOOL done = NO; 26 | int status; 27 | 28 | z_stream strm; 29 | strm.next_in = (Bytef *)self.bytes; 30 | strm.avail_in = (int)length; 31 | strm.total_out = 0; 32 | strm.zalloc = Z_NULL; 33 | strm.zfree = Z_NULL; 34 | 35 | if (inflateInit (&strm) != Z_OK) { 36 | return nil; 37 | } 38 | 39 | while (!done) { 40 | // Make sure we have enough room and reset the lengths. 41 | if (strm.total_out >= decompressed.length) { 42 | [decompressed increaseLengthBy: halfLength]; 43 | } 44 | strm.next_out = decompressed.mutableBytes + strm.total_out; 45 | strm.avail_out = (int)decompressed.length - (int)strm.total_out; 46 | 47 | // Inflate another chunk. 48 | status = inflate (&strm, Z_SYNC_FLUSH); 49 | if (status == Z_STREAM_END) { 50 | done = YES; 51 | } else if (status != Z_OK) { 52 | break; 53 | } 54 | } 55 | if (inflateEnd (&strm) != Z_OK) { 56 | return nil; 57 | } 58 | 59 | // Set real length. 60 | if (done) { 61 | [decompressed setLength: strm.total_out]; 62 | return [NSData dataWithData: decompressed]; 63 | } else { 64 | return nil; 65 | } 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /CloudyTabs/NSMenuItem+ItemCreation.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMenuItem+ItemCreation.h 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 6/9/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSMenuItem (ItemCreation) 12 | 13 | + (NSMenuItem *)menuItemWithTitle:(NSString *)tabTitle URLPath:(NSString *)URLPath action:(SEL)action; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CloudyTabs/NSMenuItem+ItemCreation.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMenuItem+ItemCreation.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 6/9/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "NSMenuItem+ItemCreation.h" 10 | #import "NSURL+Extensions.h" 11 | #import "DSFavIconManager.h" 12 | 13 | @implementation NSMenuItem (ItemCreation) 14 | 15 | const NSSize ICON_SIZE = {19, 19}; 16 | 17 | + (NSMenuItem *)menuItemWithTitle:(NSString *)tabTitle URLPath:(NSString *)URLPath action:(SEL)action { 18 | NSMenuItem *tabMenuItem = [[NSMenuItem alloc] initWithTitle:tabTitle action:action keyEquivalent:@""]; 19 | 20 | NSURL *URL = [NSURL decodeURL:URLPath]; 21 | tabMenuItem.representedObject = URL; 22 | tabMenuItem.toolTip = URL.relativeString; 23 | if (URL.host != nil) { 24 | tabMenuItem.image = [[DSFavIconManager sharedInstance] iconForURL:tabMenuItem.representedObject downloadHandler:^(NSImage *image) { 25 | image.size = ICON_SIZE; 26 | tabMenuItem.image = image; 27 | }]; 28 | } else { 29 | tabMenuItem.image = [DSFavIconManager sharedInstance].placeholder; 30 | } 31 | tabMenuItem.image.size = ICON_SIZE; 32 | 33 | return tabMenuItem; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /CloudyTabs/NSURL+Extensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+DecodeURL.h 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 28/10/17. 6 | // Copyright © 2017 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSURL (Extensions) 12 | 13 | + (NSURL *)decodeURL:(NSString *)urlString; 14 | + (NSURL *)privacyAllFilesSystemPreferencesURL; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /CloudyTabs/NSURL+Extensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+Extensions.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 28/10/17. 6 | // Copyright © 2017 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "NSURL+Extensions.h" 10 | 11 | @implementation NSURL (Extensions) 12 | 13 | + (NSURL *)decodeURL:(NSString *)urlString { 14 | // See dot point 4 under "NSURL Deprecations" in "Foundation Release Notes for OS X v10.11" 15 | if ([NSURL respondsToSelector:@selector(URLWithDataRepresentation:relativeToURL:)]) { 16 | // Modern NSURL API available 17 | return [NSURL URLWithDataRepresentation:[urlString dataUsingEncoding:NSUTF8StringEncoding] relativeToURL:nil]; 18 | } 19 | else { 20 | return [self legacyDecodeURL:urlString]; 21 | } 22 | } 23 | 24 | + (NSURL *)legacyDecodeURL:(NSString *)urlString { 25 | // Modern NSURL API not available, fall back to CoreFoundation implementation 26 | NSData *urlData = [urlString dataUsingEncoding:NSUTF8StringEncoding]; 27 | CFURLRef urlRef = CFURLCreateWithBytes(kCFAllocatorSystemDefault, (const UInt8 *)urlData.bytes, urlData.length, kCFStringEncodingUTF8, NULL); 28 | if (!urlRef) { 29 | // Fallback to using ISO Latin encoding 30 | urlRef = CFURLCreateWithBytes(kCFAllocatorSystemDefault, (const UInt8 *)urlData.bytes, urlData.length, kCFStringEncodingISOLatin1, NULL); 31 | } 32 | return (__bridge NSURL *)urlRef; 33 | } 34 | 35 | + (NSURL *)privacyAllFilesSystemPreferencesURL { 36 | return [NSURL URLWithString:@"x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /CloudyTabs/ca.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/ca.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by Pere Montpeó 6 | */ 7 | 8 | "Open All Tabs From" = "Obrir totes les pestanyes de"; 9 | "Launch %@ At Login" = "Obrir %@ a l'iniciar sessió"; 10 | "Quit %@" = "Sortir de %@"; 11 | "%@\nChanges Last Detected: %@" = "%@\nÚltims canvis detectats: %@"; 12 | "Reading List" = "Llista de Lectura"; 13 | -------------------------------------------------------------------------------- /CloudyTabs/de.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by Nick Pfadt 6 | */ 7 | 8 | "Open All Tabs From" = "Öffne alle Tabs von"; 9 | "Launch %@ At Login" = "%@ beim Anmelden öffnen"; 10 | "Quit %@" = "%@ beenden"; 11 | "%@\nChanges Last Detected: %@" = "%@\nÄnderungen zuletzt festgestellt: %@"; 12 | "Reading List" = "Leseliste"; 13 | -------------------------------------------------------------------------------- /CloudyTabs/dsa_pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBtzCCASwGByqGSM44BAEwggEfAoGBAM1Hhqo05RWBELVb2fIcUynrH+tqD2+S 3 | a2ulx90CICmuXkWW9ddrgi8USfoat3EioVMxbl2JjM4t+3KzHZw/0XMoGafqKXIv 4 | LDOTqRMlPRKB0cFGFSU3wlm5ii6cMHHiAeRfF9gqeWcdeMIk81Rlim2Vi+6yPz4R 5 | VgackC7sBrwdAhUA4px+kzXllOlWAShwGd7umY8kyH8CgYEAillHznZKi8ke/vyV 6 | 3A6nmpkYK6vMbO3o6xMP5uuVswL0ch+0K9993lkfjcFX5vueK9Aa4cZG4XB+8qyo 7 | zzAbC+7U5m9t/gF8JXnRCHBG5UgSACQTaNfV0hCjeQ9iB+M8J6JuthQGWM58UfGj 8 | lXV7THLTm37EcguNflXr6REOI4QDgYQAAoGASdJ2yqWzS7b5COgDeqT0M2OvZXxh 9 | +nQ2E6f3wxrPflgnuqbzIrCo0cQ/hyJXC+VSgvilEbHV+FYKdE3VMxj58zgOsJOB 10 | lmH25XEdzDlNxNWb5qFF8rUsv5b5yvOWMngjyYTraFywrwno0X0AjWLm4rUpnZvg 11 | aLAQL34el4W0Pvk= 12 | -----END PUBLIC KEY----- 13 | -------------------------------------------------------------------------------- /CloudyTabs/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by Josh Parnham 6 | */ 7 | 8 | "Open All Tabs From" = "Open All Tabs From"; 9 | "Export All Tabs From" = "Export All Tabs From"; 10 | "Launch %@ At Login" = "Launch %@ At Login"; 11 | "Quit %@" = "Quit %@"; 12 | "%@\nChanges Last Detected: %@" = "%@\nChanges Last Detected: %@"; 13 | "Reading List" = "Reading List"; 14 | "Requires Full Disk Access…" = "Requires Full Disk Access…"; 15 | "Enable Full Disk Access" = "Enable Full Disk Access"; 16 | "CloudyTabs requies Full Disk Access to access your Safari data. Click \"Enable\" to open System Prefences and then click the checkbox next to \"CloudyTabs\"." = "CloudyTabs requies Full Disk Access to access your Safari data. Click \"Enable\" to open System Prefences and then click the checkbox next to \"CloudyTabs\"."; 17 | "Enable" = "Enable"; 18 | "Cancel" = "Cancel"; 19 | "Check For Updates…" = "Check For Updates…"; 20 | -------------------------------------------------------------------------------- /CloudyTabs/es.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by Pere Montpeó 6 | */ 7 | 8 | "Open All Tabs From" = "Abrir todas las pestañas de"; 9 | "Launch %@ At Login" = "Abrir %@ al arrancar"; 10 | "Quit %@" = "Salir de %@"; 11 | "%@\nChanges Last Detected: %@" = "%@\nÚltimos cambios detectados: %@"; 12 | "Reading List" = "Lista de Lectura"; 13 | -------------------------------------------------------------------------------- /CloudyTabs/fr.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by @yogoo 6 | */ 7 | 8 | "Open All Tabs From" = "Ouvrir tous les onglets de"; 9 | "Launch %@ At Login" = "Ouvrir %@ à l'ouverture de session"; 10 | "Quit %@" = "Quitter %@"; 11 | "%@\nChanges Last Detected: %@" = "%@\nDerniers changements détectés : %@"; 12 | "Reading List" = "Liste de lecture"; 13 | -------------------------------------------------------------------------------- /CloudyTabs/it.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by David Gasperoni 6 | */ 7 | 8 | "Open All Tabs From" = "Apri tutte le schede da"; 9 | "Launch %@ At Login" = "Apri %@ al login"; 10 | "Quit %@" = "Chiudi %@"; 11 | "%@\nChanges Last Detected: %@" = "%@\nModifiche rilevate: %@"; 12 | "Reading List" = "Elenco lettura"; 13 | -------------------------------------------------------------------------------- /CloudyTabs/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CloudyTabs 4 | // 5 | // Created by Josh Parnham on 3/03/2014. 6 | // Copyright (c) 2014 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "JPAppDelegate.h" 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | @autoreleasepool { 14 | NSApplication * application = [NSApplication sharedApplication]; 15 | 16 | JPAppDelegate * appDelegate = [[JPAppDelegate alloc] init]; 17 | 18 | [application setDelegate:appDelegate]; 19 | [application run]; 20 | } 21 | 22 | return EXIT_SUCCESS; 23 | } 24 | -------------------------------------------------------------------------------- /CloudyTabs/nl.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/nl.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by @makruiten 6 | */ 7 | 8 | "Open All Tabs From" = "Open Alle Tabs Van"; 9 | "Launch %@ At Login" = "Open %@ Bij Inloggen"; 10 | "Quit %@" = "Stop %@"; 11 | "%@\nChanges Last Detected: %@" = "%@\nLaatst Gedetecteerde Wijzigingen: %@"; 12 | "Reading List" = "Leeslijst"; 13 | -------------------------------------------------------------------------------- /CloudyTabs/pt_BR.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/pt_BR.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by Sergio Moura 6 | */ 7 | 8 | "Open All Tabs From" = "Abrir Todas As Abas De"; 9 | "Launch %@ At Login" = "Inicializar %@ Ao Logar"; 10 | "Quit %@" = "Sair do %@"; 11 | "%@\nChanges Last Detected: %@" = "%@\nÚltimas Alterações Detectadas: %@"; 12 | "Reading List" = "Lista de Leitura"; 13 | -------------------------------------------------------------------------------- /CloudyTabs/ru.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by Josh Parnham 6 | */ 7 | 8 | "Open All Tabs From" = "Открыть все вкладки, которые есть на"; 9 | "Launch %@ At Login" = "Запускать %@ при входе в систему"; 10 | "Quit %@" = "Завершить %@"; 11 | "%@\nChanges Last Detected: %@" = "%@\nИзменений обнаружено по сравнению с: %@"; 12 | "Reading List" = "Список для чтения"; 13 | -------------------------------------------------------------------------------- /CloudyTabs/zh_CN.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/zh_CN.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by @izhaomin 6 | */ 7 | 8 | "Open All Tabs From" = "打开该设备上的所有标签页"; 9 | "Launch %@ At Login" = "登录时自动启动%@"; 10 | "Quit %@" = "退出%@"; 11 | "%@\nChanges Last Detected: %@" = "%@\n最近更改: %@"; 12 | "Reading List" = "阅读列表"; 13 | -------------------------------------------------------------------------------- /CloudyTabs/zh_TW.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CloudyTabs/zh_TW.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | CloudyTabs 4 | 5 | Contributed by @izhaomin 6 | */ 7 | 8 | "Open All Tabs From" = "打開該設備上的所有標籤頁"; 9 | "Launch %@ At Login" = "登錄時自動啟動%@"; 10 | "Quit %@" = "退出%@"; 11 | "%@\nChanges Last Detected: %@" = "%@\n最近更改: %@"; 12 | "Reading List" = "閱讀列表"; 13 | -------------------------------------------------------------------------------- /CloudyTabsHelper/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CloudyTabsHelper 4 | // 5 | // Created by Josh Parnham on 30/8/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CloudyTabsHelper/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CloudyTabsHelper 4 | // 5 | // Created by Josh Parnham on 30/8/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "NSBundle+ApplicationPath.h" 11 | 12 | @implementation AppDelegate 13 | 14 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 15 | NSString *mainApplicationPath = [[NSBundle mainBundle] mainApplicationPath]; 16 | [[NSWorkspace sharedWorkspace] launchApplication:mainApplicationPath]; 17 | [NSApp terminate:nil]; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /CloudyTabsHelper/CloudyTabsHelper.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CloudyTabsHelper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSBackgroundOnly 26 | 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | NSHumanReadableCopyright 30 | Copyright © 2020 Josh Parnham. All rights reserved. 31 | NSPrincipalClass 32 | NSApplication 33 | NSSupportsAutomaticTermination 34 | 35 | NSSupportsSuddenTermination 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /CloudyTabsHelper/NSBundle+ApplicationPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+ApplicationPath.h 3 | // CloudyTabsHelper 4 | // 5 | // Created by Josh Parnham on 30/8/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSBundle (ApplicationPath) 14 | 15 | - (NSString *)mainApplicationPath; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /CloudyTabsHelper/NSBundle+ApplicationPath.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+ApplicationPath.m 3 | // CloudyTabsHelper 4 | // 5 | // Created by Josh Parnham on 30/8/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import "NSBundle+ApplicationPath.h" 10 | 11 | @implementation NSBundle (ApplicationPath) 12 | 13 | - (NSString *)mainApplicationPath { 14 | NSArray *pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents]; 15 | NSArray *applicationPathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 4)]; 16 | NSString *path = [NSString pathWithComponents:applicationPathComponents]; 17 | return path; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /CloudyTabsHelper/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CloudyTabsHelper 4 | // 5 | // Created by Josh Parnham on 30/8/20. 6 | // Copyright © 2020 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | @autoreleasepool { 13 | // Setup code that might create autoreleased objects goes here. 14 | } 15 | return NSApplicationMain(argc, argv); 16 | } 17 | -------------------------------------------------------------------------------- /CloudyTabsTests/CloudyTabsTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CloudyTabsTests.m 3 | // CloudyTabsTests 4 | // 5 | // Created by Josh Parnham on 28/10/17. 6 | // Copyright © 2017 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CloudyTabsTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation CloudyTabsTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /CloudyTabsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CloudyTabsTests/NSURL+DecodeTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CloudyTabsTests.m 3 | // CloudyTabsTests 4 | // 5 | // Created by Josh Parnham on 28/10/17. 6 | // Copyright © 2017 Josh Parnham. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "NSURL+DecodeURL.h" 12 | 13 | @interface NSURL (DecodeTesting) 14 | 15 | + (NSURL *)legacyDecodeURL:(NSString *)urlString; 16 | 17 | @end 18 | 19 | @interface NSURLDecodeURLTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation NSURLDecodeURLTests 24 | 25 | - (void)testMaintainsHashEncoding { 26 | NSURL *finalURL = [NSURL URLWithString:@"https://www.nodebeginner.org/index-zh-cn.html#javascript-and-nodejs"]; 27 | 28 | XCTAssertEqualObjects(finalURL, [NSURL decodeURL:@"https://www.nodebeginner.org/index-zh-cn.html#javascript-and-nodejs"]); 29 | } 30 | 31 | - (void)testEncodedURL { 32 | NSURL *finalURL = [NSURL URLWithString:@"http://%E2%9E%A1.ws/%E4%A8%B9"]; 33 | 34 | XCTAssertEqualObjects(finalURL, [NSURL decodeURL:@"http://➡.ws/䨹"]); 35 | } 36 | 37 | - (void)testEncodedPath { 38 | NSURL *finalURL = [NSURL URLWithString:@"https://www.101domain.com/%E4%B8%AD%E5%9B%BD.htm"]; 39 | 40 | XCTAssertEqualObjects(finalURL, [NSURL decodeURL:@"https://www.101domain.com/中国.htm"]); 41 | } 42 | 43 | #pragma mark - Legacy implementation tests 44 | 45 | - (void)testLegacyMaintainsHashEncoding { 46 | NSURL *finalURL = [NSURL URLWithString:@"https://www.nodebeginner.org/index-zh-cn.html#javascript-and-nodejs"]; 47 | 48 | XCTAssertEqualObjects(finalURL, [NSURL legacyDecodeURL:@"https://www.nodebeginner.org/index-zh-cn.html#javascript-and-nodejs"]); 49 | } 50 | 51 | - (void)testLegacyEncodedURL { 52 | NSURL *finalURL = [NSURL URLWithString:@"http://%E2%9E%A1.ws/%E4%A8%B9"]; 53 | 54 | XCTAssertEqualObjects(finalURL, [NSURL legacyDecodeURL:@"http://➡.ws/䨹"]); 55 | } 56 | 57 | - (void)testLegacyEncodedPath { 58 | NSURL *finalURL = [NSURL URLWithString:@"https://www.101domain.com/%E4%B8%AD%E5%9B%BD.htm"]; 59 | 60 | XCTAssertEqualObjects(finalURL, [NSURL legacyDecodeURL:@"https://www.101domain.com/中国.htm"]); 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudyTabs 2 | 3 | CloudyTabs is a simple menu bar application that lists your iCloud Tabs and Reading List. 4 | 5 | ![CloudyTabs](http://joshparnham.com/projects/cloudytabs/CloudyTabs.png) 6 | 7 | ## Installation 8 | 9 | * [Download CloudyTabs](https://github.com/josh-/CloudyTabs/releases/latest) and copy it to your Applications folder. 10 | 11 | > (Alternatively, CloudyTabs is installable from your shell through [Homebrew](http://brew.sh) – once you've [set up Homebrew-cask](https://github.com/phinze/homebrew-cask/blob/master/USAGE.md#getting-started)) you can simply type `brew cask install cloudytabs`) 12 | 13 | ## Usage 14 | 15 | To use CloudyTabs, open the app and select a tab from one of your devices. The tab's URL then opens it in your default browser (useful if like me, you use Safari on iOS and Chrome on macOS), Cmd(`⌘`)-Selecting a tab (or highlighting it and pressing Cmd(`⌘`)-Return(`⏎`)) opens the tab in the background. Opt(`⌥`)-Selecting a tab (or highlighting it and pressing Opt(`⌥`)-Return(`⏎`)) will copy the tab's URL. 16 | 17 | Typing the first few letters or a tab's title will jump to that particular tab. 18 | 19 | Hovering over the CloudyTabs menu bar icon displays a tooltip which lists the date that iCloud last updated the synced tabs database or Bookmarks `plist` (where CloudyTabs reads data from). 20 | 21 | ## Requirements 22 | 23 | * macOS 10.13 or later 24 | * An active iCloud account 25 | 26 | ## License 27 | 28 | The MIT License (MIT) 29 | 30 | Copyright (c) 2018 Josh Parnham 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in all 40 | copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 48 | SOFTWARE. 49 | --------------------------------------------------------------------------------