From e4b03eebc297d217dd3b17d73b53d94c8fd551cb Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 6 Apr 2020 17:05:47 -0500 Subject: [PATCH 001/108] Add FeedProvider project. --- .../Account/Account.xcodeproj/project.pbxproj | 8 +- .../Articles.xcodeproj/project.pbxproj | 2 +- .../project.pbxproj | 10 +- .../FeedProvider.xcodeproj/project.pbxproj | 258 ++++++++++++++++++ Frameworks/FeedProvider/Info.plist | 26 ++ .../SyncDatabase.xcodeproj/project.pbxproj | 2 +- NetNewsWire.xcodeproj/project.pbxproj | 36 +++ 7 files changed, 334 insertions(+), 8 deletions(-) create mode 100644 Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj create mode 100644 Frameworks/FeedProvider/Info.plist diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 38efd6984..38674a63f 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -33,6 +33,7 @@ 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; }; 510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; }; 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */; }; + 511076F5243BD96D00D97C8C /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 511076F4243BD96D00D97C8C /* FeedProvider.framework */; }; 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* FeedIdentifier.swift */; }; 512DD4CB2431000600C17B1F /* CKRecord+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512DD4CA2431000600C17B1F /* CKRecord+Extensions.swift */; }; 512DD4CD2431098700C17B1F /* CloudKitAccountZoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512DD4CC2431098700C17B1F /* CloudKitAccountZoneDelegate.swift */; }; @@ -268,6 +269,8 @@ 5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = ""; }; 510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = ""; }; 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadataFile.swift; sourceTree = ""; }; + 511076A3243BD33100D97C8C /* .framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = .framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 511076F4243BD96D00D97C8C /* FeedProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FeedProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 511B9803237CD4270028BCAA /* FeedIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIdentifier.swift; sourceTree = ""; }; 512DD4CA2431000600C17B1F /* CKRecord+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CKRecord+Extensions.swift"; sourceTree = ""; }; 512DD4CC2431098700C17B1F /* CloudKitAccountZoneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitAccountZoneDelegate.swift; sourceTree = ""; }; @@ -457,6 +460,7 @@ buildActionMask = 2147483647; files = ( 84EAC4822148CC6300F154AB /* RSDatabase.framework in Frameworks */, + 511076F5243BD96D00D97C8C /* FeedProvider.framework in Frameworks */, 844B2981210CE3BF004020B3 /* RSWeb.framework in Frameworks */, 841D4D722106B40A00DD04E6 /* Articles.framework in Frameworks */, 841D4D702106B40400DD04E6 /* ArticlesDatabase.framework in Frameworks */, @@ -657,6 +661,8 @@ 8469F80F1F6DC3C10084783E /* Frameworks */ = { isa = PBXGroup; children = ( + 511076F4243BD96D00D97C8C /* FeedProvider.framework */, + 511076A3243BD33100D97C8C /* .framework */, 51E148EB234B8FFC0004F7A5 /* SyncDatabase.framework */, 84EAC4812148CC6300F154AB /* RSDatabase.framework */, 844B2980210CE3BF004020B3 /* RSWeb.framework */, @@ -1068,7 +1074,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n"; + shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n\nif [ $? -ne 0 ]\nthen\n echo \"error: Build Setting were found in the project.pbxproj file. Most likely you didn't intend to change this file and should revert it.\"\n exit 1\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/Frameworks/Articles/Articles.xcodeproj/project.pbxproj b/Frameworks/Articles/Articles.xcodeproj/project.pbxproj index e1c634abe..27a1ea842 100644 --- a/Frameworks/Articles/Articles.xcodeproj/project.pbxproj +++ b/Frameworks/Articles/Articles.xcodeproj/project.pbxproj @@ -311,7 +311,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n"; + shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n\nif [ $? -ne 0 ]\nthen\n echo \"error: Build Setting were found in the project.pbxproj file. Most likely you didn't intend to change this file and should revert it.\"\n exit 1\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.xcodeproj/project.pbxproj b/Frameworks/ArticlesDatabase/ArticlesDatabase.xcodeproj/project.pbxproj index 055dd02a4..a1349177f 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.xcodeproj/project.pbxproj +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.xcodeproj/project.pbxproj @@ -364,14 +364,14 @@ TargetAttributes = { 844BEE361F0AB3AA004AB7CD = { CreatedOnToolsVersion = 8.3.2; - DevelopmentTeam = M8L2WTLA8W; + DevelopmentTeam = SHJK2V3AJG; LastSwiftMigration = 0830; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; 844BEE3F1F0AB3AB004AB7CD = { CreatedOnToolsVersion = 8.3.2; - DevelopmentTeam = M8L2WTLA8W; - ProvisioningStyle = Manual; + DevelopmentTeam = SHJK2V3AJG; + ProvisioningStyle = Automatic; }; }; }; @@ -519,7 +519,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n"; + shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n\nif [ $? -ne 0 ]\nthen\n echo \"error: Build Setting were found in the project.pbxproj file. Most likely you didn't intend to change this file and should revert it.\"\n exit 1\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj b/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj new file mode 100644 index 000000000..949eb244d --- /dev/null +++ b/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj @@ -0,0 +1,258 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 5110769B243BCF3A00D97C8C /* FeedProvider_target.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */; }; + 5110769C243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 51107697243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig */; }; + 5110769D243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 51107698243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig */; }; + 5110769E243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */; }; + 5110769F243BCF3A00D97C8C /* FeedProvider_project.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */; }; + 511076EE243BD82A00D97C8C /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 511076ED243BD82A00D97C8C /* Articles.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 5110765F243BCE0400D97C8C /* FeedProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FeedProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 51107663243BCE0400D97C8C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_target.xcconfig; sourceTree = ""; }; + 51107697243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project_test.xcconfig; sourceTree = ""; }; + 51107698243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project_release.xcconfig; sourceTree = ""; }; + 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project_debug.xcconfig; sourceTree = ""; }; + 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project.xcconfig; sourceTree = ""; }; + 511076ED243BD82A00D97C8C /* Articles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Articles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5110765C243BCE0400D97C8C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 511076EE243BD82A00D97C8C /* Articles.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 51107655243BCE0400D97C8C = { + isa = PBXGroup; + children = ( + 51107689243BCEB300D97C8C /* xcconfig */, + 51107663243BCE0400D97C8C /* Info.plist */, + 51107660243BCE0400D97C8C /* Products */, + 511076EC243BD82A00D97C8C /* Frameworks */, + ); + sourceTree = ""; + }; + 51107660243BCE0400D97C8C /* Products */ = { + isa = PBXGroup; + children = ( + 5110765F243BCE0400D97C8C /* FeedProvider.framework */, + ); + name = Products; + sourceTree = ""; + }; + 51107689243BCEB300D97C8C /* xcconfig */ = { + isa = PBXGroup; + children = ( + 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */, + 51107698243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig */, + 51107697243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig */, + 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */, + 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */, + ); + path = xcconfig; + sourceTree = ""; + }; + 511076EC243BD82A00D97C8C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 511076ED243BD82A00D97C8C /* Articles.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 5110765A243BCE0400D97C8C /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 5110765E243BCE0400D97C8C /* FeedProvider */ = { + isa = PBXNativeTarget; + buildConfigurationList = 51107667243BCE0400D97C8C /* Build configuration list for PBXNativeTarget "FeedProvider" */; + buildPhases = ( + 5110765A243BCE0400D97C8C /* Headers */, + 5110765B243BCE0400D97C8C /* Sources */, + 5110765C243BCE0400D97C8C /* Frameworks */, + 5110765D243BCE0400D97C8C /* Resources */, + 511076A2243BD2E600D97C8C /* Run Script: Verfiy No Build Settings */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FeedProvider; + productName = FeedProvider; + productReference = 5110765F243BCE0400D97C8C /* FeedProvider.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 51107656243BCE0400D97C8C /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1140; + ORGANIZATIONNAME = "Vincode, Inc"; + TargetAttributes = { + 5110765E243BCE0400D97C8C = { + CreatedOnToolsVersion = 11.4; + }; + }; + }; + buildConfigurationList = 51107659243BCE0400D97C8C /* Build configuration list for PBXProject "FeedProvider" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 51107655243BCE0400D97C8C; + productRefGroup = 51107660243BCE0400D97C8C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5110765E243BCE0400D97C8C /* FeedProvider */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5110765D243BCE0400D97C8C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5110769D243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig in Resources */, + 5110769F243BCF3A00D97C8C /* FeedProvider_project.xcconfig in Resources */, + 5110769E243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig in Resources */, + 5110769B243BCF3A00D97C8C /* FeedProvider_target.xcconfig in Resources */, + 5110769C243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 511076A2243BD2E600D97C8C /* Run Script: Verfiy No Build Settings */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script: Verfiy No Build Settings"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n\nif [ $? -ne 0 ]\nthen\n echo \"error: Build Setting were found in the project.pbxproj file. Most likely you didn't intend to change this file and should revert it.\"\n exit 1\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5110765B243BCE0400D97C8C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 51107665243BCE0400D97C8C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 51107666243BCE0400D97C8C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51107698243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 51107668243BCE0400D97C8C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 51107669243BCE0400D97C8C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 511076A0243BD20A00D97C8C /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51107697243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig */; + buildSettings = { + }; + name = Test; + }; + 511076A1243BD20A00D97C8C /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */; + buildSettings = { + }; + name = Test; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 51107659243BCE0400D97C8C /* Build configuration list for PBXProject "FeedProvider" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 51107665243BCE0400D97C8C /* Debug */, + 511076A0243BD20A00D97C8C /* Test */, + 51107666243BCE0400D97C8C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 51107667243BCE0400D97C8C /* Build configuration list for PBXNativeTarget "FeedProvider" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 51107668243BCE0400D97C8C /* Debug */, + 511076A1243BD20A00D97C8C /* Test */, + 51107669243BCE0400D97C8C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 51107656243BCE0400D97C8C /* Project object */; +} diff --git a/Frameworks/FeedProvider/Info.plist b/Frameworks/FeedProvider/Info.plist new file mode 100644 index 000000000..187bfef6d --- /dev/null +++ b/Frameworks/FeedProvider/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Ranchero Software, LLC. All rights reserved. + NSPrincipalClass + + + diff --git a/Frameworks/SyncDatabase/SyncDatabase.xcodeproj/project.pbxproj b/Frameworks/SyncDatabase/SyncDatabase.xcodeproj/project.pbxproj index c6786f2e8..635e293f3 100644 --- a/Frameworks/SyncDatabase/SyncDatabase.xcodeproj/project.pbxproj +++ b/Frameworks/SyncDatabase/SyncDatabase.xcodeproj/project.pbxproj @@ -185,7 +185,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n"; + shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n\nif [ $? -ne 0 ]\nthen\n echo \"error: Build Setting were found in the project.pbxproj file. Most likely you didn't intend to change this file and should revert it.\"\n exit 1\nfi\n\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index cfe77b8a8..573f28327 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -29,6 +29,10 @@ 5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D32375EEEF001ABC45 /* TimelinePreviewTableViewController.swift */; }; 5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D723763094001ABC45 /* TickMarkSlider.swift */; }; 51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; }; + 511076F7243BDA8100D97C8C /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; }; + 511076F8243BDA8200D97C8C /* FeedProvider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 511076F9243BDA9600D97C8C /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; }; + 511076FA243BDA9600D97C8C /* FeedProvider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */; }; 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; }; @@ -691,6 +695,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 51107671243BCE0500D97C8C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5110766A243BCE0400D97C8C /* FeedProvider.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 5110765F243BCE0400D97C8C; + remoteInfo = FeedProvider; + }; 5131463C235A7BBE00387FDC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */; @@ -1197,6 +1208,7 @@ 65ED4076235DEF6C0081F399 /* Account.framework in Embed Frameworks */, 65ED4077235DEF6C0081F399 /* Articles.framework in Embed Frameworks */, 65ED4078235DEF6C0081F399 /* RSParser.framework in Embed Frameworks */, + 511076FA243BDA9600D97C8C /* FeedProvider.framework in Embed Frameworks */, 65ED4079235DEF6C0081F399 /* SyncDatabase.framework in Embed Frameworks */, 65ED407A235DEF6C0081F399 /* RSCore.framework in Embed Frameworks */, ); @@ -1235,6 +1247,7 @@ dstSubfolderSpec = 10; files = ( 84C37FAA20DD8D9000CA8CF5 /* RSWeb.framework in Embed Frameworks */, + 511076F8243BDA8200D97C8C /* FeedProvider.framework in Embed Frameworks */, 84C37FC620DD8E1D00CA8CF5 /* RSDatabase.framework in Embed Frameworks */, 84C37FAE20DD8D9900CA8CF5 /* RSTree.framework in Embed Frameworks */, 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */, @@ -1274,6 +1287,7 @@ 5108F6D32375EEEF001ABC45 /* TimelinePreviewTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePreviewTableViewController.swift; sourceTree = ""; }; 5108F6D723763094001ABC45 /* TickMarkSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TickMarkSlider.swift; sourceTree = ""; }; 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; + 5110766A243BCE0400D97C8C /* FeedProvider.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FeedProvider.xcodeproj; path = Frameworks/FeedProvider/FeedProvider.xcodeproj; sourceTree = SOURCE_ROOT; }; 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorIconHeaderView.swift; sourceTree = ""; }; 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = ""; }; 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = ""; }; @@ -1717,6 +1731,7 @@ 65ED4047235DEF6C0081F399 /* RSParser.framework in Frameworks */, 65ED4048235DEF6C0081F399 /* Account.framework in Frameworks */, 65ED4049235DEF6C0081F399 /* Articles.framework in Frameworks */, + 511076F9243BDA9600D97C8C /* FeedProvider.framework in Frameworks */, 65ED404A235DEF6C0081F399 /* RSCore.framework in Frameworks */, 65ED404B235DEF6C0081F399 /* SyncDatabase.framework in Frameworks */, ); @@ -1753,6 +1768,7 @@ files = ( 65ED42DE235E74230081F399 /* Sparkle.framework in Frameworks */, 65ED42D9235E740D0081F399 /* Sparkle.framework in Frameworks */, + 511076F7243BDA8100D97C8C /* FeedProvider.framework in Frameworks */, 84C37FA920DD8D9000CA8CF5 /* RSWeb.framework in Frameworks */, 84C37FC520DD8E1D00CA8CF5 /* RSDatabase.framework in Frameworks */, 84C37FAD20DD8D9900CA8CF5 /* RSTree.framework in Frameworks */, @@ -1776,6 +1792,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5110766B243BCE0400D97C8C /* Products */ = { + isa = PBXGroup; + children = ( + 51107672243BCE0500D97C8C /* FeedProvider.framework */, + ); + name = Products; + sourceTree = ""; + }; 511D43CE231FA51100FB1562 /* Resources */ = { isa = PBXGroup; children = ( @@ -2568,6 +2592,7 @@ isa = PBXGroup; children = ( 846E77301F6EF5D600A165E2 /* Account.xcodeproj */, + 5110766A243BCE0400D97C8C /* FeedProvider.xcodeproj */, 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */, 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */, 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */, @@ -3145,6 +3170,10 @@ ProductGroup = 8407167A2262A61100344432 /* Products */; ProjectRef = 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */; }, + { + ProductGroup = 5110766B243BCE0400D97C8C /* Products */; + ProjectRef = 5110766A243BCE0400D97C8C /* FeedProvider.xcodeproj */; + }, { ProductGroup = 84C37F7B20DD8CF200CA8CF5 /* Products */; ProjectRef = 84C37F7A20DD8CF200CA8CF5 /* RSCore.xcodeproj */; @@ -3190,6 +3219,13 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 51107672243BCE0500D97C8C /* FeedProvider.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = FeedProvider.framework; + remoteRef = 51107671243BCE0500D97C8C /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 51554C01228B6EB50055115A /* SyncDatabase.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; From 429ba1aed304f4111a1948832061b1e1e68da251 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 6 Apr 2020 21:06:42 -0500 Subject: [PATCH 002/108] Add Feed Providers preference pane. --- Frameworks/FeedProvider/FeedProvider.swift | 20 ++ .../FeedProvider.xcodeproj/project.pbxproj | 35 +++- Mac/AppAssets.swift | 4 + Mac/Base.lproj/Preferences.storyboard | 174 ++++++++++++++++-- .../FeedProviders/FeedProvidersAdd.xib | 109 +++++++++++ .../FeedProvidersAddTableCellView.swift | 16 ++ .../FeedProvidersAddViewController.swift | 141 ++++++++++++++ ...edProvidersPreferencesViewController.swift | 89 +++++++++ ...> PreferencesControlsBackgroundView.swift} | 4 +- ... PreferencesTableViewBackgroundView.swift} | 4 +- .../PreferencesWindowController.swift | 2 + .../Contents.json | 12 ++ .../feedProviderToolbar.imageset/globe.pdf | Bin 0 -> 7542 bytes .../Contents.json | 15 ++ .../feedProviderTwitter.imageset/twitter.pdf | Bin 0 -> 4237 bytes NetNewsWire.xcodeproj/project.pbxproj | 56 ++++-- 16 files changed, 651 insertions(+), 30 deletions(-) create mode 100644 Frameworks/FeedProvider/FeedProvider.swift create mode 100644 Mac/Preferences/FeedProviders/FeedProvidersAdd.xib create mode 100644 Mac/Preferences/FeedProviders/FeedProvidersAddTableCellView.swift create mode 100644 Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift create mode 100644 Mac/Preferences/FeedProviders/FeedProvidersPreferencesViewController.swift rename Mac/Preferences/{Accounts/AccountsControlsBackgroundView.swift => PreferencesControlsBackgroundView.swift} (93%) rename Mac/Preferences/{Accounts/AccountsTableViewBackgroundView.swift => PreferencesTableViewBackgroundView.swift} (81%) create mode 100644 Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/globe.pdf create mode 100644 Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/twitter.pdf diff --git a/Frameworks/FeedProvider/FeedProvider.swift b/Frameworks/FeedProvider/FeedProvider.swift new file mode 100644 index 000000000..8ce988d1e --- /dev/null +++ b/Frameworks/FeedProvider/FeedProvider.swift @@ -0,0 +1,20 @@ +// +// FeedProvider.swift +// FeedProvider +// +// Created by Maurice Parker on 4/6/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation +import RSCore + +public enum FeedProviderType: Int, Codable { + // Raw values should not change since they’re stored. + case twitter = 1 +} + + +protocol FeedProvider { + +} diff --git a/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj b/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj index 949eb244d..1440e387b 100644 --- a/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj +++ b/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj @@ -13,8 +13,28 @@ 5110769E243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */; }; 5110769F243BCF3A00D97C8C /* FeedProvider_project.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */; }; 511076EE243BD82A00D97C8C /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 511076ED243BD82A00D97C8C /* Articles.framework */; }; + 51107722243BE0DA00D97C8C /* FeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107721243BE0DA00D97C8C /* FeedProvider.swift */; }; + 51107724243BE11800D97C8C /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107723243BE11800D97C8C /* RSParser.framework */; }; + 51107725243BE11800D97C8C /* RSParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107723243BE11800D97C8C /* RSParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51107728243BE15D00D97C8C /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107727243BE15D00D97C8C /* RSCore.framework */; }; + 51107729243BE15D00D97C8C /* RSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107727243BE15D00D97C8C /* RSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 51107726243BE11800D97C8C /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 51107729243BE15D00D97C8C /* RSCore.framework in Embed Frameworks */, + 51107725243BE11800D97C8C /* RSParser.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 5110765F243BCE0400D97C8C /* FeedProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FeedProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 51107663243BCE0400D97C8C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -24,6 +44,9 @@ 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project_debug.xcconfig; sourceTree = ""; }; 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project.xcconfig; sourceTree = ""; }; 511076ED243BD82A00D97C8C /* Articles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Articles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 51107721243BE0DA00D97C8C /* FeedProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedProvider.swift; sourceTree = ""; }; + 51107723243BE11800D97C8C /* RSParser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSParser.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 51107727243BE15D00D97C8C /* RSCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -31,6 +54,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 51107728243BE15D00D97C8C /* RSCore.framework in Frameworks */, + 51107724243BE11800D97C8C /* RSParser.framework in Frameworks */, 511076EE243BD82A00D97C8C /* Articles.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -41,6 +66,7 @@ 51107655243BCE0400D97C8C = { isa = PBXGroup; children = ( + 51107721243BE0DA00D97C8C /* FeedProvider.swift */, 51107689243BCEB300D97C8C /* xcconfig */, 51107663243BCE0400D97C8C /* Info.plist */, 51107660243BCE0400D97C8C /* Products */, @@ -59,10 +85,10 @@ 51107689243BCEB300D97C8C /* xcconfig */ = { isa = PBXGroup; children = ( + 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */, 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */, 51107698243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig */, 51107697243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig */, - 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */, 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */, ); path = xcconfig; @@ -71,6 +97,8 @@ 511076EC243BD82A00D97C8C /* Frameworks */ = { isa = PBXGroup; children = ( + 51107727243BE15D00D97C8C /* RSCore.framework */, + 51107723243BE11800D97C8C /* RSParser.framework */, 511076ED243BD82A00D97C8C /* Articles.framework */, ); name = Frameworks; @@ -98,6 +126,7 @@ 5110765C243BCE0400D97C8C /* Frameworks */, 5110765D243BCE0400D97C8C /* Resources */, 511076A2243BD2E600D97C8C /* Run Script: Verfiy No Build Settings */, + 51107726243BE11800D97C8C /* Embed Frameworks */, ); buildRules = ( ); @@ -115,10 +144,11 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 1140; - ORGANIZATIONNAME = "Vincode, Inc"; + ORGANIZATIONNAME = "Ranchero Software, LLC"; TargetAttributes = { 5110765E243BCE0400D97C8C = { CreatedOnToolsVersion = 11.4; + LastSwiftMigration = 1140; }; }; }; @@ -181,6 +211,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 51107722243BE0DA00D97C8C /* FeedProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index 777438f2c..4ef1c1cc5 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -81,6 +81,10 @@ struct AppAssets { return RSImage(named: "faviconTemplateImage")! }() + static var feedProviderTwitter: RSImage = { + return RSImage(named: "feedProviderTwitter")! + }() + static var filterActive: RSImage = { return RSImage(named: "filterActive")! }() diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard index 6b2ac07d5..e9aad2bd7 100644 --- a/Mac/Base.lproj/Preferences.storyboard +++ b/Mac/Base.lproj/Preferences.storyboard @@ -1,8 +1,7 @@ - + - - + @@ -375,7 +374,7 @@ - + @@ -385,17 +384,17 @@ - - + + - + - + - + @@ -403,7 +402,6 @@ - @@ -499,11 +497,11 @@ - + - + @@ -549,6 +547,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mac/Preferences/FeedProviders/FeedProvidersAdd.xib b/Mac/Preferences/FeedProviders/FeedProvidersAdd.xib new file mode 100644 index 000000000..874bfa9e0 --- /dev/null +++ b/Mac/Preferences/FeedProviders/FeedProvidersAdd.xib @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mac/Preferences/FeedProviders/FeedProvidersAddTableCellView.swift b/Mac/Preferences/FeedProviders/FeedProvidersAddTableCellView.swift new file mode 100644 index 000000000..8d0325832 --- /dev/null +++ b/Mac/Preferences/FeedProviders/FeedProvidersAddTableCellView.swift @@ -0,0 +1,16 @@ +// +// FeedProvidersAddTableCellView.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/6/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import AppKit + +class FeedProvidersAddTableCellView: NSTableCellView { + + @IBOutlet weak var feedProviderImageView: NSImageView? + @IBOutlet weak var feedProviderNameLabel: NSTextField? + +} diff --git a/Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift b/Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift new file mode 100644 index 000000000..426037d96 --- /dev/null +++ b/Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift @@ -0,0 +1,141 @@ +// +// FeedProvidersAddViewController.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/6/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import AppKit +import FeedProvider + +class FeedProvidersAddViewController: NSViewController { + + @IBOutlet weak var tableView: NSTableView! + + private var accountsAddWindowController: NSWindowController? + + #if DEBUG + private var addableFeedProviderTypes: [FeedProviderType] = [.twitter] + #else + private var addableFeedProviderTypes: [FeedProviderType] = [.twitter] + #endif + + init() { + super.init(nibName: "FeedProvidersAdd", bundle: nil) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func viewDidLoad() { + super.viewDidLoad() + tableView.dataSource = self + tableView.delegate = self + } + +} + +// MARK: - NSTableViewDataSource + +extension FeedProvidersAddViewController: NSTableViewDataSource { + + func numberOfRows(in tableView: NSTableView) -> Int { + return addableFeedProviderTypes.count + } + + func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { + return nil + } +} + +// MARK: - NSTableViewDelegate + +extension FeedProvidersAddViewController: NSTableViewDelegate { + + private static let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "AccountCell") + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + + if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? FeedProvidersAddTableCellView { + switch addableFeedProviderTypes[row] { + case .twitter: + cell.feedProviderNameLabel?.stringValue = NSLocalizedString("Twitter", comment: "Twitter") + cell.feedProviderImageView?.image = AppAssets.feedProviderTwitter + } + return cell + } + return nil + } + + func tableViewSelectionDidChange(_ notification: Notification) { + + let selectedRow = tableView.selectedRow + guard selectedRow != -1 else { + return + } + +// switch addableAccountTypes[selectedRow] { +// case .onMyMac: +// let accountsAddLocalWindowController = AccountsAddLocalWindowController() +// accountsAddLocalWindowController.runSheetOnWindow(self.view.window!) +// accountsAddWindowController = accountsAddLocalWindowController +// case .cloudKit: +// let accountsAddCloudKitWindowController = AccountsAddCloudKitWindowController() +// accountsAddCloudKitWindowController.runSheetOnWindow(self.view.window!) { response in +// if response == NSApplication.ModalResponse.OK { +// self.restrictAccounts() +// self.tableView.reloadData() +// } +// } +// accountsAddWindowController = accountsAddCloudKitWindowController +// case .feedbin: +// let accountsFeedbinWindowController = AccountsFeedbinWindowController() +// accountsFeedbinWindowController.runSheetOnWindow(self.view.window!) +// accountsAddWindowController = accountsFeedbinWindowController +// case .feedWrangler: +// let accountsFeedWranglerWindowController = AccountsFeedWranglerWindowController() +// accountsFeedWranglerWindowController.runSheetOnWindow(self.view.window!) +// accountsAddWindowController = accountsFeedWranglerWindowController +// case .freshRSS: +// let accountsReaderAPIWindowController = AccountsReaderAPIWindowController() +// accountsReaderAPIWindowController.accountType = .freshRSS +// accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) +// accountsAddWindowController = accountsReaderAPIWindowController +// case .feedly: +// let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) +// addAccount.delegate = self +// addAccount.presentationAnchor = self.view.window! +// MainThreadOperationQueue.shared.add(addAccount) +// case .newsBlur: +// let accountsNewsBlurWindowController = AccountsNewsBlurWindowController() +// accountsNewsBlurWindowController.runSheetOnWindow(self.view.window!) +// accountsAddWindowController = accountsNewsBlurWindowController +// } + + tableView.selectRowIndexes([], byExtendingSelection: false) + + } + +} + +// MARK: OAuthAccountAuthorizationOperationDelegate + +//extension AccountsAddViewController: OAuthAccountAuthorizationOperationDelegate { +// +// func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { +// account.refreshAll { [weak self] result in +// switch result { +// case .success: +// break +// case .failure(let error): +// self?.presentError(error) +// } +// } +// } +// +// func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { +// view.window?.presentError(error) +// } +//} diff --git a/Mac/Preferences/FeedProviders/FeedProvidersPreferencesViewController.swift b/Mac/Preferences/FeedProviders/FeedProvidersPreferencesViewController.swift new file mode 100644 index 000000000..d41b97df7 --- /dev/null +++ b/Mac/Preferences/FeedProviders/FeedProvidersPreferencesViewController.swift @@ -0,0 +1,89 @@ +// +// FeedProvidersPreferencesViewController.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/6/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import AppKit + +final class FeedProvidersPreferencesViewController: NSViewController { + + @IBOutlet weak var tableView: NSTableView! + @IBOutlet weak var detailView: NSView! + @IBOutlet weak var deleteButton: NSButton! + + private var sortedAccounts = [String]() + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.delegate = self + tableView.dataSource = self + + showController(FeedProvidersAddViewController()) + + // Fix tableView frame — for some reason IB wants it 1pt wider than the clip view. This leads to unwanted horizontal scrolling. + var rTable = tableView.frame + rTable.size.width = tableView.superview!.frame.size.width + tableView.frame = rTable + } + +} + +// MARK: - NSTableViewDataSource + +extension FeedProvidersPreferencesViewController: NSTableViewDataSource { + + func numberOfRows(in tableView: NSTableView) -> Int { + return sortedAccounts.count + } + + func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { + return sortedAccounts[row] + } +} + +// MARK: - NSTableViewDelegate + +extension FeedProvidersPreferencesViewController: NSTableViewDelegate { + + private static let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "AccountCell") + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? NSTableCellView { + let account = sortedAccounts[row] +// cell.textField?.stringValue = account.nameForDisplay +// cell.imageView?.image = account.smallIcon?.image + return cell + } + return nil + } + + func tableViewSelectionDidChange(_ notification: Notification) { + + + } + +} + +// MARK: - Private + +private extension FeedProvidersPreferencesViewController { + + func showController(_ controller: NSViewController) { + + if let controller = children.first { + children.removeAll() + controller.view.removeFromSuperview() + } + + addChild(controller) + controller.view.translatesAutoresizingMaskIntoConstraints = false + detailView.addSubview(controller.view) + detailView.addFullSizeConstraints(forSubview: controller.view) + + } + +} diff --git a/Mac/Preferences/Accounts/AccountsControlsBackgroundView.swift b/Mac/Preferences/PreferencesControlsBackgroundView.swift similarity index 93% rename from Mac/Preferences/Accounts/AccountsControlsBackgroundView.swift rename to Mac/Preferences/PreferencesControlsBackgroundView.swift index ef4489430..36353dd6b 100644 --- a/Mac/Preferences/Accounts/AccountsControlsBackgroundView.swift +++ b/Mac/Preferences/PreferencesControlsBackgroundView.swift @@ -1,5 +1,5 @@ // -// AccountsControlsBackgroundView.swift +// PreferencesControlsBackgroundView.swift // NetNewsWire // // Created by Brent Simmons on 3/18/19. @@ -9,7 +9,7 @@ import AppKit import RSCore -final class AccountsControlsBackgroundView: NSView { +final class PreferencesControlsBackgroundView: NSView { private let lightModeFillColor = NSColor(white: 0.97, alpha: 1.0) private let darkModeFillColor = NSColor(red: 0.32, green: 0.34, blue: 0.35, alpha: 1.0) diff --git a/Mac/Preferences/Accounts/AccountsTableViewBackgroundView.swift b/Mac/Preferences/PreferencesTableViewBackgroundView.swift similarity index 81% rename from Mac/Preferences/Accounts/AccountsTableViewBackgroundView.swift rename to Mac/Preferences/PreferencesTableViewBackgroundView.swift index 2c30b96b1..34548e429 100644 --- a/Mac/Preferences/Accounts/AccountsTableViewBackgroundView.swift +++ b/Mac/Preferences/PreferencesTableViewBackgroundView.swift @@ -1,5 +1,5 @@ // -// AccountsTableViewBackgroundView.swift +// PreferencesTableViewBackgroundView.swift // NetNewsWire // // Created by Brent Simmons on 3/19/19. @@ -8,7 +8,7 @@ import AppKit -final class AccountsTableViewBackgroundView: NSView { +final class PreferencesTableViewBackgroundView: NSView { let lightBorderColor = NSColor(white: 0.71, alpha: 1.0) let darkBorderColor = NSColor(red: 0.41, green: 0.43, blue: 0.44, alpha: 1.0) diff --git a/Mac/Preferences/PreferencesWindowController.swift b/Mac/Preferences/PreferencesWindowController.swift index 79570b50a..b13adbfd4 100644 --- a/Mac/Preferences/PreferencesWindowController.swift +++ b/Mac/Preferences/PreferencesWindowController.swift @@ -24,6 +24,7 @@ private struct PreferencesToolbarItemSpec { private struct ToolbarItemIdentifier { static let General = "General" static let Accounts = "Accounts" + static let FeedProvider = "FeedProvider" static let Advanced = "Advanced" } @@ -35,6 +36,7 @@ class PreferencesWindowController : NSWindowController, NSToolbarDelegate { var specs = [PreferencesToolbarItemSpec]() specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.General, name: NSLocalizedString("General", comment: "Preferences"), imageName: NSImage.preferencesGeneralName)] specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Accounts, name: NSLocalizedString("Accounts", comment: "Preferences"), imageName: NSImage.userAccountsName)] + specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.FeedProvider, name: NSLocalizedString("Providers", comment: "Preferences"), imageName: "feedProviderToolbar")] // Omit the Advanced Preferences for now because the Software Update related functionality is // forbidden/non-applicable, and we can rely upon Apple to some extent for crash reports. We diff --git a/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/Contents.json b/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/Contents.json new file mode 100644 index 000000000..79f849e65 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "globe.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/globe.pdf b/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/globe.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2e2ffbf186cd7c33e0dce23a6d31f4a66dc463a6 GIT binary patch literal 7542 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 Nc`#{d#pNY1{|CIlI9LDx literal 0 HcmV?d00001 diff --git a/Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/Contents.json b/Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/Contents.json new file mode 100644 index 000000000..834100cda --- /dev/null +++ b/Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "twitter.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/twitter.pdf b/Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/twitter.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e50de4443827d9570161cdd229c8d6ce640981e4 GIT binary patch literal 4237 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 Date: Tue, 7 Apr 2020 08:06:47 -0500 Subject: [PATCH 003/108] Tweak the new preference pane. --- Mac/AppAssets.swift | 6 +++++ .../PreferencesWindowController.swift | 24 ++++++++++++------ .../Contents.json | 12 --------- .../feedProviderToolbar.imageset/globe.pdf | Bin 7542 -> 0 bytes 4 files changed, 22 insertions(+), 20 deletions(-) delete mode 100644 Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/Contents.json delete mode 100644 Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/globe.pdf diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index 4ef1c1cc5..101174152 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -77,6 +77,12 @@ struct AppAssets { return RSImage(named: "articleExtractorProgress4") }() + static var bookmarkImage: RSImage? = { + let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/BookmarkIcon.icns" + let image = RSImage(contentsOfFile: path) + return image + }() + static var faviconTemplateImage: RSImage = { return RSImage(named: "faviconTemplateImage")! }() diff --git a/Mac/Preferences/PreferencesWindowController.swift b/Mac/Preferences/PreferencesWindowController.swift index b13adbfd4..e6d225914 100644 --- a/Mac/Preferences/PreferencesWindowController.swift +++ b/Mac/Preferences/PreferencesWindowController.swift @@ -12,12 +12,12 @@ private struct PreferencesToolbarItemSpec { let identifier: NSToolbarItem.Identifier let name: String - let imageName: NSImage.Name + let image: NSImage? - init(identifierRawValue: String, name: String, imageName: NSImage.Name) { + init(identifierRawValue: String, name: String, image: NSImage?) { self.identifier = NSToolbarItem.Identifier(identifierRawValue) self.name = name - self.imageName = imageName + self.image = image } } @@ -34,16 +34,24 @@ class PreferencesWindowController : NSWindowController, NSToolbarDelegate { private var viewControllers = [String: NSViewController]() private let toolbarItemSpecs: [PreferencesToolbarItemSpec] = { var specs = [PreferencesToolbarItemSpec]() - specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.General, name: NSLocalizedString("General", comment: "Preferences"), imageName: NSImage.preferencesGeneralName)] - specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Accounts, name: NSLocalizedString("Accounts", comment: "Preferences"), imageName: NSImage.userAccountsName)] - specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.FeedProvider, name: NSLocalizedString("Providers", comment: "Preferences"), imageName: "feedProviderToolbar")] + specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.General, + name: NSLocalizedString("General", comment: "Preferences"), + image: NSImage(named: NSImage.preferencesGeneralName))] + specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Accounts, + name: NSLocalizedString("Accounts", comment: "Preferences"), + image: NSImage(named: NSImage.userAccountsName))] + specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.FeedProvider, + name: NSLocalizedString("Integrations", comment: "Preferences"), + image: AppAssets.bookmarkImage)] // Omit the Advanced Preferences for now because the Software Update related functionality is // forbidden/non-applicable, and we can rely upon Apple to some extent for crash reports. We // can add back the Crash Reporter preferences when we're ready to dynamically shuffle the rest // of the content in this tab. #if !MAC_APP_STORE - specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Advanced, name: NSLocalizedString("Advanced", comment: "Preferences"), imageName: NSImage.advancedName)] + specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Advanced, + name: NSLocalizedString("Advanced", comment: "Preferences"), + image: NSImage(named: NSImage.advancedName))] #endif return specs }() @@ -86,7 +94,7 @@ class PreferencesWindowController : NSWindowController, NSToolbarDelegate { toolbarItem.target = self toolbarItem.label = toolbarItemSpec.name toolbarItem.paletteLabel = toolbarItem.label - toolbarItem.image = NSImage(named: toolbarItemSpec.imageName) + toolbarItem.image = toolbarItemSpec.image return toolbarItem } diff --git a/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/Contents.json b/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/Contents.json deleted file mode 100644 index 79f849e65..000000000 --- a/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "globe.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/globe.pdf b/Mac/Resources/Assets.xcassets/feedProviderToolbar.imageset/globe.pdf deleted file mode 100644 index 2e2ffbf186cd7c33e0dce23a6d31f4a66dc463a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7542 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 Nc`#{d#pNY1{|CIlI9LDx From f8667be32b804e0b50b55f8bd8be9fa08b50fa8b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 7 Apr 2020 12:02:07 -0500 Subject: [PATCH 004/108] Change preference pane to Extensions for demo purposes. --- Frameworks/FeedProvider/FeedProvider.swift | 4 +++- Mac/AppAssets.swift | 18 +++++++++++++----- .../FeedProvidersAddViewController.swift | 10 ++++++++-- .../PreferencesWindowController.swift | 2 +- .../adapterMarsEdit.imageset/Contents.json | 15 +++++++++++++++ .../adapterMarsEdit.imageset/marsedit.pdf | Bin 0 -> 4453 bytes .../micro-dot-blog.pdf | Bin 0 -> 4190 bytes .../Contents.json | 0 .../twitter.pdf | Bin 9 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/Contents.json create mode 100644 Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/marsedit.pdf create mode 100644 Mac/Resources/Assets.xcassets/adapterMicroblog.imageset/micro-dot-blog.pdf rename Mac/Resources/Assets.xcassets/{feedProviderTwitter.imageset => adapterTwitter.imageset}/Contents.json (100%) rename Mac/Resources/Assets.xcassets/{feedProviderTwitter.imageset => adapterTwitter.imageset}/twitter.pdf (100%) diff --git a/Frameworks/FeedProvider/FeedProvider.swift b/Frameworks/FeedProvider/FeedProvider.swift index 8ce988d1e..8f2247cfa 100644 --- a/Frameworks/FeedProvider/FeedProvider.swift +++ b/Frameworks/FeedProvider/FeedProvider.swift @@ -11,7 +11,9 @@ import RSCore public enum FeedProviderType: Int, Codable { // Raw values should not change since they’re stored. - case twitter = 1 + case marsEdit = 1 + case microblog = 2 + case twitter = 3 } diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index 101174152..c19d809c2 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -45,6 +45,18 @@ struct AppAssets { return RSImage(named: "accountNewsBlur") }() + static var adapterMarsEdit: RSImage = { + return RSImage(named: "adapterMarsEdit")! + }() + + static var adapterMicroblog: RSImage = { + return RSImage(named: "adapterMicroblog")! + }() + + static var adapterTwitter: RSImage = { + return RSImage(named: "adapterTwitter")! + }() + static var articleExtractor: RSImage! = { return RSImage(named: "articleExtractor") }() @@ -78,7 +90,7 @@ struct AppAssets { }() static var bookmarkImage: RSImage? = { - let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/BookmarkIcon.icns" + let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/KEXT.icns" let image = RSImage(contentsOfFile: path) return image }() @@ -87,10 +99,6 @@ struct AppAssets { return RSImage(named: "faviconTemplateImage")! }() - static var feedProviderTwitter: RSImage = { - return RSImage(named: "feedProviderTwitter")! - }() - static var filterActive: RSImage = { return RSImage(named: "filterActive")! }() diff --git a/Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift b/Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift index 426037d96..e0721c0d3 100644 --- a/Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift +++ b/Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift @@ -16,7 +16,7 @@ class FeedProvidersAddViewController: NSViewController { private var accountsAddWindowController: NSWindowController? #if DEBUG - private var addableFeedProviderTypes: [FeedProviderType] = [.twitter] + private var addableFeedProviderTypes: [FeedProviderType] = [.marsEdit, .microblog, .twitter] #else private var addableFeedProviderTypes: [FeedProviderType] = [.twitter] #endif @@ -60,9 +60,15 @@ extension FeedProvidersAddViewController: NSTableViewDelegate { if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? FeedProvidersAddTableCellView { switch addableFeedProviderTypes[row] { + case .marsEdit: + cell.feedProviderNameLabel?.stringValue = NSLocalizedString("MarsEdit", comment: "MarsEdit") + cell.feedProviderImageView?.image = AppAssets.adapterMarsEdit + case .microblog: + cell.feedProviderNameLabel?.stringValue = NSLocalizedString("Micro.blog", comment: "Micro.blog") + cell.feedProviderImageView?.image = AppAssets.adapterMicroblog case .twitter: cell.feedProviderNameLabel?.stringValue = NSLocalizedString("Twitter", comment: "Twitter") - cell.feedProviderImageView?.image = AppAssets.feedProviderTwitter + cell.feedProviderImageView?.image = AppAssets.adapterTwitter } return cell } diff --git a/Mac/Preferences/PreferencesWindowController.swift b/Mac/Preferences/PreferencesWindowController.swift index e6d225914..9679aa9a3 100644 --- a/Mac/Preferences/PreferencesWindowController.swift +++ b/Mac/Preferences/PreferencesWindowController.swift @@ -41,7 +41,7 @@ class PreferencesWindowController : NSWindowController, NSToolbarDelegate { name: NSLocalizedString("Accounts", comment: "Preferences"), image: NSImage(named: NSImage.userAccountsName))] specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.FeedProvider, - name: NSLocalizedString("Integrations", comment: "Preferences"), + name: NSLocalizedString("Extensions", comment: "Preferences"), image: AppAssets.bookmarkImage)] // Omit the Advanced Preferences for now because the Software Update related functionality is diff --git a/Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/Contents.json b/Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/Contents.json new file mode 100644 index 000000000..44cd99384 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "marsedit.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/marsedit.pdf b/Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/marsedit.pdf new file mode 100644 index 0000000000000000000000000000000000000000..732eb701c397d9adbf0242ba329154a2a1b18456 GIT binary patch literal 4453 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 literal 0 HcmV?d00001 diff --git a/Mac/Resources/Assets.xcassets/adapterMicroblog.imageset/micro-dot-blog.pdf b/Mac/Resources/Assets.xcassets/adapterMicroblog.imageset/micro-dot-blog.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ad66dd377a345b3b05272209b53879a86dee7994 GIT binary patch literal 4190 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 T{ISu}axmK82?CmiTHyZx7I;X| literal 0 HcmV?d00001 diff --git a/Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/Contents.json b/Mac/Resources/Assets.xcassets/adapterTwitter.imageset/Contents.json similarity index 100% rename from Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/Contents.json rename to Mac/Resources/Assets.xcassets/adapterTwitter.imageset/Contents.json diff --git a/Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/twitter.pdf b/Mac/Resources/Assets.xcassets/adapterTwitter.imageset/twitter.pdf similarity index 100% rename from Mac/Resources/Assets.xcassets/feedProviderTwitter.imageset/twitter.pdf rename to Mac/Resources/Assets.xcassets/adapterTwitter.imageset/twitter.pdf From 49cff8eb8ea5864f9d9c2cdadb4fe92ad7b56dce Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 7 Apr 2020 15:25:33 -0500 Subject: [PATCH 005/108] Add basic ExtensionPoint support. --- Frameworks/FeedProvider/FeedProvider.swift | 10 +- .../FeedProvider.xcodeproj/project.pbxproj | 12 ++ .../Twitter/TwitterFeedProvider.swift | 17 +++ Mac/AppAssets.swift | 30 +++-- Mac/Base.lproj/Preferences.storyboard | 24 ++-- .../ExtensionPointAdd.xib} | 8 +- .../ExtensionPointAddTableCellView.swift | 16 +++ .../ExtensionPointAddViewController.swift} | 38 ++---- ...nsionPointPreferencesViewController.swift} | 12 +- .../FeedProvidersAddTableCellView.swift | 16 --- .../PreferencesWindowController.swift | 6 +- .../Contents.json | 0 .../marsedit.pdf | Bin .../Contents.json | 15 +++ .../micro-dot-blog.pdf | Bin .../Contents.json | 0 .../twitter.pdf | Bin NetNewsWire.xcodeproj/project.pbxproj | 118 +++++++++++------- Shared/ExtensionPoints/ExtensionPoint.swift | 20 +++ .../ExtensionPointManager.swift | 49 ++++++++ .../ExtensionPoints/ExtensionPointType.swift | 14 +++ .../SendToMarsEditCommand.swift | 6 +- .../SendToMicroBlogCommand.swift | 5 +- .../TwitterFeedProvider+Extensions.swift | 23 ++++ .../AddWebFeedDefaultContainer.swift | 0 .../ArticleStringFormatter.swift | 0 .../ArticleUtilities.swift | 0 .../{Data => Extensions}/CacheCleaner.swift | 0 .../SmallIconProvider.swift | 0 29 files changed, 299 insertions(+), 140 deletions(-) create mode 100644 Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift rename Mac/Preferences/{FeedProviders/FeedProvidersAdd.xib => ExtensionPoints/ExtensionPointAdd.xib} (96%) create mode 100644 Mac/Preferences/ExtensionPoints/ExtensionPointAddTableCellView.swift rename Mac/Preferences/{FeedProviders/FeedProvidersAddViewController.swift => ExtensionPoints/ExtensionPointAddViewController.swift} (75%) rename Mac/Preferences/{FeedProviders/FeedProvidersPreferencesViewController.swift => ExtensionPoints/ExtensionPointPreferencesViewController.swift} (83%) delete mode 100644 Mac/Preferences/FeedProviders/FeedProvidersAddTableCellView.swift rename Mac/Resources/Assets.xcassets/{adapterMarsEdit.imageset => extensionPointMarsEdit.imageset}/Contents.json (100%) rename Mac/Resources/Assets.xcassets/{adapterMarsEdit.imageset => extensionPointMarsEdit.imageset}/marsedit.pdf (100%) create mode 100644 Mac/Resources/Assets.xcassets/extensionPointMicroblog.imageset/Contents.json rename Mac/Resources/Assets.xcassets/{adapterMicroblog.imageset => extensionPointMicroblog.imageset}/micro-dot-blog.pdf (100%) rename Mac/Resources/Assets.xcassets/{adapterTwitter.imageset => extensionPointTwitter.imageset}/Contents.json (100%) rename Mac/Resources/Assets.xcassets/{adapterTwitter.imageset => extensionPointTwitter.imageset}/twitter.pdf (100%) create mode 100644 Shared/ExtensionPoints/ExtensionPoint.swift create mode 100644 Shared/ExtensionPoints/ExtensionPointManager.swift create mode 100644 Shared/ExtensionPoints/ExtensionPointType.swift rename Shared/{Commands => ExtensionPoints}/SendToMarsEditCommand.swift (90%) rename Shared/{Commands => ExtensionPoints}/SendToMicroBlogCommand.swift (91%) create mode 100644 Shared/ExtensionPoints/TwitterFeedProvider+Extensions.swift rename Shared/{Data => Extensions}/AddWebFeedDefaultContainer.swift (100%) rename Shared/{Data => Extensions}/ArticleStringFormatter.swift (100%) rename Shared/{Data => Extensions}/ArticleUtilities.swift (100%) rename Shared/{Data => Extensions}/CacheCleaner.swift (100%) rename Shared/{Data => Extensions}/SmallIconProvider.swift (100%) diff --git a/Frameworks/FeedProvider/FeedProvider.swift b/Frameworks/FeedProvider/FeedProvider.swift index 8f2247cfa..f12947980 100644 --- a/Frameworks/FeedProvider/FeedProvider.swift +++ b/Frameworks/FeedProvider/FeedProvider.swift @@ -9,14 +9,6 @@ import Foundation import RSCore -public enum FeedProviderType: Int, Codable { - // Raw values should not change since they’re stored. - case marsEdit = 1 - case microblog = 2 - case twitter = 3 -} - - -protocol FeedProvider { +public protocol FeedProvider { } diff --git a/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj b/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj index 1440e387b..2b5495cd4 100644 --- a/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj +++ b/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 51107725243BE11800D97C8C /* RSParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107723243BE11800D97C8C /* RSParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51107728243BE15D00D97C8C /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107727243BE15D00D97C8C /* RSCore.framework */; }; 51107729243BE15D00D97C8C /* RSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107727243BE15D00D97C8C /* RSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 515A5105243D0C6B0089E588 /* TwitterFeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5104243D0C6B0089E588 /* TwitterFeedProvider.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -47,6 +48,7 @@ 51107721243BE0DA00D97C8C /* FeedProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedProvider.swift; sourceTree = ""; }; 51107723243BE11800D97C8C /* RSParser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSParser.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 51107727243BE15D00D97C8C /* RSCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 515A5104243D0C6B0089E588 /* TwitterFeedProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterFeedProvider.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -67,6 +69,7 @@ isa = PBXGroup; children = ( 51107721243BE0DA00D97C8C /* FeedProvider.swift */, + 515A5103243D0C230089E588 /* Twitter */, 51107689243BCEB300D97C8C /* xcconfig */, 51107663243BCE0400D97C8C /* Info.plist */, 51107660243BCE0400D97C8C /* Products */, @@ -104,6 +107,14 @@ name = Frameworks; sourceTree = ""; }; + 515A5103243D0C230089E588 /* Twitter */ = { + isa = PBXGroup; + children = ( + 515A5104243D0C6B0089E588 /* TwitterFeedProvider.swift */, + ); + path = Twitter; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -211,6 +222,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 515A5105243D0C6B0089E588 /* TwitterFeedProvider.swift in Sources */, 51107722243BE0DA00D97C8C /* FeedProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift new file mode 100644 index 000000000..b4858d0c5 --- /dev/null +++ b/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -0,0 +1,17 @@ +// +// TwitterFeedProvider.swift +// FeedProvider +// +// Created by Maurice Parker on 4/7/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +public struct TwitterFeedProvider: FeedProvider { + + public init() { + + } + +} diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index c19d809c2..b7dca8f1f 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -45,18 +45,6 @@ struct AppAssets { return RSImage(named: "accountNewsBlur") }() - static var adapterMarsEdit: RSImage = { - return RSImage(named: "adapterMarsEdit")! - }() - - static var adapterMicroblog: RSImage = { - return RSImage(named: "adapterMicroblog")! - }() - - static var adapterTwitter: RSImage = { - return RSImage(named: "adapterTwitter")! - }() - static var articleExtractor: RSImage! = { return RSImage(named: "articleExtractor") }() @@ -89,12 +77,22 @@ struct AppAssets { return RSImage(named: "articleExtractorProgress4") }() - static var bookmarkImage: RSImage? = { - let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/KEXT.icns" - let image = RSImage(contentsOfFile: path) - return image + static var extensionPointMarsEdit: RSImage = { + return RSImage(named: "extensionPointMarsEdit")! }() + static var extensionPointMicroblog: RSImage = { + return RSImage(named: "extensionPointMicroblog")! + }() + + static var extensionPointTwitter: RSImage = { + return RSImage(named: "extensionPointTwitter")! + }() + + static var extensionPreference: RSImage? = { + return RSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/KEXT.icns") + }() + static var faviconTemplateImage: RSImage = { return RSImage(named: "faviconTemplateImage")! }() diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard index e9aad2bd7..df9a513cd 100644 --- a/Mac/Base.lproj/Preferences.storyboard +++ b/Mac/Base.lproj/Preferences.storyboard @@ -385,16 +385,16 @@ - + - + - + - + @@ -501,7 +501,7 @@ - + @@ -547,25 +547,25 @@ - + - + - + - + - + - + @@ -666,7 +666,7 @@ - + diff --git a/Mac/Preferences/FeedProviders/FeedProvidersAdd.xib b/Mac/Preferences/ExtensionPoints/ExtensionPointAdd.xib similarity index 96% rename from Mac/Preferences/FeedProviders/FeedProvidersAdd.xib rename to Mac/Preferences/ExtensionPoints/ExtensionPointAdd.xib index 874bfa9e0..d2ddc8921 100644 --- a/Mac/Preferences/FeedProviders/FeedProvidersAdd.xib +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointAdd.xib @@ -5,7 +5,7 @@ - + @@ -44,7 +44,7 @@ - + @@ -83,8 +83,8 @@ - - + + diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointAddTableCellView.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointAddTableCellView.swift new file mode 100644 index 000000000..0d488d49d --- /dev/null +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointAddTableCellView.swift @@ -0,0 +1,16 @@ +// +// ExtensionPointAddTableCellView.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/6/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import AppKit + +class ExtensionPointAddTableCellView: NSTableCellView { + + @IBOutlet weak var templateImageView: NSImageView? + @IBOutlet weak var titleLabel: NSTextField? + +} diff --git a/Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift similarity index 75% rename from Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift rename to Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift index e0721c0d3..fd1c1fbdc 100644 --- a/Mac/Preferences/FeedProviders/FeedProvidersAddViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift @@ -1,5 +1,5 @@ // -// FeedProvidersAddViewController.swift +// ExtensionPointAddViewController.swift // NetNewsWire // // Created by Maurice Parker on 4/6/20. @@ -9,20 +9,15 @@ import AppKit import FeedProvider -class FeedProvidersAddViewController: NSViewController { +class ExtensionPointAddViewController: NSViewController { @IBOutlet weak var tableView: NSTableView! - private var accountsAddWindowController: NSWindowController? - - #if DEBUG - private var addableFeedProviderTypes: [FeedProviderType] = [.marsEdit, .microblog, .twitter] - #else - private var addableFeedProviderTypes: [FeedProviderType] = [.twitter] - #endif + private var availableExtensionPoints = [ExtensionPoint]() + private var extensionPointAddWindowController: NSWindowController? init() { - super.init(nibName: "FeedProvidersAdd", bundle: nil) + super.init(nibName: "ExtensionPointAdd", bundle: nil) } public required init?(coder: NSCoder) { @@ -33,16 +28,17 @@ class FeedProvidersAddViewController: NSViewController { super.viewDidLoad() tableView.dataSource = self tableView.delegate = self + availableExtensionPoints = ExtensionPointManager.shared.availableExtensionPoints } } // MARK: - NSTableViewDataSource -extension FeedProvidersAddViewController: NSTableViewDataSource { +extension ExtensionPointAddViewController: NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { - return addableFeedProviderTypes.count + return availableExtensionPoints.count } func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { @@ -52,24 +48,16 @@ extension FeedProvidersAddViewController: NSTableViewDataSource { // MARK: - NSTableViewDelegate -extension FeedProvidersAddViewController: NSTableViewDelegate { +extension ExtensionPointAddViewController: NSTableViewDelegate { private static let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "AccountCell") func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? FeedProvidersAddTableCellView { - switch addableFeedProviderTypes[row] { - case .marsEdit: - cell.feedProviderNameLabel?.stringValue = NSLocalizedString("MarsEdit", comment: "MarsEdit") - cell.feedProviderImageView?.image = AppAssets.adapterMarsEdit - case .microblog: - cell.feedProviderNameLabel?.stringValue = NSLocalizedString("Micro.blog", comment: "Micro.blog") - cell.feedProviderImageView?.image = AppAssets.adapterMicroblog - case .twitter: - cell.feedProviderNameLabel?.stringValue = NSLocalizedString("Twitter", comment: "Twitter") - cell.feedProviderImageView?.image = AppAssets.adapterTwitter - } + if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? ExtensionPointAddTableCellView { + let extensionPoint = availableExtensionPoints[row] + cell.titleLabel?.stringValue = extensionPoint.title + cell.imageView?.image = extensionPoint.templateImage return cell } return nil diff --git a/Mac/Preferences/FeedProviders/FeedProvidersPreferencesViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift similarity index 83% rename from Mac/Preferences/FeedProviders/FeedProvidersPreferencesViewController.swift rename to Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift index d41b97df7..de6000f6b 100644 --- a/Mac/Preferences/FeedProviders/FeedProvidersPreferencesViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift @@ -1,5 +1,5 @@ // -// FeedProvidersPreferencesViewController.swift +// ExtensionsPreferencesViewController.swift // NetNewsWire // // Created by Maurice Parker on 4/6/20. @@ -8,7 +8,7 @@ import AppKit -final class FeedProvidersPreferencesViewController: NSViewController { +final class ExtensionPointPreferencesViewController: NSViewController { @IBOutlet weak var tableView: NSTableView! @IBOutlet weak var detailView: NSView! @@ -22,7 +22,7 @@ final class FeedProvidersPreferencesViewController: NSViewController { tableView.delegate = self tableView.dataSource = self - showController(FeedProvidersAddViewController()) + showController(ExtensionPointAddViewController()) // Fix tableView frame — for some reason IB wants it 1pt wider than the clip view. This leads to unwanted horizontal scrolling. var rTable = tableView.frame @@ -34,7 +34,7 @@ final class FeedProvidersPreferencesViewController: NSViewController { // MARK: - NSTableViewDataSource -extension FeedProvidersPreferencesViewController: NSTableViewDataSource { +extension ExtensionPointPreferencesViewController: NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { return sortedAccounts.count @@ -47,7 +47,7 @@ extension FeedProvidersPreferencesViewController: NSTableViewDataSource { // MARK: - NSTableViewDelegate -extension FeedProvidersPreferencesViewController: NSTableViewDelegate { +extension ExtensionPointPreferencesViewController: NSTableViewDelegate { private static let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "AccountCell") @@ -70,7 +70,7 @@ extension FeedProvidersPreferencesViewController: NSTableViewDelegate { // MARK: - Private -private extension FeedProvidersPreferencesViewController { +private extension ExtensionPointPreferencesViewController { func showController(_ controller: NSViewController) { diff --git a/Mac/Preferences/FeedProviders/FeedProvidersAddTableCellView.swift b/Mac/Preferences/FeedProviders/FeedProvidersAddTableCellView.swift deleted file mode 100644 index 8d0325832..000000000 --- a/Mac/Preferences/FeedProviders/FeedProvidersAddTableCellView.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// FeedProvidersAddTableCellView.swift -// NetNewsWire -// -// Created by Maurice Parker on 4/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import AppKit - -class FeedProvidersAddTableCellView: NSTableCellView { - - @IBOutlet weak var feedProviderImageView: NSImageView? - @IBOutlet weak var feedProviderNameLabel: NSTextField? - -} diff --git a/Mac/Preferences/PreferencesWindowController.swift b/Mac/Preferences/PreferencesWindowController.swift index 9679aa9a3..143425ce5 100644 --- a/Mac/Preferences/PreferencesWindowController.swift +++ b/Mac/Preferences/PreferencesWindowController.swift @@ -24,7 +24,7 @@ private struct PreferencesToolbarItemSpec { private struct ToolbarItemIdentifier { static let General = "General" static let Accounts = "Accounts" - static let FeedProvider = "FeedProvider" + static let Extensions = "Extensions" static let Advanced = "Advanced" } @@ -40,9 +40,9 @@ class PreferencesWindowController : NSWindowController, NSToolbarDelegate { specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Accounts, name: NSLocalizedString("Accounts", comment: "Preferences"), image: NSImage(named: NSImage.userAccountsName))] - specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.FeedProvider, + specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Extensions, name: NSLocalizedString("Extensions", comment: "Preferences"), - image: AppAssets.bookmarkImage)] + image: AppAssets.extensionPreference)] // Omit the Advanced Preferences for now because the Software Update related functionality is // forbidden/non-applicable, and we can rely upon Apple to some extent for crash reports. We diff --git a/Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/Contents.json b/Mac/Resources/Assets.xcassets/extensionPointMarsEdit.imageset/Contents.json similarity index 100% rename from Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/Contents.json rename to Mac/Resources/Assets.xcassets/extensionPointMarsEdit.imageset/Contents.json diff --git a/Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/marsedit.pdf b/Mac/Resources/Assets.xcassets/extensionPointMarsEdit.imageset/marsedit.pdf similarity index 100% rename from Mac/Resources/Assets.xcassets/adapterMarsEdit.imageset/marsedit.pdf rename to Mac/Resources/Assets.xcassets/extensionPointMarsEdit.imageset/marsedit.pdf diff --git a/Mac/Resources/Assets.xcassets/extensionPointMicroblog.imageset/Contents.json b/Mac/Resources/Assets.xcassets/extensionPointMicroblog.imageset/Contents.json new file mode 100644 index 000000000..4db95b5b7 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/extensionPointMicroblog.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "micro-dot-blog.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Mac/Resources/Assets.xcassets/adapterMicroblog.imageset/micro-dot-blog.pdf b/Mac/Resources/Assets.xcassets/extensionPointMicroblog.imageset/micro-dot-blog.pdf similarity index 100% rename from Mac/Resources/Assets.xcassets/adapterMicroblog.imageset/micro-dot-blog.pdf rename to Mac/Resources/Assets.xcassets/extensionPointMicroblog.imageset/micro-dot-blog.pdf diff --git a/Mac/Resources/Assets.xcassets/adapterTwitter.imageset/Contents.json b/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json similarity index 100% rename from Mac/Resources/Assets.xcassets/adapterTwitter.imageset/Contents.json rename to Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json diff --git a/Mac/Resources/Assets.xcassets/adapterTwitter.imageset/twitter.pdf b/Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf similarity index 100% rename from Mac/Resources/Assets.xcassets/adapterTwitter.imageset/twitter.pdf rename to Mac/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 7eb562138..6ef203954 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -28,19 +28,24 @@ 5108F6D22375EED2001ABC45 /* TimelineCustomizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */; }; 5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D32375EEEF001ABC45 /* TimelinePreviewTableViewController.swift */; }; 5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D723763094001ABC45 /* TickMarkSlider.swift */; }; - 510C43ED243C0973009F70C3 /* FeedProvidersAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 510C43EC243C0973009F70C3 /* FeedProvidersAdd.xib */; }; - 510C43EE243C0973009F70C3 /* FeedProvidersAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 510C43EC243C0973009F70C3 /* FeedProvidersAdd.xib */; }; - 510C43F0243C0A80009F70C3 /* FeedProvidersAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43EF243C0A80009F70C3 /* FeedProvidersAddViewController.swift */; }; - 510C43F1243C0A80009F70C3 /* FeedProvidersAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43EF243C0A80009F70C3 /* FeedProvidersAddViewController.swift */; }; - 510C43F3243C11FE009F70C3 /* FeedProvidersAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F2243C11FE009F70C3 /* FeedProvidersAddTableCellView.swift */; }; - 510C43F4243C11FE009F70C3 /* FeedProvidersAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F2243C11FE009F70C3 /* FeedProvidersAddTableCellView.swift */; }; + 510C43ED243C0973009F70C3 /* ExtensionPointAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 510C43EC243C0973009F70C3 /* ExtensionPointAdd.xib */; }; + 510C43EE243C0973009F70C3 /* ExtensionPointAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 510C43EC243C0973009F70C3 /* ExtensionPointAdd.xib */; }; + 510C43F0243C0A80009F70C3 /* ExtensionPointAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43EF243C0A80009F70C3 /* ExtensionPointAddViewController.swift */; }; + 510C43F1243C0A80009F70C3 /* ExtensionPointAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43EF243C0A80009F70C3 /* ExtensionPointAddViewController.swift */; }; + 510C43F3243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */; }; + 510C43F4243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */; }; + 510C43F7243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; + 510C43F8243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; + 510C43F9243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; + 510C43FA243D0445009F70C3 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; + 510C43FB243D0445009F70C3 /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; 51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; }; 511076F7243BDA8100D97C8C /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; }; 511076F8243BDA8200D97C8C /* FeedProvider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 511076F9243BDA9600D97C8C /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; }; 511076FA243BDA9600D97C8C /* FeedProvider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 51107746243BEE2500D97C8C /* FeedProvidersPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107745243BEE2500D97C8C /* FeedProvidersPreferencesViewController.swift */; }; - 51107747243BEE2500D97C8C /* FeedProvidersPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107745243BEE2500D97C8C /* FeedProvidersPreferencesViewController.swift */; }; + 51107746243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */; }; + 51107747243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */; }; 5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */; }; 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; }; @@ -114,6 +119,12 @@ 51554C25228B71910055115A /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51554C30228B71A10055115A /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; 51554C31228B71A10055115A /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 515A50E6243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; + 515A50E7243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; + 515A50E8243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; + 515A5107243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */; }; + 515A5108243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */; }; + 515A5109243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */; }; 515D4FC123257A3200EE1167 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; }; @@ -1294,12 +1305,13 @@ 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineCustomizerViewController.swift; sourceTree = ""; }; 5108F6D32375EEEF001ABC45 /* TimelinePreviewTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePreviewTableViewController.swift; sourceTree = ""; }; 5108F6D723763094001ABC45 /* TickMarkSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TickMarkSlider.swift; sourceTree = ""; }; - 510C43EC243C0973009F70C3 /* FeedProvidersAdd.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FeedProvidersAdd.xib; sourceTree = ""; }; - 510C43EF243C0A80009F70C3 /* FeedProvidersAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedProvidersAddViewController.swift; sourceTree = ""; }; - 510C43F2243C11FE009F70C3 /* FeedProvidersAddTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedProvidersAddTableCellView.swift; sourceTree = ""; }; + 510C43EC243C0973009F70C3 /* ExtensionPointAdd.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExtensionPointAdd.xib; sourceTree = ""; }; + 510C43EF243C0A80009F70C3 /* ExtensionPointAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointAddViewController.swift; sourceTree = ""; }; + 510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointAddTableCellView.swift; sourceTree = ""; }; + 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPoint.swift; sourceTree = ""; }; 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; 5110766A243BCE0400D97C8C /* FeedProvider.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FeedProvider.xcodeproj; path = Frameworks/FeedProvider/FeedProvider.xcodeproj; sourceTree = SOURCE_ROOT; }; - 51107745243BEE2500D97C8C /* FeedProvidersPreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedProvidersPreferencesViewController.swift; sourceTree = ""; }; + 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointPreferencesViewController.swift; sourceTree = ""; }; 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorIconHeaderView.swift; sourceTree = ""; }; 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = ""; }; 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = ""; }; @@ -1347,6 +1359,8 @@ 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = ""; }; 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddControllerType.swift; sourceTree = ""; }; 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SyncDatabase.xcodeproj; path = Frameworks/SyncDatabase/SyncDatabase.xcodeproj; sourceTree = SOURCE_ROOT; }; + 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointManager.swift; sourceTree = ""; }; + 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TwitterFeedProvider+Extensions.swift"; sourceTree = ""; }; 515D4FCB2325815A00EE1167 /* SafariExt.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = SafariExt.js; sourceTree = ""; }; 515D4FCD2325909200EE1167 /* NetNewsWire_iOS_ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_ShareExtension.entitlements; sourceTree = ""; }; 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSshareextension_target.xcconfig; sourceTree = ""; }; @@ -1804,6 +1818,18 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 510C43F5243D0325009F70C3 /* ExtensionPoints */ = { + isa = PBXGroup; + children = ( + 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */, + 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */, + 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */, + 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */, + 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */, + ); + path = ExtensionPoints; + sourceTree = ""; + }; 5110766B243BCE0400D97C8C /* Products */ = { isa = PBXGroup; children = ( @@ -1812,15 +1838,15 @@ name = Products; sourceTree = ""; }; - 51107744243BEDD300D97C8C /* FeedProviders */ = { + 51107744243BEDD300D97C8C /* ExtensionPoints */ = { isa = PBXGroup; children = ( - 510C43EC243C0973009F70C3 /* FeedProvidersAdd.xib */, - 510C43F2243C11FE009F70C3 /* FeedProvidersAddTableCellView.swift */, - 510C43EF243C0A80009F70C3 /* FeedProvidersAddViewController.swift */, - 51107745243BEE2500D97C8C /* FeedProvidersPreferencesViewController.swift */, + 510C43EC243C0973009F70C3 /* ExtensionPointAdd.xib */, + 510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */, + 510C43EF243C0A80009F70C3 /* ExtensionPointAddViewController.swift */, + 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */, ); - path = FeedProviders; + path = ExtensionPoints; sourceTree = ""; }; 511D43CE231FA51100FB1562 /* Resources */ = { @@ -2330,8 +2356,6 @@ 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */, 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */, 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */, - 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */, - 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */, ); path = Commands; sourceTree = ""; @@ -2379,16 +2403,21 @@ name = "Add Feed"; sourceTree = ""; }; - 849A97561ED9EB0D007D329B /* Data */ = { + 849A97561ED9EB0D007D329B /* Extensions */ = { isa = PBXGroup; children = ( 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */, 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */, 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */, 5108F6B52375E612001ABC45 /* CacheCleaner.swift */, + 516AE9DE2372269A007DEEAA /* IconImage.swift */, + 849A97971ED9EFAA007D329B /* Node-Extensions.swift */, + 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */, + B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */, + 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */, 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */, ); - path = Data; + path = Extensions; sourceTree = ""; }; 849A975F1ED9EB95007D329B /* Sidebar */ = { @@ -2468,18 +2497,6 @@ path = Shared/ArticleStyles; sourceTree = SOURCE_ROOT; }; - 849A97961ED9EFAA007D329B /* Extensions */ = { - isa = PBXGroup; - children = ( - 849A97971ED9EFAA007D329B /* Node-Extensions.swift */, - 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */, - 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */, - 516AE9DE2372269A007DEEAA /* IconImage.swift */, - B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */, - ); - path = Extensions; - sourceTree = ""; - }; 849C64571ED37A5D003D8FC0 = { isa = PBXGroup; children = ( @@ -2635,9 +2652,9 @@ 845213211FCA5B10003B6E93 /* Images */, 8426119C1FCB6ED40086A189 /* HTMLMetadata */, 5183CCEA226F70350010922C /* Timer */, - 849A97561ED9EB0D007D329B /* Data */, 512E08DD22687FA000BDCFDD /* Tree */, - 849A97961ED9EFAA007D329B /* Extensions */, + 849A97561ED9EB0D007D329B /* Extensions */, + 510C43F5243D0325009F70C3 /* ExtensionPoints */, 513228F1233037620033D4ED /* Network */, 511D43CE231FA51100FB1562 /* Resources */, ); @@ -2654,7 +2671,7 @@ 84C9FC6A22629E1200D921D6 /* Advanced */, 84C9FC6C22629E1200D921D6 /* General */, 84C9FC6F22629E1200D921D6 /* Accounts */, - 51107744243BEDD300D97C8C /* FeedProviders */, + 51107744243BEDD300D97C8C /* ExtensionPoints */, ); path = Preferences; sourceTree = ""; @@ -3551,7 +3568,7 @@ 65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */, 65ED4052235DEF6C0081F399 /* template.html in Resources */, 65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */, - 510C43EE243C0973009F70C3 /* FeedProvidersAdd.xib in Resources */, + 510C43EE243C0973009F70C3 /* ExtensionPointAdd.xib in Resources */, 65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */, 65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */, 65ED4057235DEF6C0081F399 /* AccountsDetail.xib in Resources */, @@ -3666,7 +3683,7 @@ 3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */, 55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */, 49F40DF82335B71000552BF4 /* newsfoot.js in Resources */, - 510C43ED243C0973009F70C3 /* FeedProvidersAdd.xib in Resources */, + 510C43ED243C0973009F70C3 /* ExtensionPointAdd.xib in Resources */, BDCB516724282C8A00102A80 /* AccountsNewsBlur.xib in Resources */, 5103A9982421643300410853 /* blank.html in Resources */, 84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */, @@ -3846,7 +3863,7 @@ files = ( 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */, 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */, - 51107747243BEE2500D97C8C /* FeedProvidersPreferencesViewController.swift in Sources */, + 51107747243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */, 65ED3FB9235DEF6C0081F399 /* IconView.swift in Sources */, 65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */, 65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */, @@ -3904,6 +3921,7 @@ 65ED3FF0235DEF6C0081F399 /* ArticleStylesManager.swift in Sources */, 65ED3FF1235DEF6C0081F399 /* DetailContainerView.swift in Sources */, 65ED3FF2235DEF6C0081F399 /* SharingServiceDelegate.swift in Sources */, + 515A50E7243D07A90089E588 /* ExtensionPointManager.swift in Sources */, 65ED3FF3235DEF6C0081F399 /* ArticleSorter.swift in Sources */, 65ED3FF4235DEF6C0081F399 /* TimelineViewController+ContextualMenus.swift in Sources */, 65ED3FF5235DEF6C0081F399 /* ArticleStringFormatter.swift in Sources */, @@ -3939,6 +3957,7 @@ 65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */, 65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */, 65ED4014235DEF6C0081F399 /* PasteboardWebFeed.swift in Sources */, + 510C43F8243D035C009F70C3 /* ExtensionPoint.swift in Sources */, 65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */, 65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */, 65ED4017235DEF6C0081F399 /* AppDelegate.swift in Sources */, @@ -3949,7 +3968,7 @@ 65ED401C235DEF6C0081F399 /* FaviconGenerator.swift in Sources */, 65ED401D235DEF6C0081F399 /* RefreshInterval.swift in Sources */, 65ED401E235DEF6C0081F399 /* TimelineCellData.swift in Sources */, - 510C43F1243C0A80009F70C3 /* FeedProvidersAddViewController.swift in Sources */, + 510C43F1243C0A80009F70C3 /* ExtensionPointAddViewController.swift in Sources */, 65ED401F235DEF6C0081F399 /* BuiltinSmartFeedInspectorViewController.swift in Sources */, 65ED4020235DEF6C0081F399 /* AppDelegate+Scriptability.swift in Sources */, 65ED4021235DEF6C0081F399 /* NNW3Document.swift in Sources */, @@ -3964,6 +3983,7 @@ 65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */, 65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */, 65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */, + 515A5108243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */, 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */, 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */, 65ED402D235DEF6C0081F399 /* DetailStatusBarView.swift in Sources */, @@ -3984,7 +4004,7 @@ 65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */, 65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */, 65ED403E235DEF6C0081F399 /* TimelineCellAppearance.swift in Sources */, - 510C43F4243C11FE009F70C3 /* FeedProvidersAddTableCellView.swift in Sources */, + 510C43F4243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift in Sources */, 65ED403F235DEF6C0081F399 /* ArticleRenderer.swift in Sources */, 65ED4040235DEF6C0081F399 /* GeneralPrefencesViewController.swift in Sources */, 179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */, @@ -4008,6 +4028,7 @@ 512DD4C92430086400C17B1F /* CloudKitAccountViewController.swift in Sources */, 840D617F2029031C009BC708 /* AppDelegate.swift in Sources */, 51236339236915B100951F16 /* RoundedProgressView.swift in Sources */, + 510C43F9243D035C009F70C3 /* ExtensionPoint.swift in Sources */, 512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */, 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */, 51EF0F79227716380050506E /* ColorHash.swift in Sources */, @@ -4016,6 +4037,7 @@ B2B80778239C4C7000F191E0 /* RSImage-AppIcons.swift in Sources */, 518ED21D23D0F26000E0A862 /* UIViewController-Extensions.swift in Sources */, 51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */, + 510C43FB243D0445009F70C3 /* SendToMicroBlogCommand.swift in Sources */, 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */, @@ -4034,6 +4056,7 @@ 513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */, 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */, 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, + 515A5109243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 51AB8AB323B7F4C6008F147D /* WebViewController.swift in Sources */, 516A09392360A2AE00EAE89B /* SettingsAccountTableViewCell.swift in Sources */, @@ -4054,6 +4077,7 @@ 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, + 510C43FA243D0445009F70C3 /* SendToMarsEditCommand.swift in Sources */, 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */, 51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, @@ -4127,6 +4151,7 @@ 514219372352510100E07E2C /* ImageScrollView.swift in Sources */, 516AE9B32371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift in Sources */, 51DC370B2405BC9A0095D371 /* PreloadedWebView.swift in Sources */, + 515A50E8243D07A90089E588 /* ExtensionPointManager.swift in Sources */, C5A6ED5223C9AF4300AB6BE2 /* TitleActivityItemSource.swift in Sources */, 51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, @@ -4198,11 +4223,12 @@ D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */, 8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, + 515A50E6243D07A90089E588 /* ExtensionPointManager.swift in Sources */, 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 849A97651ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift in Sources */, 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 51FE10092346739D0056195D /* ActivityType.swift in Sources */, - 510C43F3243C11FE009F70C3 /* FeedProvidersAddTableCellView.swift in Sources */, + 510C43F3243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, 8426118A1FCB67AA0086A189 /* WebFeedIconDownloader.swift in Sources */, 84C9FC7B22629E1200D921D6 /* PreferencesControlsBackgroundView.swift in Sources */, @@ -4223,7 +4249,7 @@ D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */, 849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */, 8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */, - 51107746243BEE2500D97C8C /* FeedProvidersPreferencesViewController.swift in Sources */, + 51107746243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */, 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */, FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */, 84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */, @@ -4240,6 +4266,7 @@ 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */, 84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */, 845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */, + 515A5107243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */, 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */, 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */, 511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */, @@ -4279,7 +4306,7 @@ 518651B223555EB20078E021 /* NNW3Document.swift in Sources */, D5F4EDB5200744A700B9E363 /* ScriptingObject.swift in Sources */, D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */, - 510C43F0243C0A80009F70C3 /* FeedProvidersAddViewController.swift in Sources */, + 510C43F0243C0A80009F70C3 /* ExtensionPointAddViewController.swift in Sources */, 849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */, 84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */, 849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */, @@ -4301,6 +4328,7 @@ 849ADEE42359817E000E1B81 /* NNW3ImportController.swift in Sources */, 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */, 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */, + 510C43F7243D035C009F70C3 /* ExtensionPoint.swift in Sources */, 845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */, D5F4EDB720074D6500B9E363 /* WebFeed+Scriptability.swift in Sources */, 84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */, diff --git a/Shared/ExtensionPoints/ExtensionPoint.swift b/Shared/ExtensionPoints/ExtensionPoint.swift new file mode 100644 index 000000000..06c6211c1 --- /dev/null +++ b/Shared/ExtensionPoints/ExtensionPoint.swift @@ -0,0 +1,20 @@ +// +// ExtensionPoint.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import RSCore + +protocol ExtensionPoint { + + /// The title of the command. + var title: String { get } + + /// The template image for the command. + var templateImage: RSImage { get } + +} diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift new file mode 100644 index 000000000..c7502fc19 --- /dev/null +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -0,0 +1,49 @@ +// +// ExtensionPointManager.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import FeedProvider +import RSCore + +struct ExtensionPointManager { + + static let shared = ExtensionPointManager() + + let marsEdit = SendToMarsEditCommand() + let microblog = SendToMicroBlogCommand() + let twitter = TwitterFeedProvider() + + let availableExtensionPoints: [ExtensionPoint] + let activeSendToCommands: [SendToCommand] + let activeFeedProviders: [FeedProvider] + + init() { + #if os(macOS) + #if DEBUG + availableExtensionPoints = [marsEdit, microblog, twitter] + activeSendToCommands = [marsEdit, microblog] + activeFeedProviders = [twitter] + #else + availableExtensionPoints = [marsEdit, microblog, twitter] + activeSendToCommands = [marsEdit, microblog] + activeFeedProviders = [twitter] + #endif + #else + #if DEBUG + availableExtensionPoints = [twitter] + activeSendToCommands = []() + activeFeedProviders = [twitter] + #else + availableExtensionPoints = [twitter] + activeSendToCommands = []() + activeFeedProviders = [twitter] + #endif + #endif + } + +} diff --git a/Shared/ExtensionPoints/ExtensionPointType.swift b/Shared/ExtensionPoints/ExtensionPointType.swift new file mode 100644 index 000000000..1b6fb845b --- /dev/null +++ b/Shared/ExtensionPoints/ExtensionPointType.swift @@ -0,0 +1,14 @@ +// +// ExtensionPointType.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation + +enum ExtensionPoint: Int, Codable { + case sentToCommand = 1 + case feedProvider = 2 +} diff --git a/Shared/Commands/SendToMarsEditCommand.swift b/Shared/ExtensionPoints/SendToMarsEditCommand.swift similarity index 90% rename from Shared/Commands/SendToMarsEditCommand.swift rename to Shared/ExtensionPoints/SendToMarsEditCommand.swift index 7b50f07fa..d00944600 100644 --- a/Shared/Commands/SendToMarsEditCommand.swift +++ b/Shared/ExtensionPoints/SendToMarsEditCommand.swift @@ -10,14 +10,16 @@ import AppKit import RSCore import Articles -final class SendToMarsEditCommand: SendToCommand { +final class SendToMarsEditCommand: ExtensionPoint, SendToCommand { - let title = "MarsEdit" + let title = NSLocalizedString("MarsEdit", comment: "MarsEdit") + let templateImage = AppAssets.extensionPointMarsEdit var image: NSImage? { return appToUse()?.icon ?? nil } + private let marsEditApps = [UserApp(bundleID: "com.red-sweater.marsedit4"), UserApp(bundleID: "com.red-sweater.marsedit")] func canSendObject(_ object: Any?, selectedText: String?) -> Bool { diff --git a/Shared/Commands/SendToMicroBlogCommand.swift b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift similarity index 91% rename from Shared/Commands/SendToMicroBlogCommand.swift rename to Shared/ExtensionPoints/SendToMicroBlogCommand.swift index 71dca639f..4c4961942 100644 --- a/Shared/Commands/SendToMicroBlogCommand.swift +++ b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift @@ -12,9 +12,10 @@ import RSCore // Not undoable. -final class SendToMicroBlogCommand: SendToCommand { +final class SendToMicroBlogCommand: ExtensionPoint, SendToCommand { - let title = "Micro.blog" + let title = NSLocalizedString("Micro.blog", comment: "Micro.blog") + let templateImage = AppAssets.extensionPointMicroblog var image: NSImage? { return microBlogApp.icon diff --git a/Shared/ExtensionPoints/TwitterFeedProvider+Extensions.swift b/Shared/ExtensionPoints/TwitterFeedProvider+Extensions.swift new file mode 100644 index 000000000..560ff7ff4 --- /dev/null +++ b/Shared/ExtensionPoints/TwitterFeedProvider+Extensions.swift @@ -0,0 +1,23 @@ +// +// TwitterFeedProvider+Extensions.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import FeedProvider +import RSCore + +extension TwitterFeedProvider: ExtensionPoint { + + var title: String { + return NSLocalizedString("Twitter", comment: "Twitter") + } + + var templateImage: RSImage { + return AppAssets.extensionPointTwitter + } + +} diff --git a/Shared/Data/AddWebFeedDefaultContainer.swift b/Shared/Extensions/AddWebFeedDefaultContainer.swift similarity index 100% rename from Shared/Data/AddWebFeedDefaultContainer.swift rename to Shared/Extensions/AddWebFeedDefaultContainer.swift diff --git a/Shared/Data/ArticleStringFormatter.swift b/Shared/Extensions/ArticleStringFormatter.swift similarity index 100% rename from Shared/Data/ArticleStringFormatter.swift rename to Shared/Extensions/ArticleStringFormatter.swift diff --git a/Shared/Data/ArticleUtilities.swift b/Shared/Extensions/ArticleUtilities.swift similarity index 100% rename from Shared/Data/ArticleUtilities.swift rename to Shared/Extensions/ArticleUtilities.swift diff --git a/Shared/Data/CacheCleaner.swift b/Shared/Extensions/CacheCleaner.swift similarity index 100% rename from Shared/Data/CacheCleaner.swift rename to Shared/Extensions/CacheCleaner.swift diff --git a/Shared/Data/SmallIconProvider.swift b/Shared/Extensions/SmallIconProvider.swift similarity index 100% rename from Shared/Data/SmallIconProvider.swift rename to Shared/Extensions/SmallIconProvider.swift From b0781c6df49697b2757e58ea775f09f933d14fe6 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 7 Apr 2020 18:18:27 -0500 Subject: [PATCH 006/108] Add missing xcconfig files --- .../xcconfig/FeedProvider_project.xcconfig | 61 +++++++++++++++++++ .../FeedProvider_project_debug.xcconfig | 15 +++++ .../FeedProvider_project_release.xcconfig | 9 +++ .../FeedProvider_project_test.xcconfig | 3 + .../xcconfig/FeedProvider_target.xcconfig | 13 ++++ 5 files changed, 101 insertions(+) create mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_project.xcconfig create mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_project_debug.xcconfig create mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_project_release.xcconfig create mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_project_test.xcconfig create mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_target.xcconfig diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_project.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_project.xcconfig new file mode 100644 index 000000000..573284972 --- /dev/null +++ b/Frameworks/FeedProvider/xcconfig/FeedProvider_project.xcconfig @@ -0,0 +1,61 @@ +CODE_SIGN_IDENTITY = Developer ID Application +DEVELOPMENT_TEAM = M8L2WTLA8W +CODE_SIGN_STYLE = Manual +PROVISIONING_PROFILE_SPECIFIER = + +// See the notes in NetNewsWire_target.xcconfig on why the +// DeveloperSettings.xcconfig is #included here + +#include? "../../../SharedXcodeSettings/DeveloperSettings.xcconfig" + +SDKROOT = macosx +MACOSX_DEPLOYMENT_TARGET = 10.14 +IPHONEOS_DEPLOYMENT_TARGET = 13.0 +SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator + +CLANG_ENABLE_OBJC_WEAK = YES +SWIFT_VERSION = 5.1 +COMBINE_HIDPI_IMAGES = YES + +COPY_PHASE_STRIP = NO +ALWAYS_SEARCH_USER_PATHS = NO +CURRENT_PROJECT_VERSION = 1 +VERSION_INFO_PREFIX = +VERSIONING_SYSTEM = apple-generic +GCC_NO_COMMON_BLOCKS = YES +GCC_C_LANGUAGE_STANDARD = gnu99 +CLANG_CXX_LANGUAGE_STANDARD = gnu++0x +CLANG_CXX_LIBRARY = libc++ +CLANG_ENABLE_MODULES = YES +CLANG_ENABLE_OBJC_ARC = YES +ENABLE_STRICT_OBJC_MSGSEND = YES +CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES +CLANG_WARN_DOCUMENTATION_COMMENTS = YES +CLANG_WARN_EMPTY_BODY = YES +CLANG_WARN_BOOL_CONVERSION = YES +CLANG_WARN_CONSTANT_CONVERSION = YES +GCC_WARN_64_TO_32_BIT_CONVERSION = YES +CLANG_WARN_ENUM_CONVERSION = YES +CLANG_WARN_INT_CONVERSION = YES +CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES +CLANG_WARN_INFINITE_RECURSION = YES +GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE +CLANG_WARN_UNREACHABLE_CODE = YES +GCC_WARN_UNUSED_FUNCTION = YES +GCC_WARN_UNUSED_VARIABLE = YES +CLANG_WARN_RANGE_LOOP_ANALYSIS = YES +CLANG_WARN_SUSPICIOUS_MOVE = YES +CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_OBJC_LITERAL_CONVERSION = YES +CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +CLANG_ANALYZER_NONNULL = YES +CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE +SWIFT_SWIFT3_OBJC_INFERENCE = Off diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_project_debug.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_project_debug.xcconfig new file mode 100644 index 000000000..beca2742a --- /dev/null +++ b/Frameworks/FeedProvider/xcconfig/FeedProvider_project_debug.xcconfig @@ -0,0 +1,15 @@ +#include "./FeedProvider_project.xcconfig" + +DEBUG_INFORMATION_FORMAT = dwarf +ENABLE_TESTABILITY = YES +GCC_DYNAMIC_NO_PIC = NO +GCC_OPTIMIZATION_LEVEL = 0 +GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 $(inherited) + +MTL_ENABLE_DEBUG_INFO = YES +SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG +SWIFT_COMPILATION_MODE = singlefile +SWIFT_OPTIMIZATION_LEVEL = -Onone +ONLY_ACTIVE_ARCH = YES + + diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_project_release.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_project_release.xcconfig new file mode 100644 index 000000000..781ad3437 --- /dev/null +++ b/Frameworks/FeedProvider/xcconfig/FeedProvider_project_release.xcconfig @@ -0,0 +1,9 @@ +#include "./FeedProvider_project.xcconfig" + +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym +ENABLE_NS_ASSERTIONS = NO + +MTL_ENABLE_DEBUG_INFO = NO +SWIFT_OPTIMIZATION_LEVEL = -O + +SWIFT_COMPILATION_MODE = wholemodule diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_project_test.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_project_test.xcconfig new file mode 100644 index 000000000..5f9ae96a0 --- /dev/null +++ b/Frameworks/FeedProvider/xcconfig/FeedProvider_project_test.xcconfig @@ -0,0 +1,3 @@ +#include "./FeedProvider_project_debug.xcconfig" + +OTHER_SWIFT_FLAGS = -DTEST $(inherited) diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_target.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_target.xcconfig new file mode 100644 index 000000000..ece936457 --- /dev/null +++ b/Frameworks/FeedProvider/xcconfig/FeedProvider_target.xcconfig @@ -0,0 +1,13 @@ +INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks +SKIP_INSTALL = YES +DYLIB_COMPATIBILITY_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +DYLIB_INSTALL_NAME_BASE = @rpath +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks +DEFINES_MODULE = YES +FRAMEWORK_VERSION = A +INFOPLIST_FILE = Info.plist +PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.FeedProvider +PRODUCT_NAME = $(TARGET_NAME) +CLANG_ENABLE_MODULES = YES +APPLICATION_EXTENSION_API_ONLY = YES From 14189b19e92a83f558edfa8ce928e189268aac94 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 8 Apr 2020 10:12:06 -0500 Subject: [PATCH 007/108] Change extension point so that multiple of the same type can exist. --- .../Twitter/TwitterFeedProvider.swift | 6 +- .../ExtensionPointAddViewController.swift | 12 ++-- Shared/ExtensionPoints/ExtensionPoint.swift | 63 +++++++++++++++++-- .../ExtensionPointManager.swift | 26 +++----- .../SendToMarsEditCommand.swift | 6 +- .../SendToMicroBlogCommand.swift | 6 +- .../TwitterFeedProvider+Extensions.swift | 11 ++-- 7 files changed, 87 insertions(+), 43 deletions(-) diff --git a/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift index b4858d0c5..125e47b3c 100644 --- a/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -10,8 +10,10 @@ import Foundation public struct TwitterFeedProvider: FeedProvider { - public init() { - + public var username: String + + public init(username: String) { + self.username = username } } diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift index fd1c1fbdc..9dd556daa 100644 --- a/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift @@ -13,7 +13,7 @@ class ExtensionPointAddViewController: NSViewController { @IBOutlet weak var tableView: NSTableView! - private var availableExtensionPoints = [ExtensionPoint]() + private var availableExtensionPointTypes = [ExtensionPointType]() private var extensionPointAddWindowController: NSWindowController? init() { @@ -28,7 +28,7 @@ class ExtensionPointAddViewController: NSViewController { super.viewDidLoad() tableView.dataSource = self tableView.delegate = self - availableExtensionPoints = ExtensionPointManager.shared.availableExtensionPoints + availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes } } @@ -38,7 +38,7 @@ class ExtensionPointAddViewController: NSViewController { extension ExtensionPointAddViewController: NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { - return availableExtensionPoints.count + return availableExtensionPointTypes.count } func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { @@ -55,9 +55,9 @@ extension ExtensionPointAddViewController: NSTableViewDelegate { func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? ExtensionPointAddTableCellView { - let extensionPoint = availableExtensionPoints[row] - cell.titleLabel?.stringValue = extensionPoint.title - cell.imageView?.image = extensionPoint.templateImage + let extensionPointType = availableExtensionPointTypes[row] + cell.titleLabel?.stringValue = extensionPointType.title + cell.imageView?.image = extensionPointType.templateImage return cell } return nil diff --git a/Shared/ExtensionPoints/ExtensionPoint.swift b/Shared/ExtensionPoints/ExtensionPoint.swift index 06c6211c1..fe5440141 100644 --- a/Shared/ExtensionPoints/ExtensionPoint.swift +++ b/Shared/ExtensionPoints/ExtensionPoint.swift @@ -9,12 +9,65 @@ import Foundation import RSCore -protocol ExtensionPoint { +enum ExtensionPointType { + case marsEdit + case microblog + case twitter - /// The title of the command. - var title: String { get } + var title: String { + switch self { + case .marsEdit: + return NSLocalizedString("MarsEdit", comment: "MarsEdit") + case .microblog: + return NSLocalizedString("Micro.blog", comment: "Micro.blog") + case .twitter: + return NSLocalizedString("Twitter", comment: "Twitter") + } - /// The template image for the command. - var templateImage: RSImage { get } + } + + var templateImage: RSImage { + switch self { + case .marsEdit: + return AppAssets.extensionPointMarsEdit + case .microblog: + return AppAssets.extensionPointMicroblog + case .twitter: + return AppAssets.extensionPointTwitter + } + } } + +enum ExtensionPointIdentifer { + case marsEdit + case microblog + case twitter(String) + + var title: String { + switch self { + case .marsEdit: + return ExtensionPointType.marsEdit.title + case .microblog: + return ExtensionPointType.microblog.title + case .twitter(let username): + return "\(ExtensionPointType.microblog.title) (\(username))" + } + } + +} + +protocol ExtensionPoint { + + var extensionPointType: ExtensionPointType { get } + var extensionPointID: ExtensionPointIdentifer { get } + +} + +extension ExtensionPoint { + + var title: String { + return extensionPointID.title + } + +} diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift index c7502fc19..ac479d47d 100644 --- a/Shared/ExtensionPoints/ExtensionPointManager.swift +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -14,34 +14,22 @@ struct ExtensionPointManager { static let shared = ExtensionPointManager() - let marsEdit = SendToMarsEditCommand() - let microblog = SendToMicroBlogCommand() - let twitter = TwitterFeedProvider() - - let availableExtensionPoints: [ExtensionPoint] - let activeSendToCommands: [SendToCommand] - let activeFeedProviders: [FeedProvider] + let availableExtensionPointTypes: [ExtensionPointType] +// let activeSendToCommands: [SendToCommand] +// let activeFeedProviders: [FeedProvider] init() { #if os(macOS) #if DEBUG - availableExtensionPoints = [marsEdit, microblog, twitter] - activeSendToCommands = [marsEdit, microblog] - activeFeedProviders = [twitter] + availableExtensionPointTypes = [.marsEdit, .microblog, .twitter] #else - availableExtensionPoints = [marsEdit, microblog, twitter] - activeSendToCommands = [marsEdit, microblog] - activeFeedProviders = [twitter] + availableExtensionPointTypes = [.marsEdit, .microblog, .twitter] #endif #else #if DEBUG - availableExtensionPoints = [twitter] - activeSendToCommands = []() - activeFeedProviders = [twitter] + availableExtensionPoints = [.twitter] #else - availableExtensionPoints = [twitter] - activeSendToCommands = []() - activeFeedProviders = [twitter] + availableExtensionPoints = [.twitter] #endif #endif } diff --git a/Shared/ExtensionPoints/SendToMarsEditCommand.swift b/Shared/ExtensionPoints/SendToMarsEditCommand.swift index d00944600..25b094e82 100644 --- a/Shared/ExtensionPoints/SendToMarsEditCommand.swift +++ b/Shared/ExtensionPoints/SendToMarsEditCommand.swift @@ -12,9 +12,9 @@ import Articles final class SendToMarsEditCommand: ExtensionPoint, SendToCommand { - let title = NSLocalizedString("MarsEdit", comment: "MarsEdit") - let templateImage = AppAssets.extensionPointMarsEdit - + let extensionPointType = ExtensionPointType.marsEdit + let extensionPointID = ExtensionPointIdentifer.marsEdit + var image: NSImage? { return appToUse()?.icon ?? nil } diff --git a/Shared/ExtensionPoints/SendToMicroBlogCommand.swift b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift index 4c4961942..e304d26d3 100644 --- a/Shared/ExtensionPoints/SendToMicroBlogCommand.swift +++ b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift @@ -14,9 +14,9 @@ import RSCore final class SendToMicroBlogCommand: ExtensionPoint, SendToCommand { - let title = NSLocalizedString("Micro.blog", comment: "Micro.blog") - let templateImage = AppAssets.extensionPointMicroblog - + let extensionPointType = ExtensionPointType.microblog + let extensionPointID = ExtensionPointIdentifer.microblog + var image: NSImage? { return microBlogApp.icon } diff --git a/Shared/ExtensionPoints/TwitterFeedProvider+Extensions.swift b/Shared/ExtensionPoints/TwitterFeedProvider+Extensions.swift index 560ff7ff4..fc8cfe5b8 100644 --- a/Shared/ExtensionPoints/TwitterFeedProvider+Extensions.swift +++ b/Shared/ExtensionPoints/TwitterFeedProvider+Extensions.swift @@ -12,12 +12,13 @@ import RSCore extension TwitterFeedProvider: ExtensionPoint { - var title: String { - return NSLocalizedString("Twitter", comment: "Twitter") + var extensionPointType: ExtensionPointType { + return ExtensionPointType.twitter + } + + var extensionPointID: ExtensionPointIdentifer { + return ExtensionPointIdentifer.twitter(username) } - var templateImage: RSImage { - return AppAssets.extensionPointTwitter - } } From 61b755486aae5a6e934db4d3f95461874842d11d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 8 Apr 2020 13:46:15 -0500 Subject: [PATCH 008/108] Flesh out the ExtensionPointManager a little --- Mac/AppDefaults.swift | 10 ++++ .../SharingServicePickerDelegate.swift | 9 +-- Shared/ExtensionPoints/ExtensionPoint.swift | 47 +++++++++++++++- .../ExtensionPointManager.swift | 55 +++++++++++++++++-- 4 files changed, 109 insertions(+), 12 deletions(-) diff --git a/Mac/AppDefaults.swift b/Mac/AppDefaults.swift index 89d42b928..f7e362c68 100644 --- a/Mac/AppDefaults.swift +++ b/Mac/AppDefaults.swift @@ -19,6 +19,7 @@ struct AppDefaults { struct Key { static let firstRunDate = "firstRunDate" static let windowState = "windowState" + static let activeExtensionPointIDs = "activeExtensionPointIDs" static let lastImageCacheFlushDate = "lastImageCacheFlushDate" static let sidebarFontSize = "sidebarFontSize" static let timelineFontSize = "timelineFontSize" @@ -72,6 +73,15 @@ struct AppDefaults { } } + static var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { + get { + return UserDefaults.standard.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] + } + set { + UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs) + } + } + static var lastImageCacheFlushDate: Date? { get { return date(for: Key.lastImageCacheFlushDate) diff --git a/Mac/MainWindow/SharingServicePickerDelegate.swift b/Mac/MainWindow/SharingServicePickerDelegate.swift index 45cbd9eb7..1a5349a42 100644 --- a/Mac/MainWindow/SharingServicePickerDelegate.swift +++ b/Mac/MainWindow/SharingServicePickerDelegate.swift @@ -18,25 +18,20 @@ import RSCore } func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] { - return proposedServices + SharingServicePickerDelegate.customSharingServices(for: items) - } func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, delegateFor sharingService: NSSharingService) -> NSSharingServiceDelegate? { return sharingServiceDelegate } - private static let sendToCommands: [SendToCommand] = { - return [SendToMicroBlogCommand(), SendToMarsEditCommand()] - }() - static func customSharingServices(for items: [Any]) -> [NSSharingService] { - let customServices = sendToCommands.compactMap { (sendToCommand) -> NSSharingService? in + let customServices = ExtensionPointManager.shared.activeSendToCommands.compactMap { (sendToCommand) -> NSSharingService? in guard let object = items.first else { return nil } + guard sendToCommand.canSendObject(object, selectedText: nil) else { return nil } diff --git a/Shared/ExtensionPoints/ExtensionPoint.swift b/Shared/ExtensionPoints/ExtensionPoint.swift index fe5440141..f41e68fe6 100644 --- a/Shared/ExtensionPoints/ExtensionPoint.swift +++ b/Shared/ExtensionPoints/ExtensionPoint.swift @@ -39,7 +39,7 @@ enum ExtensionPointType { } -enum ExtensionPointIdentifer { +enum ExtensionPointIdentifer: Hashable { case marsEdit case microblog case twitter(String) @@ -55,6 +55,51 @@ enum ExtensionPointIdentifer { } } + public var userInfo: [AnyHashable: AnyHashable] { + switch self { + case .marsEdit: + return [ + "type": "marsEdit" + ] + case .microblog: + return [ + "type": "microblog" + ] + case .twitter(let username): + return [ + "type": "feed", + "username": username + ] + } + } + + public init?(userInfo: [AnyHashable: AnyHashable]) { + guard let type = userInfo["type"] as? String else { return nil } + + switch type { + case "marsEdit": + self = ExtensionPointIdentifer.marsEdit + case "microblog": + self = ExtensionPointIdentifer.microblog + case "twitter": + guard let username = userInfo["username"] as? String else { return nil } + self = ExtensionPointIdentifer.twitter(username) + default: + return nil + } + } + + public func hash(into hasher: inout Hasher) { + switch self { + case .marsEdit: + hasher.combine("marsEdit") + case .microblog: + hasher.combine("microblog") + case .twitter(let username): + hasher.combine("twitter") + hasher.combine(username) + } + } } protocol ExtensionPoint { diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift index ac479d47d..831dd449f 100644 --- a/Shared/ExtensionPoints/ExtensionPointManager.swift +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -10,13 +10,20 @@ import Foundation import FeedProvider import RSCore -struct ExtensionPointManager { +final class ExtensionPointManager { static let shared = ExtensionPointManager() - + + var activeExtensionPoints = [ExtensionPointIdentifer: ExtensionPoint]() let availableExtensionPointTypes: [ExtensionPointType] -// let activeSendToCommands: [SendToCommand] -// let activeFeedProviders: [FeedProvider] + + var activeSendToCommands: [SendToCommand] { + return activeExtensionPoints.values.compactMap({ return $0 as? SendToCommand }) + } + + var activeFeedProviders: [FeedProvider] { + return activeExtensionPoints.values.compactMap({ return $0 as? FeedProvider }) + } init() { #if os(macOS) @@ -32,6 +39,46 @@ struct ExtensionPointManager { availableExtensionPoints = [.twitter] #endif #endif + loadExtensionPointIDs() + } + + func activateExtensionPoint(_ extensionPointID: ExtensionPointIdentifer) { + activeExtensionPoints[extensionPointID] = extensionPoint(for: extensionPointID) + saveExtensionPointIDs() + } + + func deactivateExtensionPoint(_ extensionPointID: ExtensionPointIdentifer) { + activeExtensionPoints[extensionPointID] = nil + saveExtensionPointIDs() + } + +} + +private extension ExtensionPointManager { + + func loadExtensionPointIDs() { + if let extensionPointUserInfos = AppDefaults.activeExtensionPointIDs { + for extensionPointUserInfo in extensionPointUserInfos { + if let extensionPointID = ExtensionPointIdentifer(userInfo: extensionPointUserInfo) { + activeExtensionPoints[extensionPointID] = extensionPoint(for: extensionPointID) + } + } + } + } + + func saveExtensionPointIDs() { + AppDefaults.activeExtensionPointIDs = activeExtensionPoints.keys.map({ $0.userInfo }) + } + + func extensionPoint(for extensionPointID: ExtensionPointIdentifer) -> ExtensionPoint { + switch extensionPointID { + case .marsEdit: + return SendToMarsEditCommand() + case .microblog: + return SendToMicroBlogCommand() + case .twitter(let username): + return TwitterFeedProvider(username: username) + } } } From d49eabbcb3cd175b8c493228f034b756a5312493 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 8 Apr 2020 20:22:13 -0500 Subject: [PATCH 009/108] Add extension management for our two SendToCommand implementations --- Mac/Base.lproj/Preferences.storyboard | 36 +++--- .../ExtensionPointAddViewController.swift | 47 ++----- .../ExtensionPoints/ExtensionPointDetail.xib | 84 +++++++++++++ .../ExtensionPointDetailViewController.swift | 37 ++++++ .../ExtensionPointEnableBasic.xib | 115 ++++++++++++++++++ ...sionPointEnableBasicWindowController.swift | 61 ++++++++++ ...tensionPointMarsEditWindowController.swift | 41 +++++++ ...ensionPointPreferencesViewController.swift | 51 ++++++-- NetNewsWire.xcodeproj/project.pbxproj | 60 +++++++-- Shared/ExtensionPoints/ExtensionPoint.swift | 93 -------------- .../ExtensionPointIdentifer.swift | 92 ++++++++++++++ .../ExtensionPointManager.swift | 35 ++++-- .../ExtensionPoints/ExtensionPointType.swift | 89 +++++++++++++- 13 files changed, 666 insertions(+), 175 deletions(-) create mode 100644 Mac/Preferences/ExtensionPoints/ExtensionPointDetail.xib create mode 100644 Mac/Preferences/ExtensionPoints/ExtensionPointDetailViewController.swift create mode 100644 Mac/Preferences/ExtensionPoints/ExtensionPointEnableBasic.xib create mode 100644 Mac/Preferences/ExtensionPoints/ExtensionPointEnableBasicWindowController.swift create mode 100644 Mac/Preferences/ExtensionPoints/ExtensionPointMarsEditWindowController.swift create mode 100644 Shared/ExtensionPoints/ExtensionPointIdentifer.swift diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard index df9a513cd..1e3394f8a 100644 --- a/Mac/Base.lproj/Preferences.storyboard +++ b/Mac/Base.lproj/Preferences.storyboard @@ -385,16 +385,16 @@ - + - + - + - + @@ -501,7 +501,7 @@ - + @@ -532,7 +532,7 @@ - + @@ -556,16 +556,16 @@ - + - + - + - + @@ -619,10 +619,6 @@ - - - - @@ -654,6 +650,9 @@ + + + - + @@ -692,12 +694,12 @@ - + - + diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift index 9dd556daa..f12e07e2d 100644 --- a/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointAddViewController.swift @@ -70,43 +70,16 @@ extension ExtensionPointAddViewController: NSTableViewDelegate { return } -// switch addableAccountTypes[selectedRow] { -// case .onMyMac: -// let accountsAddLocalWindowController = AccountsAddLocalWindowController() -// accountsAddLocalWindowController.runSheetOnWindow(self.view.window!) -// accountsAddWindowController = accountsAddLocalWindowController -// case .cloudKit: -// let accountsAddCloudKitWindowController = AccountsAddCloudKitWindowController() -// accountsAddCloudKitWindowController.runSheetOnWindow(self.view.window!) { response in -// if response == NSApplication.ModalResponse.OK { -// self.restrictAccounts() -// self.tableView.reloadData() -// } -// } -// accountsAddWindowController = accountsAddCloudKitWindowController -// case .feedbin: -// let accountsFeedbinWindowController = AccountsFeedbinWindowController() -// accountsFeedbinWindowController.runSheetOnWindow(self.view.window!) -// accountsAddWindowController = accountsFeedbinWindowController -// case .feedWrangler: -// let accountsFeedWranglerWindowController = AccountsFeedWranglerWindowController() -// accountsFeedWranglerWindowController.runSheetOnWindow(self.view.window!) -// accountsAddWindowController = accountsFeedWranglerWindowController -// case .freshRSS: -// let accountsReaderAPIWindowController = AccountsReaderAPIWindowController() -// accountsReaderAPIWindowController.accountType = .freshRSS -// accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) -// accountsAddWindowController = accountsReaderAPIWindowController -// case .feedly: -// let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) -// addAccount.delegate = self -// addAccount.presentationAnchor = self.view.window! -// MainThreadOperationQueue.shared.add(addAccount) -// case .newsBlur: -// let accountsNewsBlurWindowController = AccountsNewsBlurWindowController() -// accountsNewsBlurWindowController.runSheetOnWindow(self.view.window!) -// accountsAddWindowController = accountsNewsBlurWindowController -// } + let extensionPointType = availableExtensionPointTypes[selectedRow] + switch extensionPointType { + case .marsEdit, .microblog: + let windowController = ExtensionPointEnableBasicWindowController() + windowController.extensionPointType = extensionPointType + windowController.runSheetOnWindow(self.view.window!) + extensionPointAddWindowController = windowController + default: + break + } tableView.selectRowIndexes([], byExtendingSelection: false) diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointDetail.xib b/Mac/Preferences/ExtensionPoints/ExtensionPointDetail.xib new file mode 100644 index 000000000..be4b650a5 --- /dev/null +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointDetail.xib @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointDetailViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointDetailViewController.swift new file mode 100644 index 000000000..d7829b4d2 --- /dev/null +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointDetailViewController.swift @@ -0,0 +1,37 @@ +// +// ExtensionPointDetailViewController.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/8/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Cocoa + +class ExtensionPointDetailViewController: NSViewController { + + @IBOutlet weak var imageView: NSImageView! + @IBOutlet weak var titleLabel: NSTextField! + @IBOutlet weak var descriptionLabel: NSTextField! + + private var extensionPointWindowController: NSWindowController? + private var extensionPointID: ExtensionPointIdentifer? + + init(extensionPointID: ExtensionPointIdentifer) { + super.init(nibName: "ExtensionPointDetail", bundle: nil) + self.extensionPointID = extensionPointID + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func viewDidLoad() { + super.viewDidLoad() + guard let extensionPointID = extensionPointID else { return } + imageView.image = extensionPointID.templateImage + titleLabel.stringValue = extensionPointID.title + descriptionLabel.attributedStringValue = extensionPointID.description + } + +} diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointEnableBasic.xib b/Mac/Preferences/ExtensionPoints/ExtensionPointEnableBasic.xib new file mode 100644 index 000000000..93c57f61e --- /dev/null +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointEnableBasic.xib @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointEnableBasicWindowController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointEnableBasicWindowController.swift new file mode 100644 index 000000000..48ab3ea44 --- /dev/null +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointEnableBasicWindowController.swift @@ -0,0 +1,61 @@ +// +// ExtensionPointEnableBasicWindowController.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/8/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Cocoa + +class ExtensionPointEnableBasicWindowController: NSWindowController { + + @IBOutlet weak var imageView: NSImageView! + @IBOutlet weak var titleLabel: NSTextField! + @IBOutlet weak var descriptionLabel: NSTextField! + + var extensionPointType: ExtensionPointType? + private weak var hostWindow: NSWindow? + + convenience init() { + self.init(windowNibName: NSNib.Name("ExtensionPointEnableBasic")) + } + + override func windowDidLoad() { + super.windowDidLoad() + guard let extensionPointType = extensionPointType else { return } + + imageView.image = extensionPointType.templateImage + titleLabel.stringValue = extensionPointType.title + descriptionLabel.attributedStringValue = extensionPointType.description + } + + // MARK: API + + func runSheetOnWindow(_ hostWindow: NSWindow) { + self.hostWindow = hostWindow + hostWindow.beginSheet(window!) + } + + // MARK: Actions + + @IBAction func cancel(_ sender: Any) { + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) + } + + @IBAction func enable(_ sender: Any) { + guard let extensionPointType = extensionPointType else { return } + + switch extensionPointType { + case .marsEdit: + ExtensionPointManager.shared.activateExtensionPoint(ExtensionPointIdentifer.marsEdit) + case .microblog: + ExtensionPointManager.shared.activateExtensionPoint(ExtensionPointIdentifer.microblog) + default: + assertionFailure("Unknown extension point.") + } + + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) + } + +} diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointMarsEditWindowController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointMarsEditWindowController.swift new file mode 100644 index 000000000..91e3a729a --- /dev/null +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointMarsEditWindowController.swift @@ -0,0 +1,41 @@ +// +// ExtensionPointMarsEditWindowController.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/8/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Cocoa + +class ExtensionPointEnableMarsEditWindowController: NSWindowController { + + private weak var hostWindow: NSWindow? + + convenience init() { + self.init(windowNibName: NSNib.Name("ExtensionPointMarsEdit")) + } + + override func windowDidLoad() { + super.windowDidLoad() + } + + // MARK: API + + func runSheetOnWindow(_ hostWindow: NSWindow) { + self.hostWindow = hostWindow + hostWindow.beginSheet(window!) + } + + // MARK: Actions + + @IBAction func cancel(_ sender: Any) { + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) + } + + @IBAction func enable(_ sender: Any) { + ExtensionPointManager.shared.activateExtensionPoint(ExtensionPointIdentifer.marsEdit) + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) + } + +} diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift index de6000f6b..79219ed4a 100644 --- a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift +++ b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift @@ -14,22 +14,42 @@ final class ExtensionPointPreferencesViewController: NSViewController { @IBOutlet weak var detailView: NSView! @IBOutlet weak var deleteButton: NSButton! - private var sortedAccounts = [String]() + private var activeExtensionPointIDs = [ExtensionPointIdentifer]() override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self tableView.dataSource = self - + + NotificationCenter.default.addObserver(self, selector: #selector(activeExtensionPointsDidChange(_:)), name: .ActiveExtensionPointsDidChange, object: nil) + showController(ExtensionPointAddViewController()) // Fix tableView frame — for some reason IB wants it 1pt wider than the clip view. This leads to unwanted horizontal scrolling. var rTable = tableView.frame rTable.size.width = tableView.superview!.frame.size.width tableView.frame = rTable + + activeExtensionPointIDs = Array(ExtensionPointManager.shared.activeExtensionPoints.keys) + tableView.reloadData() } + @IBAction func enableExtensionPoints(_ sender: Any) { + tableView.selectRowIndexes([], byExtendingSelection: false) + showController(ExtensionPointAddViewController()) + } + + @IBAction func disableExtensionPoint(_ sender: Any) { + guard tableView.selectedRow != -1 else { + return + } + + let extensionPointID = activeExtensionPointIDs[tableView.selectedRow] + ExtensionPointManager.shared.deactivateExtensionPoint(extensionPointID) + + showController(ExtensionPointAddViewController()) + } } // MARK: - NSTableViewDataSource @@ -37,11 +57,11 @@ final class ExtensionPointPreferencesViewController: NSViewController { extension ExtensionPointPreferencesViewController: NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { - return sortedAccounts.count + return activeExtensionPointIDs.count } func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - return sortedAccounts[row] + return activeExtensionPointIDs[row] } } @@ -53,9 +73,9 @@ extension ExtensionPointPreferencesViewController: NSTableViewDelegate { func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? NSTableCellView { - let account = sortedAccounts[row] -// cell.textField?.stringValue = account.nameForDisplay -// cell.imageView?.image = account.smallIcon?.image + let extensionPointID = activeExtensionPointIDs[row] + cell.textField?.stringValue = extensionPointID.title + cell.imageView?.image = extensionPointID.templateImage return cell } return nil @@ -63,6 +83,17 @@ extension ExtensionPointPreferencesViewController: NSTableViewDelegate { func tableViewSelectionDidChange(_ notification: Notification) { + let selectedRow = tableView.selectedRow + if tableView.selectedRow == -1 { + deleteButton.isEnabled = false + return + } else { + deleteButton.isEnabled = true + } + + let extensionPointID = activeExtensionPointIDs[selectedRow] + let controller = ExtensionPointDetailViewController(extensionPointID: extensionPointID) + showController(controller) } @@ -72,6 +103,12 @@ extension ExtensionPointPreferencesViewController: NSTableViewDelegate { private extension ExtensionPointPreferencesViewController { + @objc func activeExtensionPointsDidChange(_ note: Notification) { + activeExtensionPointIDs = Array(ExtensionPointManager.shared.activeExtensionPoints.keys).sorted(by: { $0.title < $1.title }) + tableView.reloadData() + showController(ExtensionPointAddViewController()) + } + func showController(_ controller: NSViewController) { if let controller = children.first { diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 6ef203954..23ad5baab 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -36,9 +36,6 @@ 510C43F4243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */; }; 510C43F7243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; 510C43F8243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; - 510C43F9243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; - 510C43FA243D0445009F70C3 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; - 510C43FB243D0445009F70C3 /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; 51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; }; 511076F7243BDA8100D97C8C /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; }; 511076F8243BDA8200D97C8C /* FeedProvider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -121,10 +118,27 @@ 51554C31228B71A10055115A /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 515A50E6243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; 515A50E7243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; - 515A50E8243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; 515A5107243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */; }; 515A5108243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */; }; - 515A5109243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */; }; + 515A5148243E64BA0089E588 /* ExtensionPointEnableBasicWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableBasicWindowController.swift */; }; + 515A5149243E64BA0089E588 /* ExtensionPointEnableBasicWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableBasicWindowController.swift */; }; + 515A5168243E66910089E588 /* ExtensionPointEnableBasic.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A5167243E66910089E588 /* ExtensionPointEnableBasic.xib */; }; + 515A5169243E66910089E588 /* ExtensionPointEnableBasic.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A5167243E66910089E588 /* ExtensionPointEnableBasic.xib */; }; + 515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */; }; + 515A516F243E7F950089E588 /* ExtensionPointDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */; }; + 515A5171243E802B0089E588 /* ExtensionPointDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */; }; + 515A5172243E802B0089E588 /* ExtensionPointDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */; }; + 515A5174243E8FEA0089E588 /* ExtensionPointType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5173243E8FEA0089E588 /* ExtensionPointType.swift */; }; + 515A5175243E8FEA0089E588 /* ExtensionPointType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5173243E8FEA0089E588 /* ExtensionPointType.swift */; }; + 515A5177243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; + 515A5178243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; + 515A517B243E90260089E588 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; + 515A517C243E90260089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; + 515A517D243E90260089E588 /* ExtensionPointType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5173243E8FEA0089E588 /* ExtensionPointType.swift */; }; + 515A517E243E90260089E588 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; + 515A517F243E90260089E588 /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; + 515A5180243E90260089E588 /* TwitterFeedProvider+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */; }; + 515A5181243E90260089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; 515D4FC123257A3200EE1167 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; }; @@ -1361,6 +1375,12 @@ 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SyncDatabase.xcodeproj; path = Frameworks/SyncDatabase/SyncDatabase.xcodeproj; sourceTree = SOURCE_ROOT; }; 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointManager.swift; sourceTree = ""; }; 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TwitterFeedProvider+Extensions.swift"; sourceTree = ""; }; + 515A5147243E64BA0089E588 /* ExtensionPointEnableBasicWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointEnableBasicWindowController.swift; sourceTree = ""; }; + 515A5167243E66910089E588 /* ExtensionPointEnableBasic.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExtensionPointEnableBasic.xib; sourceTree = ""; }; + 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExtensionPointDetail.xib; sourceTree = ""; }; + 515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointDetailViewController.swift; sourceTree = ""; }; + 515A5173243E8FEA0089E588 /* ExtensionPointType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointType.swift; sourceTree = ""; }; + 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointIdentifer.swift; sourceTree = ""; }; 515D4FCB2325815A00EE1167 /* SafariExt.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = SafariExt.js; sourceTree = ""; }; 515D4FCD2325909200EE1167 /* NetNewsWire_iOS_ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_ShareExtension.entitlements; sourceTree = ""; }; 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSshareextension_target.xcconfig; sourceTree = ""; }; @@ -1822,7 +1842,9 @@ isa = PBXGroup; children = ( 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */, + 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */, 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */, + 515A5173243E8FEA0089E588 /* ExtensionPointType.swift */, 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */, 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */, 515A5106243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift */, @@ -1844,7 +1866,11 @@ 510C43EC243C0973009F70C3 /* ExtensionPointAdd.xib */, 510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */, 510C43EF243C0A80009F70C3 /* ExtensionPointAddViewController.swift */, + 515A5167243E66910089E588 /* ExtensionPointEnableBasic.xib */, + 515A5147243E64BA0089E588 /* ExtensionPointEnableBasicWindowController.swift */, 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */, + 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */, + 515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */, ); path = ExtensionPoints; sourceTree = ""; @@ -3574,6 +3600,7 @@ 65ED4057235DEF6C0081F399 /* AccountsDetail.xib in Resources */, 65ED4058235DEF6C0081F399 /* main.js in Resources */, 65ED40A1235DEFF00081F399 /* container-migration.plist in Resources */, + 515A5169243E66910089E588 /* ExtensionPointEnableBasic.xib in Resources */, 65ED4059235DEF6C0081F399 /* AccountsAddLocal.xib in Resources */, 65ED405A235DEF6C0081F399 /* main_mac.js in Resources */, 65ED405B235DEF6C0081F399 /* KeyboardShortcuts.html in Resources */, @@ -3592,6 +3619,7 @@ 65ED4068235DEF6C0081F399 /* MainWindow.storyboard in Resources */, BDCB516824282C8A00102A80 /* AccountsNewsBlur.xib in Resources */, 3B826DCD2385C89600FC1ADB /* AccountsFeedWrangler.xib in Resources */, + 515A516F243E7F950089E588 /* ExtensionPointDetail.xib in Resources */, 65ED4069235DEF6C0081F399 /* AccountsReaderAPI.xib in Resources */, 65ED406A235DEF6C0081F399 /* newsfoot.js in Resources */, 5103A9992421643300410853 /* blank.html in Resources */, @@ -3666,6 +3694,7 @@ 5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */, 5142194B2353C1CF00E07E2C /* main_mac.js in Resources */, 84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */, + 515A5168243E66910089E588 /* ExtensionPointEnableBasic.xib in Resources */, 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */, 844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */, 5103A9F4242258C600410853 /* AccountsAddCloudKit.xib in Resources */, @@ -3686,6 +3715,7 @@ 510C43ED243C0973009F70C3 /* ExtensionPointAdd.xib in Resources */, BDCB516724282C8A00102A80 /* AccountsNewsBlur.xib in Resources */, 5103A9982421643300410853 /* blank.html in Resources */, + 515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */, 84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */, 84C9FC8E22629E8F00D921D6 /* Credits.rtf in Resources */, 84BBB12D20142A4700F054F5 /* Inspector.storyboard in Resources */, @@ -3880,7 +3910,9 @@ 65ED3FC7235DEF6C0081F399 /* Reachability.swift in Sources */, 65ED3FC8235DEF6C0081F399 /* SidebarCellLayout.swift in Sources */, 65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */, + 515A5149243E64BA0089E588 /* ExtensionPointEnableBasicWindowController.swift in Sources */, 65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */, + 515A5178243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */, 65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */, 65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */, 65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */, @@ -3890,6 +3922,7 @@ 65ED3FD2235DEF6C0081F399 /* AccountsAddViewController.swift in Sources */, 65ED3FD3235DEF6C0081F399 /* NSScriptCommand+NetNewsWire.swift in Sources */, 65ED3FD4235DEF6C0081F399 /* Article+Scriptability.swift in Sources */, + 515A5172243E802B0089E588 /* ExtensionPointDetailViewController.swift in Sources */, 65ED3FD5235DEF6C0081F399 /* SmartFeed.swift in Sources */, 65ED3FD6235DEF6C0081F399 /* MarkStatusCommand.swift in Sources */, 65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */, @@ -3929,6 +3962,7 @@ 65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */, 65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */, 65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */, + 515A5175243E8FEA0089E588 /* ExtensionPointType.swift in Sources */, 65ED3FFA235DEF6C0081F399 /* WebFeedInspectorViewController.swift in Sources */, 65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */, 65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */, @@ -4028,7 +4062,6 @@ 512DD4C92430086400C17B1F /* CloudKitAccountViewController.swift in Sources */, 840D617F2029031C009BC708 /* AppDelegate.swift in Sources */, 51236339236915B100951F16 /* RoundedProgressView.swift in Sources */, - 510C43F9243D035C009F70C3 /* ExtensionPoint.swift in Sources */, 512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */, 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */, 51EF0F79227716380050506E /* ColorHash.swift in Sources */, @@ -4037,7 +4070,6 @@ B2B80778239C4C7000F191E0 /* RSImage-AppIcons.swift in Sources */, 518ED21D23D0F26000E0A862 /* UIViewController-Extensions.swift in Sources */, 51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */, - 510C43FB243D0445009F70C3 /* SendToMicroBlogCommand.swift in Sources */, 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */, @@ -4056,7 +4088,6 @@ 513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */, 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */, 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, - 515A5109243D0CCD0089E588 /* TwitterFeedProvider+Extensions.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 51AB8AB323B7F4C6008F147D /* WebViewController.swift in Sources */, 516A09392360A2AE00EAE89B /* SettingsAccountTableViewCell.swift in Sources */, @@ -4077,7 +4108,6 @@ 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, - 510C43FA243D0445009F70C3 /* SendToMarsEditCommand.swift in Sources */, 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */, 51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, @@ -4122,10 +4152,12 @@ C5A6ED6D23C9B0C800AB6BE2 /* UIActivityViewController-Extensions.swift in Sources */, 5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */, 84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */, + 515A517B243E90260089E588 /* ExtensionPoint.swift in Sources */, 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */, 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 51F9F3F723DF6DB200A314FD /* ArticleIconSchemeHandler.swift in Sources */, 512AF9C2236ED52C0066F8BE /* ImageHeaderView.swift in Sources */, + 515A5181243E90260089E588 /* ExtensionPointIdentifer.swift in Sources */, 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */, 51C45290226509C100C03939 /* PseudoFeed.swift in Sources */, 51C452A922650DC600C03939 /* ArticleRenderer.swift in Sources */, @@ -4134,6 +4166,7 @@ 512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */, 5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */, 51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */, + 515A5180243E90260089E588 /* TwitterFeedProvider+Extensions.swift in Sources */, 51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */, 51EF0F7E2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift in Sources */, 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */, @@ -4151,9 +4184,10 @@ 514219372352510100E07E2C /* ImageScrollView.swift in Sources */, 516AE9B32371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift in Sources */, 51DC370B2405BC9A0095D371 /* PreloadedWebView.swift in Sources */, - 515A50E8243D07A90089E588 /* ExtensionPointManager.swift in Sources */, + 515A517D243E90260089E588 /* ExtensionPointType.swift in Sources */, C5A6ED5223C9AF4300AB6BE2 /* TitleActivityItemSource.swift in Sources */, 51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */, + 515A517E243E90260089E588 /* SendToMarsEditCommand.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */, @@ -4167,8 +4201,10 @@ 51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */, 51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */, 51DC37072402153E0095D371 /* UpdateSelectionOperation.swift in Sources */, + 515A517F243E90260089E588 /* SendToMicroBlogCommand.swift in Sources */, 51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */, 5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */, + 515A517C243E90260089E588 /* ExtensionPointManager.swift in Sources */, 51627A6723861DA3007B3B4B /* MasterFeedViewController+Drag.swift in Sources */, 51FFF0C4235EE8E5002762AA /* VibrantButton.swift in Sources */, 513228FC233037630033D4ED /* Reachability.swift in Sources */, @@ -4189,6 +4225,7 @@ files = ( 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */, 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */, + 515A5171243E802B0089E588 /* ExtensionPointDetailViewController.swift in Sources */, 847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */, 84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */, 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */, @@ -4225,6 +4262,7 @@ 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 515A50E6243D07A90089E588 /* ExtensionPointManager.swift in Sources */, 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, + 515A5177243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */, 849A97651ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift in Sources */, 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 51FE10092346739D0056195D /* ActivityType.swift in Sources */, @@ -4262,6 +4300,7 @@ 55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */, 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */, 84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */, + 515A5148243E64BA0089E588 /* ExtensionPointEnableBasicWindowController.swift in Sources */, 5103A9F724225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */, 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */, 84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */, @@ -4309,6 +4348,7 @@ 510C43F0243C0A80009F70C3 /* ExtensionPointAddViewController.swift in Sources */, 849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */, 84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */, + 515A5174243E8FEA0089E588 /* ExtensionPointType.swift in Sources */, 849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */, 849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */, 51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */, diff --git a/Shared/ExtensionPoints/ExtensionPoint.swift b/Shared/ExtensionPoints/ExtensionPoint.swift index f41e68fe6..9ba8e27be 100644 --- a/Shared/ExtensionPoints/ExtensionPoint.swift +++ b/Shared/ExtensionPoints/ExtensionPoint.swift @@ -9,99 +9,6 @@ import Foundation import RSCore -enum ExtensionPointType { - case marsEdit - case microblog - case twitter - - var title: String { - switch self { - case .marsEdit: - return NSLocalizedString("MarsEdit", comment: "MarsEdit") - case .microblog: - return NSLocalizedString("Micro.blog", comment: "Micro.blog") - case .twitter: - return NSLocalizedString("Twitter", comment: "Twitter") - } - - } - - var templateImage: RSImage { - switch self { - case .marsEdit: - return AppAssets.extensionPointMarsEdit - case .microblog: - return AppAssets.extensionPointMicroblog - case .twitter: - return AppAssets.extensionPointTwitter - } - } - -} - -enum ExtensionPointIdentifer: Hashable { - case marsEdit - case microblog - case twitter(String) - - var title: String { - switch self { - case .marsEdit: - return ExtensionPointType.marsEdit.title - case .microblog: - return ExtensionPointType.microblog.title - case .twitter(let username): - return "\(ExtensionPointType.microblog.title) (\(username))" - } - } - - public var userInfo: [AnyHashable: AnyHashable] { - switch self { - case .marsEdit: - return [ - "type": "marsEdit" - ] - case .microblog: - return [ - "type": "microblog" - ] - case .twitter(let username): - return [ - "type": "feed", - "username": username - ] - } - } - - public init?(userInfo: [AnyHashable: AnyHashable]) { - guard let type = userInfo["type"] as? String else { return nil } - - switch type { - case "marsEdit": - self = ExtensionPointIdentifer.marsEdit - case "microblog": - self = ExtensionPointIdentifer.microblog - case "twitter": - guard let username = userInfo["username"] as? String else { return nil } - self = ExtensionPointIdentifer.twitter(username) - default: - return nil - } - } - - public func hash(into hasher: inout Hasher) { - switch self { - case .marsEdit: - hasher.combine("marsEdit") - case .microblog: - hasher.combine("microblog") - case .twitter(let username): - hasher.combine("twitter") - hasher.combine(username) - } - } -} - protocol ExtensionPoint { var extensionPointType: ExtensionPointType { get } diff --git a/Shared/ExtensionPoints/ExtensionPointIdentifer.swift b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift new file mode 100644 index 000000000..eb00609e4 --- /dev/null +++ b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift @@ -0,0 +1,92 @@ +// +// ExtensionPointIdentifer.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/8/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import RSCore + +enum ExtensionPointIdentifer: Hashable { + case marsEdit + case microblog + case twitter(String) + + var title: String { + switch self { + case .marsEdit: + return ExtensionPointType.marsEdit.title + case .microblog: + return ExtensionPointType.microblog.title + case .twitter(let username): + return "\(ExtensionPointType.microblog.title) (\(username))" + } + } + + var templateImage: RSImage { + return type.templateImage + } + + var description: NSAttributedString { + return type.description + } + + var type: ExtensionPointType { + switch self { + case .marsEdit: + return ExtensionPointType.marsEdit + case .microblog: + return ExtensionPointType.microblog + case .twitter: + return ExtensionPointType.twitter + } + } + + public var userInfo: [AnyHashable: AnyHashable] { + switch self { + case .marsEdit: + return [ + "type": "marsEdit" + ] + case .microblog: + return [ + "type": "microblog" + ] + case .twitter(let username): + return [ + "type": "feed", + "username": username + ] + } + } + + public init?(userInfo: [AnyHashable: AnyHashable]) { + guard let type = userInfo["type"] as? String else { return nil } + + switch type { + case "marsEdit": + self = ExtensionPointIdentifer.marsEdit + case "microblog": + self = ExtensionPointIdentifer.microblog + case "twitter": + guard let username = userInfo["username"] as? String else { return nil } + self = ExtensionPointIdentifer.twitter(username) + default: + return nil + } + } + + public func hash(into hasher: inout Hasher) { + switch self { + case .marsEdit: + hasher.combine("marsEdit") + case .microblog: + hasher.combine("microblog") + case .twitter(let username): + hasher.combine("twitter") + hasher.combine(username) + } + } +} diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift index 831dd449f..eb8cc5993 100644 --- a/Shared/ExtensionPoints/ExtensionPointManager.swift +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -10,12 +10,32 @@ import Foundation import FeedProvider import RSCore +public extension Notification.Name { + static let ActiveExtensionPointsDidChange = Notification.Name(rawValue: "ActiveExtensionPointsDidChange") +} + final class ExtensionPointManager { static let shared = ExtensionPointManager() var activeExtensionPoints = [ExtensionPointIdentifer: ExtensionPoint]() - let availableExtensionPointTypes: [ExtensionPointType] + let possibleExtensionPointTypes: [ExtensionPointType] + var availableExtensionPointTypes: [ExtensionPointType] { + + let activeExtensionPointTypes = Set(activeExtensionPoints.keys.compactMap({ $0.type })) + var available = [ExtensionPointType]() + for possibleExtensionPointType in possibleExtensionPointTypes { + if possibleExtensionPointType.isSinglton { + if !activeExtensionPointTypes.contains(possibleExtensionPointType) { + available.append(possibleExtensionPointType) + } + } else { + available.append(possibleExtensionPointType) + } + } + + return available + } var activeSendToCommands: [SendToCommand] { return activeExtensionPoints.values.compactMap({ return $0 as? SendToCommand }) @@ -28,18 +48,18 @@ final class ExtensionPointManager { init() { #if os(macOS) #if DEBUG - availableExtensionPointTypes = [.marsEdit, .microblog, .twitter] + possibleExtensionPointTypes = [.marsEdit, .microblog, .twitter] #else - availableExtensionPointTypes = [.marsEdit, .microblog, .twitter] + possibleExtensionPointTypes = [.marsEdit, .microblog, .twitter] #endif #else #if DEBUG - availableExtensionPoints = [.twitter] + possibleExtensionPointTypes = [.twitter] #else - availableExtensionPoints = [.twitter] + possibleExtensionPointTypes = [.twitter] #endif #endif - loadExtensionPointIDs() + loadExtensionPoints() } func activateExtensionPoint(_ extensionPointID: ExtensionPointIdentifer) { @@ -56,7 +76,7 @@ final class ExtensionPointManager { private extension ExtensionPointManager { - func loadExtensionPointIDs() { + func loadExtensionPoints() { if let extensionPointUserInfos = AppDefaults.activeExtensionPointIDs { for extensionPointUserInfo in extensionPointUserInfos { if let extensionPointID = ExtensionPointIdentifer(userInfo: extensionPointUserInfo) { @@ -68,6 +88,7 @@ private extension ExtensionPointManager { func saveExtensionPointIDs() { AppDefaults.activeExtensionPointIDs = activeExtensionPoints.keys.map({ $0.userInfo }) + NotificationCenter.default.post(name: .ActiveExtensionPointsDidChange, object: nil, userInfo: nil) } func extensionPoint(for extensionPointID: ExtensionPointIdentifer) -> ExtensionPoint { diff --git a/Shared/ExtensionPoints/ExtensionPointType.swift b/Shared/ExtensionPoints/ExtensionPointType.swift index 1b6fb845b..470759449 100644 --- a/Shared/ExtensionPoints/ExtensionPointType.swift +++ b/Shared/ExtensionPoints/ExtensionPointType.swift @@ -2,13 +2,94 @@ // ExtensionPointType.swift // NetNewsWire // -// Created by Maurice Parker on 4/7/20. +// Created by Maurice Parker on 4/8/20. // Copyright © 2020 Ranchero Software. All rights reserved. // import Foundation +import RSCore -enum ExtensionPoint: Int, Codable { - case sentToCommand = 1 - case feedProvider = 2 +enum ExtensionPointType { + case marsEdit + case microblog + case twitter + + var isSinglton: Bool { + switch self { + case .marsEdit, .microblog: + return true + default: + return false + } + } + + var title: String { + switch self { + case .marsEdit: + return NSLocalizedString("MarsEdit", comment: "MarsEdit") + case .microblog: + return NSLocalizedString("Micro.blog", comment: "Micro.blog") + case .twitter: + return NSLocalizedString("Twitter", comment: "Twitter") + } + + } + + var templateImage: RSImage { + switch self { + case .marsEdit: + return AppAssets.extensionPointMarsEdit + case .microblog: + return AppAssets.extensionPointMicroblog + case .twitter: + return AppAssets.extensionPointTwitter + } + } + + var description: NSAttributedString { + switch self { + case .marsEdit: + let attrString = makeAttrString("This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work.") + let range = NSRange(location: 81, length: 8) + attrString.beginEditing() + attrString.addAttribute(NSAttributedString.Key.link, value: "https://red-sweater.com/marsedit/", range: range) + attrString.addAttribute(NSAttributedString.Key.foregroundColor, value: NSColor.systemBlue, range: range) + attrString.endEditing() + return attrString + case .microblog: + let attrString = makeAttrString("This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work.") + let range = NSRange(location: 81, length: 10) + attrString.beginEditing() + attrString.addAttribute(NSAttributedString.Key.link, value: "https://micro.blog", range: range) + attrString.addAttribute(NSAttributedString.Key.foregroundColor, value: NSColor.systemBlue, range: range) + attrString.endEditing() + return attrString + case .twitter: + let attrString = makeAttrString("This extension enables you to subscribe to Twitter URL's as if they were RSS feeds.") + let range = NSRange(location: 43, length: 7) + attrString.beginEditing() + attrString.addAttribute(NSAttributedString.Key.link, value: "https://twitter.com", range: range) + attrString.addAttribute(NSAttributedString.Key.foregroundColor, value: NSColor.systemBlue, range: range) + attrString.endEditing() + return attrString + } + } + +} + +private extension ExtensionPointType { + + func makeAttrString(_ text: String) -> NSMutableAttributedString { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + + let attrs = [ + NSAttributedString.Key.paragraphStyle: paragraphStyle, + NSAttributedString.Key.font: NSFont.systemFont(ofSize: NSFont.systemFontSize), + NSAttributedString.Key.foregroundColor: NSColor.textColor + ] + + return NSMutableAttributedString(string: text, attributes: attrs) + } + } From 0e588b5e092e56b0dc39161db54299888c3db1ad Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 9 Apr 2020 10:50:23 -0500 Subject: [PATCH 010/108] Update MarsEdit icon and remove "Information" from Preferences tab. --- Mac/Preferences/Accounts/AccountsDetail.xib | 22 +++++++++--------- .../ExtensionPoints/ExtensionPointDetail.xib | 2 +- .../Contents.json | 2 +- .../MarsEditOfficial.pdf | Bin 0 -> 6339 bytes .../marsedit.pdf | Bin 4453 -> 0 bytes 5 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 Mac/Resources/Assets.xcassets/extensionPointMarsEdit.imageset/MarsEditOfficial.pdf delete mode 100644 Mac/Resources/Assets.xcassets/extensionPointMarsEdit.imageset/marsedit.pdf diff --git a/Mac/Preferences/Accounts/AccountsDetail.xib b/Mac/Preferences/Accounts/AccountsDetail.xib index 82b5891c9..00bae7a7a 100644 --- a/Mac/Preferences/Accounts/AccountsDetail.xib +++ b/Mac/Preferences/Accounts/AccountsDetail.xib @@ -1,7 +1,7 @@ - + - + @@ -21,13 +21,13 @@ - + - + @@ -41,7 +41,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -62,7 +62,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + - - + + diff --git a/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json b/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json new file mode 100644 index 000000000..834100cda --- /dev/null +++ b/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "twitter.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf b/iOS/Resources/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e50de4443827d9570161cdd229c8d6ce640981e4 GIT binary patch literal 4237 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 UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsAccountTableViewCell", for: indexPath) as! SettingsAccountTableViewCell + let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsAccountTableViewCell", for: indexPath) as! SettingsComboTableViewCell switch addableAccountTypes[indexPath.row] { case .onMyMac: - cell.accountNameLabel?.text = Account.defaultLocalAccountName - cell.accountImage?.image = AppAssets.image(for: .onMyMac) + cell.comboNameLabel?.text = Account.defaultLocalAccountName + cell.comboImage?.image = AppAssets.image(for: .onMyMac) case .cloudKit: - cell.accountNameLabel?.text = NSLocalizedString("iCloud", comment: "iCloud") - cell.accountImage?.image = AppAssets.accountCloudKitImage + cell.comboNameLabel?.text = NSLocalizedString("iCloud", comment: "iCloud") + cell.comboImage?.image = AppAssets.accountCloudKitImage case .feedbin: - cell.accountNameLabel?.text = NSLocalizedString("Feedbin", comment: "Feedbin") - cell.accountImage?.image = AppAssets.accountFeedbinImage + cell.comboNameLabel?.text = NSLocalizedString("Feedbin", comment: "Feedbin") + cell.comboImage?.image = AppAssets.accountFeedbinImage case .feedWrangler: - cell.accountNameLabel?.text = NSLocalizedString("Feed Wrangler", comment: "Feed Wrangler") - cell.accountImage?.image = AppAssets.accountFeedWranglerImage + cell.comboNameLabel?.text = NSLocalizedString("Feed Wrangler", comment: "Feed Wrangler") + cell.comboImage?.image = AppAssets.accountFeedWranglerImage case .feedly: - cell.accountNameLabel?.text = NSLocalizedString("Feedly", comment: "Feedly") - cell.accountImage?.image = AppAssets.accountFeedlyImage + cell.comboNameLabel?.text = NSLocalizedString("Feedly", comment: "Feedly") + cell.comboImage?.image = AppAssets.accountFeedlyImage case .newsBlur: - cell.accountNameLabel?.text = NSLocalizedString("NewsBlur", comment: "NewsBlur") - cell.accountImage?.image = AppAssets.accountNewsBlurImage + cell.comboNameLabel?.text = NSLocalizedString("NewsBlur", comment: "NewsBlur") + cell.comboImage?.image = AppAssets.accountNewsBlurImage default: break } diff --git a/iOS/Settings/AddExtensionPointViewController.swift b/iOS/Settings/AddExtensionPointViewController.swift new file mode 100644 index 000000000..a9b44fce0 --- /dev/null +++ b/iOS/Settings/AddExtensionPointViewController.swift @@ -0,0 +1,59 @@ +// +// AddExtensionPointViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +protocol AddExtensionPointDismissDelegate: UIViewController { + func dismiss() +} + +class AddExtensionPointViewController: UITableViewController, AddExtensionPointDismissDelegate { + + private var availableExtensionPointTypes = [ExtensionPoint.Type]() + + override func viewDidLoad() { + super.viewDidLoad() + availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes + } + + override func numberOfSections(in tableView: UITableView) -> Int { + 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return availableExtensionPointTypes.count + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 52.0 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsExtensionTableViewCell", for: indexPath) as! SettingsComboTableViewCell + + let extensionPointType = availableExtensionPointTypes[indexPath.row] + cell.comboNameLabel?.text = extensionPointType.title + cell.comboImage?.image = extensionPointType.templateImage + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "EnableExtensionPointNavigationViewController") as! UINavigationController + navController.modalPresentationStyle = .currentContext + let enableViewController = navController.topViewController as! EnableExtensionPointViewController + enableViewController.delegate = self + enableViewController.extensionPointType = availableExtensionPointTypes[indexPath.row] + present(navController, animated: true) + } + + func dismiss() { + navigationController?.popViewController(animated: false) + } + +} diff --git a/iOS/Settings/AddExtensionViewContrller.swift b/iOS/Settings/AddExtensionViewContrller.swift new file mode 100644 index 000000000..91125f159 --- /dev/null +++ b/iOS/Settings/AddExtensionViewContrller.swift @@ -0,0 +1,59 @@ +// +// AddExtensionViewContrller.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +protocol AddExtensionDismissDelegate: UIViewController { + func dismiss() +} + +class AddExtensionViewController: UITableViewController, AddExtensionDismissDelegate { + + private var availableExtensionPointTypes = [ExtensionPoint.Type]() + + override func viewDidLoad() { + super.viewDidLoad() + availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes + } + + override func numberOfSections(in tableView: UITableView) -> Int { + 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return availableExtensionPointTypes.count + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 52.0 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsExtensionTableViewCell", for: indexPath) as! SettingsComboTableViewCell + + let extensionPointType = availableExtensionPointTypes[indexPath.row] + cell.comboNameLabel?.text = extensionPointType.title + cell.comboImage?.image = extensionPointType.templateImage + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let navController = UIStoryboard.settings.instantiateViewController(withIdentifier: "EnableExtensiontNavigationViewController") as! UINavigationController + navController.modalPresentationStyle = .currentContext + let enableViewController = navController.topViewController as! EnableExtensionViewController + enableViewController.delegate = self + enableViewController.extensionPointType = availableExtensionPointTypes[indexPath.row] + present(navController, animated: true) + } + + func dismiss() { + navigationController?.popViewController(animated: false) + } + +} diff --git a/iOS/Settings/EnableExtensionPointViewController.swift b/iOS/Settings/EnableExtensionPointViewController.swift new file mode 100644 index 000000000..a3099f3df --- /dev/null +++ b/iOS/Settings/EnableExtensionPointViewController.swift @@ -0,0 +1,132 @@ +// +// EnableExtensionPointViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit +import AuthenticationServices +import Account +import OAuthSwift +import Secrets + +class EnableExtensionPointViewController: UITableViewController { + + @IBOutlet weak var extensionDescription: UILabel! + + private let callbackURL = URL(string: "vincodennw://")! + private var oauth: OAuthSwift? + + weak var delegate: AddExtensionPointDismissDelegate? + var extensionPointType: ExtensionPoint.Type? + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.title = extensionPointType?.title + extensionDescription.attributedText = extensionPointType?.description + tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") + } + + @IBAction func cancel(_ sender: Any) { + dismiss(animated: true, completion: nil) + delegate?.dismiss() + } + + @IBAction func enable(_ sender: Any) { + guard let extensionPointType = extensionPointType else { return } + + if let oauth1 = extensionPointType as? OAuth1SwiftProvider.Type { + enableOauth1(oauth1) + } else { + ExtensionPointManager.shared.activateExtensionPoint(extensionPointType) + dismiss(animated: true, completion: nil) + delegate?.dismiss() + } + } + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return section == 0 ? ImageHeaderView.rowHeight : super.tableView(tableView, heightForHeaderInSection: section) + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if section == 0 { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView + headerView.imageView.image = extensionPointType?.templateImage + return headerView + } else { + return super.tableView(tableView, viewForHeaderInSection: section) + } + } + +} + +extension EnableExtensionPointViewController: OAuthSwiftURLHandlerType { + + public func handle(_ url: URL) { + let session = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURL.scheme, completionHandler: { (url, error) in + if let callbackedURL = url { + OAuth1Swift.handle(url: callbackedURL) + } + + guard let error = error else { return } + + self.oauth?.cancel() + self.oauth = nil + + DispatchQueue.main.async { + self.dismiss(animated: true, completion: nil) + self.delegate?.dismiss() + } + + if case ASWebAuthenticationSessionError.canceledLogin = error { + print("Login cancelled.") + } else { + self.presentError(error) + } + }) + + session.presentationContextProvider = self + if !session.start() { + print("Session failed to start!!!") + } + + } +} + +extension EnableExtensionPointViewController: ASWebAuthenticationPresentationContextProviding { + + public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return view.window! + } + +} + +private extension EnableExtensionPointViewController { + + func enableOauth1(_ provider: OAuth1SwiftProvider.Type) { + + let oauth1 = provider.oauth1Swift + self.oauth = oauth1 + oauth1.authorizeURLHandler = self + + oauth1.authorize(withCallbackURL: callbackURL) { [weak self] result in + guard let self = self, let extensionPointType = self.extensionPointType else { return } + + switch result { + case .success(let tokenSuccess): + ExtensionPointManager.shared.activateExtensionPoint(extensionPointType, tokenSuccess: tokenSuccess) + self.dismiss(animated: true, completion: nil) + self.delegate?.dismiss() + case .failure(let oauthSwiftError): + self.presentError(oauthSwiftError) + } + + self.oauth?.cancel() + self.oauth = nil + } + + } + +} diff --git a/iOS/Settings/EnableExtensionViewController.swift b/iOS/Settings/EnableExtensionViewController.swift new file mode 100644 index 000000000..0732c43a2 --- /dev/null +++ b/iOS/Settings/EnableExtensionViewController.swift @@ -0,0 +1,132 @@ +// +// EnableExtensionViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit +import AuthenticationServices +import Account +import OAuthSwift +import Secrets + +class EnableExtensionPointViewController: UITableViewController { + + @IBOutlet weak var extensionDescription: UILabel! + + private let callbackURL = URL(string: "vincodennw://")! + private var oauth: OAuthSwift? + + weak var delegate: AddExtensionPointDismissDelegate? + var extensionPointType: ExtensionPoint.Type? + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.title = extensionPointType?.title ?? "" + extensionDescription = extensionPointType?.extensionDescription ?? "" + tableView.register(ImageHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader") + } + + @IBAction func cancel(_ sender: Any) { + dismiss(animated: true, completion: nil) + delegate?.dismiss() + } + + @IBAction func enable(_ sender: Any) { + guard let extensionPointType = extensionPointType else { return } + + if let oauth1 = extensionPointType as? OAuth1SwiftProvider.Type { + enableOauth1(oauth1) + } else { + ExtensionPointManager.shared.activateExtensionPoint(extensionPointType) + dismiss(animated: true, completion: nil) + delegate?.dismiss() + } + } + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return section == 0 ? ImageHeaderView.rowHeight : super.tableView(tableView, heightForHeaderInSection: section) + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if section == 0 { + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView + headerView.imageView.image = extensionPointType?.templateImage + return headerView + } else { + return super.tableView(tableView, viewForHeaderInSection: section) + } + } + +} + +extension EnableExtensionPointViewController: OAuthSwiftURLHandlerType { + + public func handle(_ url: URL) { + let session = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURL.scheme, completionHandler: { (url, error) in + if let callbackedURL = url { + OAuth1Swift.handle(url: callbackedURL) + } + + guard let error = error else { return } + + self.oauth?.cancel() + self.oauth = nil + + DispatchQueue.main.async { + self.dismiss(animated: true, completion: nil) + self.delegate?.dismiss() + } + + if case ASWebAuthenticationSessionError.canceledLogin = error { + print("Login cancelled.") + } else { + self.presentError(error) + } + }) + + session.presentationContextProvider = self + if !session.start() { + print("Session failed to start!!!") + } + + } +} + +extension EnableExtensionPointViewController: ASWebAuthenticationPresentationContextProviding { + + public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return view.window! + } + +} + +private extension EnableExtensionPointViewController { + + func enableOauth1(_ provider: OAuth1SwiftProvider.Type) { + + let oauth1 = provider.oauth1Swift + self.oauth = oauth1 + oauth1.authorizeURLHandler = self + + oauth1.authorize(withCallbackURL: callbackURL) { [weak self] result in + guard let self = self, let extensionPointType = self.extensionPointType else { return } + + switch result { + case .success(let tokenSuccess): + ExtensionPointManager.shared.activateExtensionPoint(extensionPointType, tokenSuccess: tokenSuccess) + self.dismiss(animated: true, completion: nil) + self.delegate?.dismiss() + case .failure(let oauthSwiftError): + self.presentError(oauthSwiftError) + } + + self.oauth?.cancel() + self.oauth = nil + } + + } + +} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index 7d7c0f0f6..04a998ca9 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -2,7 +2,7 @@ - + @@ -58,10 +58,31 @@ + + + + + + + + + + + + + + + - + @@ -78,7 +99,7 @@ - + @@ -95,7 +116,7 @@ - + @@ -116,7 +137,7 @@ - + @@ -149,7 +170,7 @@ - + @@ -182,7 +203,7 @@ - + @@ -215,7 +236,7 @@ - + @@ -242,7 +263,7 @@ - + @@ -275,7 +296,7 @@ - + @@ -320,7 +341,7 @@ - + @@ -356,14 +377,14 @@ - + - + - + @@ -407,7 +428,7 @@ - + @@ -424,7 +445,7 @@ - + @@ -441,7 +462,7 @@ - + @@ -458,7 +479,7 @@ - + @@ -475,7 +496,7 @@ - + @@ -527,7 +548,7 @@ - + @@ -560,8 +581,8 @@ - - + + @@ -726,7 +747,7 @@ - + @@ -829,7 +850,7 @@ - + @@ -885,7 +906,7 @@ - + @@ -926,9 +947,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Settings/SettingsAccountTableViewCell.swift b/iOS/Settings/SettingsComboTableViewCell.swift similarity index 61% rename from iOS/Settings/SettingsAccountTableViewCell.swift rename to iOS/Settings/SettingsComboTableViewCell.swift index 2d5ba352d..ec1775600 100644 --- a/iOS/Settings/SettingsAccountTableViewCell.swift +++ b/iOS/Settings/SettingsComboTableViewCell.swift @@ -8,18 +8,18 @@ import UIKit -class SettingsAccountTableViewCell: VibrantTableViewCell { +class SettingsComboTableViewCell: VibrantTableViewCell { - @IBOutlet weak var accountImage: UIImageView! - @IBOutlet weak var accountNameLabel: UILabel! + @IBOutlet weak var comboImage: UIImageView! + @IBOutlet weak var comboNameLabel: UILabel! override func updateVibrancy(animated: Bool) { super.updateVibrancy(animated: animated) - updateLabelVibrancy(accountNameLabel, color: labelColor, animated: animated) + updateLabelVibrancy(comboNameLabel, color: labelColor, animated: animated) let tintColor = isHighlighted || isSelected ? AppAssets.vibrantTextColor : UIColor.label UIView.animate(withDuration: duration(animated: animated)) { - self.accountImage?.tintColor = tintColor + self.comboImage?.tintColor = tintColor } } diff --git a/iOS/Settings/SettingsAccountTableViewCell.xib b/iOS/Settings/SettingsComboTableViewCell.xib similarity index 90% rename from iOS/Settings/SettingsAccountTableViewCell.xib rename to iOS/Settings/SettingsComboTableViewCell.xib index 9eafae53f..4cf193f27 100644 --- a/iOS/Settings/SettingsAccountTableViewCell.xib +++ b/iOS/Settings/SettingsComboTableViewCell.xib @@ -1,14 +1,14 @@ - + - + - + @@ -39,8 +39,8 @@ - - + + diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift index 0f3c47505..3814e3ddc 100644 --- a/iOS/Settings/SettingsViewController.swift +++ b/iOS/Settings/SettingsViewController.swift @@ -34,8 +34,9 @@ class SettingsViewController: UITableViewController { NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange), name: .UserDidAddAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange), name: .UserDidDeleteAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange), name: .DisplayNameDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(activeExtensionPointsDidChange), name: .ActiveExtensionPointsDidChange, object: nil) - tableView.register(UINib(nibName: "SettingsAccountTableViewCell", bundle: nil), forCellReuseIdentifier: "SettingsAccountTableViewCell") + tableView.register(UINib(nibName: "SettingsComboTableViewCell", bundle: nil), forCellReuseIdentifier: "SettingsComboTableViewCell") tableView.register(UINib(nibName: "SettingsTableViewCell", bundle: nil), forCellReuseIdentifier: "SettingsTableViewCell") tableView.rowHeight = UITableView.automaticDimension @@ -110,12 +111,14 @@ class SettingsViewController: UITableViewController { case 1: return AccountManager.shared.accounts.count + 1 case 2: + return ExtensionPointManager.shared.activeExtensionPoints.count + 1 + case 3: let defaultNumberOfRows = super.tableView(tableView, numberOfRowsInSection: section) if AccountManager.shared.activeAccounts.isEmpty || AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) { return defaultNumberOfRows - 1 } return defaultNumberOfRows - case 4: + case 5: return traitCollection.userInterfaceIdiom == .phone ? 2 : 1 default: return super.tableView(tableView, numberOfRowsInSection: section) @@ -133,11 +136,26 @@ class SettingsViewController: UITableViewController { cell = tableView.dequeueReusableCell(withIdentifier: "SettingsTableViewCell", for: indexPath) cell.textLabel?.text = NSLocalizedString("Add Account", comment: "Accounts") } else { - let acctCell = tableView.dequeueReusableCell(withIdentifier: "SettingsAccountTableViewCell", for: indexPath) as! SettingsAccountTableViewCell + let acctCell = tableView.dequeueReusableCell(withIdentifier: "SettingsComboTableViewCell", for: indexPath) as! SettingsComboTableViewCell acctCell.applyThemeProperties() let account = sortedAccounts[indexPath.row] - acctCell.accountImage?.image = AppAssets.image(for: account.type) - acctCell.accountNameLabel?.text = account.nameForDisplay + acctCell.comboImage?.image = AppAssets.image(for: account.type) + acctCell.comboNameLabel?.text = account.nameForDisplay + cell = acctCell + } + + case 2: + + let extensionPoints = Array(ExtensionPointManager.shared.activeExtensionPoints.values) + if indexPath.row == extensionPoints.count { + cell = tableView.dequeueReusableCell(withIdentifier: "SettingsTableViewCell", for: indexPath) + cell.textLabel?.text = NSLocalizedString("Add Extension", comment: "Extensions") + } else { + let acctCell = tableView.dequeueReusableCell(withIdentifier: "SettingsComboTableViewCell", for: indexPath) as! SettingsComboTableViewCell + acctCell.applyThemeProperties() + let extensionPoint = extensionPoints[indexPath.row] + acctCell.comboImage?.image = extensionPoint.templateImage + acctCell.comboNameLabel?.text = extensionPoint.title cell = acctCell } @@ -166,6 +184,16 @@ class SettingsViewController: UITableViewController { self.navigationController?.pushViewController(controller, animated: true) } case 2: + let extensionPoints = Array(ExtensionPointManager.shared.activeExtensionPoints.values) + if indexPath.row == extensionPoints.count { + let controller = UIStoryboard.settings.instantiateController(ofType: AddExtensionPointViewController.self) + self.navigationController?.pushViewController(controller, animated: true) + } else { + let controller = UIStoryboard.inspector.instantiateController(ofType: ExtensionPointInspectorViewController.self) + controller.extensionPoint = extensionPoints[indexPath.row] + self.navigationController?.pushViewController(controller, animated: true) + } + case 3: switch indexPath.row { case 0: tableView.selectRow(at: nil, animated: true, scrollPosition: .none) @@ -185,7 +213,7 @@ class SettingsViewController: UITableViewController { default: break } - case 3: + case 4: switch indexPath.row { case 3: let timeline = UIStoryboard.settings.instantiateController(ofType: TimelineCustomizerViewController.self) @@ -193,10 +221,10 @@ class SettingsViewController: UITableViewController { default: break } - case 5: + case 6: let colorPalette = UIStoryboard.settings.instantiateController(ofType: ColorPaletteTableViewController.self) self.navigationController?.pushViewController(colorPalette, animated: true) - case 6: + case 7: switch indexPath.row { case 0: openURL("https://ranchero.com/netnewswire/help/ios/5.0/en/") @@ -310,6 +338,10 @@ class SettingsViewController: UITableViewController { tableView.reloadData() } + @objc func activeExtensionPointsDidChange() { + tableView.reloadData() + } + } // MARK: OPML Document Picker From eb02568409e14308f89b295a34e6597564700655 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 11:15:39 -0500 Subject: [PATCH 028/108] Made Feed Providers part of the Account project --- .../Account/Account.xcodeproj/project.pbxproj | 36 ++- .../FeedProvider/FeedProvider.swift | 0 .../Twitter/TwitterFeedProvider.swift | 0 .../FeedProvider.xcodeproj/project.pbxproj | 287 ------------------ Frameworks/FeedProvider/Info.plist | 26 -- .../xcconfig/FeedProvider_project.xcconfig | 61 ---- .../FeedProvider_project_debug.xcconfig | 15 - .../FeedProvider_project_release.xcconfig | 9 - .../FeedProvider_project_test.xcconfig | 3 - .../xcconfig/FeedProvider_target.xcconfig | 13 - NetNewsWire.xcodeproj/project.pbxproj | 40 --- .../ExtensionPointIdentifer.swift | 2 +- .../ExtensionPointManager.swift | 2 +- .../TwitterFeedProvider-Extensions.swift | 7 +- 14 files changed, 33 insertions(+), 468 deletions(-) rename Frameworks/{ => Account}/FeedProvider/FeedProvider.swift (100%) rename Frameworks/{ => Account}/FeedProvider/Twitter/TwitterFeedProvider.swift (100%) delete mode 100644 Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj delete mode 100644 Frameworks/FeedProvider/Info.plist delete mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_project.xcconfig delete mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_project_debug.xcconfig delete mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_project_release.xcconfig delete mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_project_test.xcconfig delete mode 100644 Frameworks/FeedProvider/xcconfig/FeedProvider_target.xcconfig diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index d9958c367..751767700 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -33,10 +33,11 @@ 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; }; 510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; }; 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */; }; - 511076F5243BD96D00D97C8C /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 511076F4243BD96D00D97C8C /* FeedProvider.framework */; }; 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* FeedIdentifier.swift */; }; 512DD4CB2431000600C17B1F /* CKRecord+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512DD4CA2431000600C17B1F /* CKRecord+Extensions.swift */; }; 512DD4CD2431098700C17B1F /* CloudKitAccountZoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512DD4CC2431098700C17B1F /* CloudKitAccountZoneDelegate.swift */; }; + 5132AAC42448BAD90077840A /* FeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC12448BAD90077840A /* FeedProvider.swift */; }; + 5132AAC52448BAD90077840A /* TwitterFeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */; }; 513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; }; 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; }; 5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; }; @@ -272,6 +273,8 @@ 511B9803237CD4270028BCAA /* FeedIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIdentifier.swift; sourceTree = ""; }; 512DD4CA2431000600C17B1F /* CKRecord+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CKRecord+Extensions.swift"; sourceTree = ""; }; 512DD4CC2431098700C17B1F /* CloudKitAccountZoneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitAccountZoneDelegate.swift; sourceTree = ""; }; + 5132AAC12448BAD90077840A /* FeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedProvider.swift; sourceTree = ""; }; + 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwitterFeedProvider.swift; sourceTree = ""; }; 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = ""; }; 513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = ""; }; 5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = ""; }; @@ -456,7 +459,6 @@ buildActionMask = 2147483647; files = ( 84EAC4822148CC6300F154AB /* RSDatabase.framework in Frameworks */, - 511076F5243BD96D00D97C8C /* FeedProvider.framework in Frameworks */, 844B2981210CE3BF004020B3 /* RSWeb.framework in Frameworks */, 841D4D722106B40A00DD04E6 /* Articles.framework in Frameworks */, 841D4D702106B40400DD04E6 /* ArticlesDatabase.framework in Frameworks */, @@ -545,6 +547,23 @@ path = Feedbin; sourceTree = ""; }; + 5132AABB2448BA5B0077840A /* FeedProvider */ = { + isa = PBXGroup; + children = ( + 5132AAC12448BAD90077840A /* FeedProvider.swift */, + 5132AAC22448BAD90077840A /* Twitter */, + ); + path = FeedProvider; + sourceTree = ""; + }; + 5132AAC22448BAD90077840A /* Twitter */ = { + isa = PBXGroup; + children = ( + 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */, + ); + path = Twitter; + sourceTree = ""; + }; 5165D71F22835E9800D9D53D /* FeedFinder */ = { isa = PBXGroup; children = ( @@ -690,18 +709,19 @@ 84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */, 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */, 5165D71F22835E9800D9D53D /* FeedFinder */, + 5132AABB2448BA5B0077840A /* FeedProvider */, 8419742B1F6DDE84006346C4 /* LocalAccount */, - 84245C7D1FDDD2580074AFBB /* Feedbin */, - 3B826D9D2385C81C00FC1ADB /* FeedWrangler */, - 552032EA229D5D5A009559E0 /* ReaderAPI */, - 9EA31339231E368100268BA0 /* Feedly */, 5103A9D7242253DC00410853 /* CloudKit */, + 84245C7D1FDDD2580074AFBB /* Feedbin */, + 9EA31339231E368100268BA0 /* Feedly */, + 3B826D9D2385C81C00FC1ADB /* FeedWrangler */, + 769F2630AF8DC873D4A73567 /* NewsBlur */, + 552032EA229D5D5A009559E0 /* ReaderAPI */, 848935031F62484F00CEBD24 /* AccountTests */, 848934F71F62484F00CEBD24 /* Products */, 8469F80F1F6DC3C10084783E /* Frameworks */, D511EEB4202422BB00712EC3 /* xcconfig */, 848934FA1F62484F00CEBD24 /* Info.plist */, - 769F2630AF8DC873D4A73567 /* NewsBlur */, ); sourceTree = ""; usesTabs = 1; @@ -1084,6 +1104,7 @@ 9EA643D5239306AC0018A28C /* FeedlyFeedsSearchResponse.swift in Sources */, 9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */, 5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */, + 5132AAC52448BAD90077840A /* TwitterFeedProvider.swift in Sources */, 519E84AC2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift in Sources */, 512DD4CB2431000600C17B1F /* CKRecord+Extensions.swift in Sources */, 3B826DAF2385C81C00FC1ADB /* FeedWranglerGenericResult.swift in Sources */, @@ -1146,6 +1167,7 @@ 846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */, 9EA643CF2391D3560018A28C /* FeedlyAddExistingFeedOperation.swift in Sources */, 55203300229D5D5A009559E0 /* ReaderAPICaller.swift in Sources */, + 5132AAC42448BAD90077840A /* FeedProvider.swift in Sources */, 9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */, 9EAEC626233318400085D7C9 /* FeedlyStream.swift in Sources */, 9E5DE60E23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift in Sources */, diff --git a/Frameworks/FeedProvider/FeedProvider.swift b/Frameworks/Account/FeedProvider/FeedProvider.swift similarity index 100% rename from Frameworks/FeedProvider/FeedProvider.swift rename to Frameworks/Account/FeedProvider/FeedProvider.swift diff --git a/Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift similarity index 100% rename from Frameworks/FeedProvider/Twitter/TwitterFeedProvider.swift rename to Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift diff --git a/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj b/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj deleted file mode 100644 index b0e51298e..000000000 --- a/Frameworks/FeedProvider/FeedProvider.xcodeproj/project.pbxproj +++ /dev/null @@ -1,287 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - -/* Begin PBXBuildFile section */ - 5102FD7D244009CF00534F17 /* Secrets.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5102FD7C244009CF00534F17 /* Secrets.framework */; }; - 5110769B243BCF3A00D97C8C /* FeedProvider_target.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */; }; - 5110769C243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 51107697243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig */; }; - 5110769D243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 51107698243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig */; }; - 5110769E243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */; }; - 5110769F243BCF3A00D97C8C /* FeedProvider_project.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */; }; - 511076EE243BD82A00D97C8C /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 511076ED243BD82A00D97C8C /* Articles.framework */; }; - 51107722243BE0DA00D97C8C /* FeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107721243BE0DA00D97C8C /* FeedProvider.swift */; }; - 51107724243BE11800D97C8C /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107723243BE11800D97C8C /* RSParser.framework */; }; - 51107728243BE15D00D97C8C /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107727243BE15D00D97C8C /* RSCore.framework */; }; - 515A5105243D0C6B0089E588 /* TwitterFeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5104243D0C6B0089E588 /* TwitterFeedProvider.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 5102FD7C244009CF00534F17 /* Secrets.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Secrets.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5110765F243BCE0400D97C8C /* FeedProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FeedProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 51107663243BCE0400D97C8C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_target.xcconfig; sourceTree = ""; }; - 51107697243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project_test.xcconfig; sourceTree = ""; }; - 51107698243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project_release.xcconfig; sourceTree = ""; }; - 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project_debug.xcconfig; sourceTree = ""; }; - 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeedProvider_project.xcconfig; sourceTree = ""; }; - 511076ED243BD82A00D97C8C /* Articles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Articles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 51107721243BE0DA00D97C8C /* FeedProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedProvider.swift; sourceTree = ""; }; - 51107723243BE11800D97C8C /* RSParser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSParser.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 51107727243BE15D00D97C8C /* RSCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 515A5104243D0C6B0089E588 /* TwitterFeedProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterFeedProvider.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 5110765C243BCE0400D97C8C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 51107728243BE15D00D97C8C /* RSCore.framework in Frameworks */, - 51107724243BE11800D97C8C /* RSParser.framework in Frameworks */, - 5102FD7D244009CF00534F17 /* Secrets.framework in Frameworks */, - 511076EE243BD82A00D97C8C /* Articles.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 51107655243BCE0400D97C8C = { - isa = PBXGroup; - children = ( - 51107721243BE0DA00D97C8C /* FeedProvider.swift */, - 515A5103243D0C230089E588 /* Twitter */, - 51107689243BCEB300D97C8C /* xcconfig */, - 51107663243BCE0400D97C8C /* Info.plist */, - 51107660243BCE0400D97C8C /* Products */, - 511076EC243BD82A00D97C8C /* Frameworks */, - ); - sourceTree = ""; - }; - 51107660243BCE0400D97C8C /* Products */ = { - isa = PBXGroup; - children = ( - 5110765F243BCE0400D97C8C /* FeedProvider.framework */, - ); - name = Products; - sourceTree = ""; - }; - 51107689243BCEB300D97C8C /* xcconfig */ = { - isa = PBXGroup; - children = ( - 5110769A243BCF3A00D97C8C /* FeedProvider_project.xcconfig */, - 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */, - 51107698243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig */, - 51107697243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig */, - 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */, - ); - path = xcconfig; - sourceTree = ""; - }; - 511076EC243BD82A00D97C8C /* Frameworks */ = { - isa = PBXGroup; - children = ( - 5102FD7C244009CF00534F17 /* Secrets.framework */, - 51107727243BE15D00D97C8C /* RSCore.framework */, - 51107723243BE11800D97C8C /* RSParser.framework */, - 511076ED243BD82A00D97C8C /* Articles.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 515A5103243D0C230089E588 /* Twitter */ = { - isa = PBXGroup; - children = ( - 515A5104243D0C6B0089E588 /* TwitterFeedProvider.swift */, - ); - path = Twitter; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 5110765A243BCE0400D97C8C /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 5110765E243BCE0400D97C8C /* FeedProvider */ = { - isa = PBXNativeTarget; - buildConfigurationList = 51107667243BCE0400D97C8C /* Build configuration list for PBXNativeTarget "FeedProvider" */; - buildPhases = ( - 5110765A243BCE0400D97C8C /* Headers */, - 5110765B243BCE0400D97C8C /* Sources */, - 5110765C243BCE0400D97C8C /* Frameworks */, - 5110765D243BCE0400D97C8C /* Resources */, - 511076A2243BD2E600D97C8C /* Run Script: Verfiy No Build Settings */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = FeedProvider; - productName = FeedProvider; - productReference = 5110765F243BCE0400D97C8C /* FeedProvider.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 51107656243BCE0400D97C8C /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1140; - ORGANIZATIONNAME = "Ranchero Software, LLC"; - TargetAttributes = { - 5110765E243BCE0400D97C8C = { - CreatedOnToolsVersion = 11.4; - LastSwiftMigration = 1140; - }; - }; - }; - buildConfigurationList = 51107659243BCE0400D97C8C /* Build configuration list for PBXProject "FeedProvider" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 51107655243BCE0400D97C8C; - productRefGroup = 51107660243BCE0400D97C8C /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 5110765E243BCE0400D97C8C /* FeedProvider */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 5110765D243BCE0400D97C8C /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 5110769D243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig in Resources */, - 5110769F243BCF3A00D97C8C /* FeedProvider_project.xcconfig in Resources */, - 5110769E243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig in Resources */, - 5110769B243BCF3A00D97C8C /* FeedProvider_target.xcconfig in Resources */, - 5110769C243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 511076A2243BD2E600D97C8C /* Run Script: Verfiy No Build Settings */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Script: Verfiy No Build Settings"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n\nif [ $? -ne 0 ]\nthen\n echo \"error: Build Setting were found in the project.pbxproj file. Most likely you didn't intend to change this file and should revert it.\"\n exit 1\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 5110765B243BCE0400D97C8C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 515A5105243D0C6B0089E588 /* TwitterFeedProvider.swift in Sources */, - 51107722243BE0DA00D97C8C /* FeedProvider.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 51107665243BCE0400D97C8C /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51107699243BCF3A00D97C8C /* FeedProvider_project_debug.xcconfig */; - buildSettings = { - }; - name = Debug; - }; - 51107666243BCE0400D97C8C /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51107698243BCF3A00D97C8C /* FeedProvider_project_release.xcconfig */; - buildSettings = { - }; - name = Release; - }; - 51107668243BCE0400D97C8C /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */; - buildSettings = { - }; - name = Debug; - }; - 51107669243BCE0400D97C8C /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */; - buildSettings = { - }; - name = Release; - }; - 511076A0243BD20A00D97C8C /* Test */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51107697243BCF3A00D97C8C /* FeedProvider_project_test.xcconfig */; - buildSettings = { - }; - name = Test; - }; - 511076A1243BD20A00D97C8C /* Test */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51107696243BCF3A00D97C8C /* FeedProvider_target.xcconfig */; - buildSettings = { - }; - name = Test; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 51107659243BCE0400D97C8C /* Build configuration list for PBXProject "FeedProvider" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 51107665243BCE0400D97C8C /* Debug */, - 511076A0243BD20A00D97C8C /* Test */, - 51107666243BCE0400D97C8C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 51107667243BCE0400D97C8C /* Build configuration list for PBXNativeTarget "FeedProvider" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 51107668243BCE0400D97C8C /* Debug */, - 511076A1243BD20A00D97C8C /* Test */, - 51107669243BCE0400D97C8C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 51107656243BCE0400D97C8C /* Project object */; -} diff --git a/Frameworks/FeedProvider/Info.plist b/Frameworks/FeedProvider/Info.plist deleted file mode 100644 index 187bfef6d..000000000 --- a/Frameworks/FeedProvider/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2020 Ranchero Software, LLC. All rights reserved. - NSPrincipalClass - - - diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_project.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_project.xcconfig deleted file mode 100644 index 573284972..000000000 --- a/Frameworks/FeedProvider/xcconfig/FeedProvider_project.xcconfig +++ /dev/null @@ -1,61 +0,0 @@ -CODE_SIGN_IDENTITY = Developer ID Application -DEVELOPMENT_TEAM = M8L2WTLA8W -CODE_SIGN_STYLE = Manual -PROVISIONING_PROFILE_SPECIFIER = - -// See the notes in NetNewsWire_target.xcconfig on why the -// DeveloperSettings.xcconfig is #included here - -#include? "../../../SharedXcodeSettings/DeveloperSettings.xcconfig" - -SDKROOT = macosx -MACOSX_DEPLOYMENT_TARGET = 10.14 -IPHONEOS_DEPLOYMENT_TARGET = 13.0 -SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator - -CLANG_ENABLE_OBJC_WEAK = YES -SWIFT_VERSION = 5.1 -COMBINE_HIDPI_IMAGES = YES - -COPY_PHASE_STRIP = NO -ALWAYS_SEARCH_USER_PATHS = NO -CURRENT_PROJECT_VERSION = 1 -VERSION_INFO_PREFIX = -VERSIONING_SYSTEM = apple-generic -GCC_NO_COMMON_BLOCKS = YES -GCC_C_LANGUAGE_STANDARD = gnu99 -CLANG_CXX_LANGUAGE_STANDARD = gnu++0x -CLANG_CXX_LIBRARY = libc++ -CLANG_ENABLE_MODULES = YES -CLANG_ENABLE_OBJC_ARC = YES -ENABLE_STRICT_OBJC_MSGSEND = YES -CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES -CLANG_WARN_DOCUMENTATION_COMMENTS = YES -CLANG_WARN_EMPTY_BODY = YES -CLANG_WARN_BOOL_CONVERSION = YES -CLANG_WARN_CONSTANT_CONVERSION = YES -GCC_WARN_64_TO_32_BIT_CONVERSION = YES -CLANG_WARN_ENUM_CONVERSION = YES -CLANG_WARN_INT_CONVERSION = YES -CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES -CLANG_WARN_INFINITE_RECURSION = YES -GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE -CLANG_WARN_UNREACHABLE_CODE = YES -GCC_WARN_UNUSED_FUNCTION = YES -GCC_WARN_UNUSED_VARIABLE = YES -CLANG_WARN_RANGE_LOOP_ANALYSIS = YES -CLANG_WARN_SUSPICIOUS_MOVE = YES -CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_OBJC_LITERAL_CONVERSION = YES -CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -CLANG_ANALYZER_NONNULL = YES -CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE -SWIFT_SWIFT3_OBJC_INFERENCE = Off diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_project_debug.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_project_debug.xcconfig deleted file mode 100644 index beca2742a..000000000 --- a/Frameworks/FeedProvider/xcconfig/FeedProvider_project_debug.xcconfig +++ /dev/null @@ -1,15 +0,0 @@ -#include "./FeedProvider_project.xcconfig" - -DEBUG_INFORMATION_FORMAT = dwarf -ENABLE_TESTABILITY = YES -GCC_DYNAMIC_NO_PIC = NO -GCC_OPTIMIZATION_LEVEL = 0 -GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 $(inherited) - -MTL_ENABLE_DEBUG_INFO = YES -SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG -SWIFT_COMPILATION_MODE = singlefile -SWIFT_OPTIMIZATION_LEVEL = -Onone -ONLY_ACTIVE_ARCH = YES - - diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_project_release.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_project_release.xcconfig deleted file mode 100644 index 781ad3437..000000000 --- a/Frameworks/FeedProvider/xcconfig/FeedProvider_project_release.xcconfig +++ /dev/null @@ -1,9 +0,0 @@ -#include "./FeedProvider_project.xcconfig" - -DEBUG_INFORMATION_FORMAT = dwarf-with-dsym -ENABLE_NS_ASSERTIONS = NO - -MTL_ENABLE_DEBUG_INFO = NO -SWIFT_OPTIMIZATION_LEVEL = -O - -SWIFT_COMPILATION_MODE = wholemodule diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_project_test.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_project_test.xcconfig deleted file mode 100644 index 5f9ae96a0..000000000 --- a/Frameworks/FeedProvider/xcconfig/FeedProvider_project_test.xcconfig +++ /dev/null @@ -1,3 +0,0 @@ -#include "./FeedProvider_project_debug.xcconfig" - -OTHER_SWIFT_FLAGS = -DTEST $(inherited) diff --git a/Frameworks/FeedProvider/xcconfig/FeedProvider_target.xcconfig b/Frameworks/FeedProvider/xcconfig/FeedProvider_target.xcconfig deleted file mode 100644 index ece936457..000000000 --- a/Frameworks/FeedProvider/xcconfig/FeedProvider_target.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks -SKIP_INSTALL = YES -DYLIB_COMPATIBILITY_VERSION = 1 -DYLIB_CURRENT_VERSION = 1 -DYLIB_INSTALL_NAME_BASE = @rpath -LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks -DEFINES_MODULE = YES -FRAMEWORK_VERSION = A -INFOPLIST_FILE = Info.plist -PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.FeedProvider -PRODUCT_NAME = $(TARGET_NAME) -CLANG_ENABLE_MODULES = YES -APPLICATION_EXTENSION_API_ONLY = YES diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index dc73b5941..5686e21bf 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -45,10 +45,6 @@ 510C43F7243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; 510C43F8243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; 51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; }; - 511076F7243BDA8100D97C8C /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; }; - 511076F8243BDA8200D97C8C /* FeedProvider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 511076F9243BDA9600D97C8C /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; }; - 511076FA243BDA9600D97C8C /* FeedProvider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51107746243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */; }; 51107747243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */; }; 5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */; }; @@ -187,8 +183,6 @@ 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; - 519ED43F24482629007F8E94 /* FeedProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; }; - 519ED44024482629007F8E94 /* FeedProvider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51107672243BCE0500D97C8C /* FeedProvider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; }; 519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */; }; 519ED47C24488C6F007F8E94 /* ExtensionInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47B24488C6F007F8E94 /* ExtensionInspectorViewController.swift */; }; @@ -750,13 +744,6 @@ remoteGlobalIDString = 514BB41A243FFA640023B621; remoteInfo = Secrets; }; - 51107671243BCE0500D97C8C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5110766A243BCE0400D97C8C /* FeedProvider.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 5110765F243BCE0400D97C8C; - remoteInfo = FeedProvider; - }; 5131463C235A7BBE00387FDC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */; @@ -1280,7 +1267,6 @@ 51C451F92264C83E00C03939 /* Account.framework in Embed Frameworks */, 51C451F12264C83100C03939 /* ArticlesDatabase.framework in Embed Frameworks */, 517A757C24451C1500B553B9 /* OAuthSwift.framework in Embed Frameworks */, - 519ED44024482629007F8E94 /* FeedProvider.framework in Embed Frameworks */, 51C451F52264C83900C03939 /* Articles.framework in Embed Frameworks */, 51C451E92264C81000C03939 /* RSDatabase.framework in Embed Frameworks */, 51554C31228B71A10055115A /* SyncDatabase.framework in Embed Frameworks */, @@ -1316,7 +1302,6 @@ 65ED4076235DEF6C0081F399 /* Account.framework in Embed Frameworks */, 65ED4077235DEF6C0081F399 /* Articles.framework in Embed Frameworks */, 65ED4078235DEF6C0081F399 /* RSParser.framework in Embed Frameworks */, - 511076FA243BDA9600D97C8C /* FeedProvider.framework in Embed Frameworks */, 65ED4079235DEF6C0081F399 /* SyncDatabase.framework in Embed Frameworks */, 5102FD9C244009FA00534F17 /* Secrets.framework in Embed Frameworks */, 65ED407A235DEF6C0081F399 /* RSCore.framework in Embed Frameworks */, @@ -1356,7 +1341,6 @@ dstSubfolderSpec = 10; files = ( 84C37FAA20DD8D9000CA8CF5 /* RSWeb.framework in Embed Frameworks */, - 511076F8243BDA8200D97C8C /* FeedProvider.framework in Embed Frameworks */, 5102FD84244009F000534F17 /* Secrets.framework in Embed Frameworks */, 84C37FC620DD8E1D00CA8CF5 /* RSDatabase.framework in Embed Frameworks */, 84C37FAE20DD8D9900CA8CF5 /* RSTree.framework in Embed Frameworks */, @@ -1403,7 +1387,6 @@ 510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointAddTableCellView.swift; sourceTree = ""; }; 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPoint.swift; sourceTree = ""; }; 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; - 5110766A243BCE0400D97C8C /* FeedProvider.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FeedProvider.xcodeproj; path = Frameworks/FeedProvider/FeedProvider.xcodeproj; sourceTree = SOURCE_ROOT; }; 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointPreferencesViewController.swift; sourceTree = ""; }; 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorIconHeaderView.swift; sourceTree = ""; }; 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = ""; }; @@ -1863,7 +1846,6 @@ 65ED4047235DEF6C0081F399 /* RSParser.framework in Frameworks */, 65ED4048235DEF6C0081F399 /* Account.framework in Frameworks */, 65ED4049235DEF6C0081F399 /* Articles.framework in Frameworks */, - 511076F9243BDA9600D97C8C /* FeedProvider.framework in Frameworks */, 65ED404A235DEF6C0081F399 /* RSCore.framework in Frameworks */, 5102FD9B244009FA00534F17 /* Secrets.framework in Frameworks */, 65ED404B235DEF6C0081F399 /* SyncDatabase.framework in Frameworks */, @@ -1892,7 +1874,6 @@ 51C451E82264C81000C03939 /* RSDatabase.framework in Frameworks */, 51E4DB082425F9EB0091EB5B /* CloudKit.framework in Frameworks */, 51C451EC2264C81B00C03939 /* RSCore.framework in Frameworks */, - 519ED43F24482629007F8E94 /* FeedProvider.framework in Frameworks */, 51554C30228B71A10055115A /* SyncDatabase.framework in Frameworks */, 51C451E42264C80600C03939 /* RSParser.framework in Frameworks */, ); @@ -1904,7 +1885,6 @@ files = ( 65ED42DE235E74230081F399 /* Sparkle.framework in Frameworks */, 65ED42D9235E740D0081F399 /* Sparkle.framework in Frameworks */, - 511076F7243BDA8100D97C8C /* FeedProvider.framework in Frameworks */, 84C37FA920DD8D9000CA8CF5 /* RSWeb.framework in Frameworks */, 84C37FC520DD8E1D00CA8CF5 /* RSDatabase.framework in Frameworks */, 84C37FAD20DD8D9900CA8CF5 /* RSTree.framework in Frameworks */, @@ -1951,14 +1931,6 @@ path = ExtensionPoints; sourceTree = ""; }; - 5110766B243BCE0400D97C8C /* Products */ = { - isa = PBXGroup; - children = ( - 51107672243BCE0500D97C8C /* FeedProvider.framework */, - ); - name = Products; - sourceTree = ""; - }; 51107744243BEDD300D97C8C /* ExtensionPoints */ = { isa = PBXGroup; children = ( @@ -2776,7 +2748,6 @@ isa = PBXGroup; children = ( 846E77301F6EF5D600A165E2 /* Account.xcodeproj */, - 5110766A243BCE0400D97C8C /* FeedProvider.xcodeproj */, 5102FD72244008A700534F17 /* Secrets.xcodeproj */, 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */, 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */, @@ -3356,10 +3327,6 @@ ProductGroup = 8407167A2262A61100344432 /* Products */; ProjectRef = 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */; }, - { - ProductGroup = 5110766B243BCE0400D97C8C /* Products */; - ProjectRef = 5110766A243BCE0400D97C8C /* FeedProvider.xcodeproj */; - }, { ProductGroup = 517A754524451BD500B553B9 /* Products */; ProjectRef = 517A754424451BD500B553B9 /* OAuthSwift.xcodeproj */; @@ -3420,13 +3387,6 @@ remoteRef = 5102FD7A244008A700534F17 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 51107672243BCE0500D97C8C /* FeedProvider.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = FeedProvider.framework; - remoteRef = 51107671243BCE0500D97C8C /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 51554C01228B6EB50055115A /* SyncDatabase.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; diff --git a/Shared/ExtensionPoints/ExtensionPointIdentifer.swift b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift index 0ecbe5ff7..4709ffd2b 100644 --- a/Shared/ExtensionPoints/ExtensionPointIdentifer.swift +++ b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift @@ -7,7 +7,7 @@ // import Foundation -import FeedProvider +import Account import RSCore enum ExtensionPointIdentifer: Hashable { diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift index 4cc3b7a41..f620a5b04 100644 --- a/Shared/ExtensionPoints/ExtensionPointManager.swift +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -7,7 +7,7 @@ // import Foundation -import FeedProvider +import Account import RSCore import OAuthSwift diff --git a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift index eee2b444a..aa014d7c0 100644 --- a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift +++ b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift @@ -7,10 +7,7 @@ // import Foundation -import FeedProvider -import RSCore -import OAuthSwift -import Secrets +import Account extension TwitterFeedProvider: ExtensionPoint { @@ -18,7 +15,7 @@ extension TwitterFeedProvider: ExtensionPoint { static var title = NSLocalizedString("Twitter", comment: "Twitter") static var templateImage = AppAssets.extensionPointTwitter static var description: NSAttributedString = { - return TwitterFeedProvider.makeAttrString("This extension enables you to subscribe to Twitter URL's as if they were RSS feeds.") + return TwitterFeedProvider.makeAttrString("This extension enables you to subscribe to Twitter URL's as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.") }() var extensionPointID: ExtensionPointIdentifer { From b3afee02527ec281aa41fafada22863b536d7b8e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 11:25:39 -0500 Subject: [PATCH 029/108] Change Feed Provider interface to make it work better with refreshing WebFeeds. --- Frameworks/Account/FeedProvider/FeedProvider.swift | 8 ++++---- .../FeedProvider/Twitter/TwitterFeedProvider.swift | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Frameworks/Account/FeedProvider/FeedProvider.swift b/Frameworks/Account/FeedProvider/FeedProvider.swift index 231019379..47ab5d468 100644 --- a/Frameworks/Account/FeedProvider/FeedProvider.swift +++ b/Frameworks/Account/FeedProvider/FeedProvider.swift @@ -19,15 +19,15 @@ public enum FeedProviderAbility { public protocol FeedProvider { /// Informs the caller of the ability for this feed provider to service the given URL - func ability(_ url: URLComponents, forUsername: String?) -> FeedProviderAbility + func ability(_ urlComponents: URLComponents, forUsername: String?) -> FeedProviderAbility /// Provide the iconURL of the given URL - func iconURL(_ url: URLComponents, completion: @escaping (Result) -> Void) + func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) /// Construct a ParsedFeed that can be used to create and store a new Feed - func provide(_ url: URLComponents, completion: @escaping (Result) -> Void) + func provide(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) /// Refresh all the article entries (ParsedItems) - func refresh(_ url: URLComponents, completion: @escaping (Result, Error>) -> Void) + func refresh(_ webFeed: WebFeed, completion: @escaping (Result, Error>) -> Void) } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index f71d43f87..19db50fdd 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -84,16 +84,16 @@ public struct TwitterFeedProvider: FeedProvider { return .available } - public func iconURL(_ url: URLComponents, completion: @escaping (Result) -> Void) { - let screenName = extractScreenName(url) + public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { + let screenName = extractScreenName(urlComponents) fetchIconURL(screenName: screenName, completion: completion) } - public func provide(_ url: URLComponents, completion: @escaping (Result) -> Void) { + public func provide(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { // TODO: Finish implementation } - public func refresh(_ url: URLComponents, completion: @escaping (Result, Error>) -> Void) { + public func refresh(_ webFeed: WebFeed, completion: @escaping (Result, Error>) -> Void) { // TODO: Finish implementation } From a59cb3b79b5ecffcce1a90264265ff8ba1a16d87 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 12:07:57 -0500 Subject: [PATCH 030/108] Clean up the URL screen name extraction --- .../Twitter/TwitterFeedProvider.swift | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 19db50fdd..60102040f 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -13,6 +13,7 @@ import RSParser // TODO: Beef up error handling... public enum TwitterFeedProviderError: Error { + case screenNameNotFound case unknown } @@ -21,6 +22,9 @@ public struct TwitterFeedProvider: FeedProvider { private static let server = "api.twitter.com" private static let apiBase = "https://api.twitter.com/1.1/" + private static let userPaths = ["/home", "/notifications"] + private static let reservedPaths = ["/search", "/explore", "/messages", "/i", "/compose"] + public var userID: String public var screenName: String @@ -85,8 +89,11 @@ public struct TwitterFeedProvider: FeedProvider { } public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { - let screenName = extractScreenName(urlComponents) - fetchIconURL(screenName: screenName, completion: completion) + if let screenName = deriveScreenName(urlComponents) { + fetchIconURL(screenName: screenName, completion: completion) + } else { + completion(.failure(TwitterFeedProviderError.screenNameNotFound)) + } } public func provide(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { @@ -119,24 +126,18 @@ extension TwitterFeedProvider: OAuth1SwiftProvider { private extension TwitterFeedProvider { - // TODO: Full parsing routine - func extractScreenName(_ urlComponents: URLComponents) -> String { + func deriveScreenName(_ urlComponents: URLComponents) -> String? { let path = urlComponents.path - if let index = path.firstIndex(of: "?") { - let range = path.index(path.startIndex, offsetBy: 1)...index - return String(path[range]) + guard !Self.reservedPaths.contains(path) else { return nil } + + if path.isEmpty || Self.userPaths.contains(path) { + return screenName } else { return String(path.suffix(from: path.index(path.startIndex, offsetBy: 1))) } } - // TODO: Update to retrieve the full user func fetchIconURL(screenName: String, completion: @escaping (Result) -> Void) { - guard screenName != "search" else { - completion(.failure(TwitterFeedProviderError.unknown)) - return - } - let url = "\(Self.apiBase)users/show.json" let parameters = ["screen_name": screenName] From 55bc7eae0be934fc679fba8bdf64d6c600f8683c Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 13:42:40 -0500 Subject: [PATCH 031/108] Fix release config for new project --- Frameworks/Secrets/xcconfig/Secrets_project_release.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Frameworks/Secrets/xcconfig/Secrets_project_release.xcconfig b/Frameworks/Secrets/xcconfig/Secrets_project_release.xcconfig index 4fd9ec377..7643e4a2b 100644 --- a/Frameworks/Secrets/xcconfig/Secrets_project_release.xcconfig +++ b/Frameworks/Secrets/xcconfig/Secrets_project_release.xcconfig @@ -1,4 +1,4 @@ -#include "./Secret_project.xcconfig" +#include "./Secrets_project.xcconfig" DEBUG_INFORMATION_FORMAT = dwarf-with-dsym ENABLE_NS_ASSERTIONS = NO From d4b5d7bde4c8c5aae1e2bfc3e4c8efa44b492499 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 15:06:56 -0500 Subject: [PATCH 032/108] Add FeedProviderManager --- .../Account/Account.xcodeproj/project.pbxproj | 4 ++ .../FeedProvider/FeedProviderManager.swift | 42 +++++++++++++++++++ Mac/AppDelegate.swift | 3 +- .../ExtensionPointManager.swift | 9 +--- Shared/Images/WebFeedIconDownloader.swift | 2 +- iOS/AppDelegate.swift | 1 + 6 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 Frameworks/Account/FeedProvider/FeedProviderManager.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 751767700..7a5ce7774 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ 5165D72922835F7A00D9D53D /* FeedSpecifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D71D22835E9800D9D53D /* FeedSpecifier.swift */; }; 5165D72A22835F7D00D9D53D /* HTMLFeedFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D71E22835E9800D9D53D /* HTMLFeedFinder.swift */; }; 5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D73022837F3400D9D53D /* InitialFeedDownloader.swift */; }; + 516896352448EBEA00185AC5 /* FeedProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516896342448EBEA00185AC5 /* FeedProviderManager.swift */; }; 5170743C232AEDB500A461A3 /* OPMLFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5170743B232AEDB500A461A3 /* OPMLFile.swift */; }; 519E84A62433D49000D238B0 /* OPMLNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A52433D49000D238B0 /* OPMLNormalizer.swift */; }; 519E84A82434C5EF00D238B0 /* CloudKitArticlesZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */; }; @@ -292,6 +293,7 @@ 5165D71D22835E9800D9D53D /* FeedSpecifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedSpecifier.swift; sourceTree = ""; }; 5165D71E22835E9800D9D53D /* HTMLFeedFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLFeedFinder.swift; sourceTree = ""; }; 5165D73022837F3400D9D53D /* InitialFeedDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitialFeedDownloader.swift; sourceTree = ""; }; + 516896342448EBEA00185AC5 /* FeedProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedProviderManager.swift; sourceTree = ""; }; 5170743B232AEDB500A461A3 /* OPMLFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLFile.swift; sourceTree = ""; }; 518B2EA52351306200400001 /* Account_project_test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_test.xcconfig; sourceTree = ""; }; 519E84A52433D49000D238B0 /* OPMLNormalizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLNormalizer.swift; sourceTree = ""; }; @@ -551,6 +553,7 @@ isa = PBXGroup; children = ( 5132AAC12448BAD90077840A /* FeedProvider.swift */, + 516896342448EBEA00185AC5 /* FeedProviderManager.swift */, 5132AAC22448BAD90077840A /* Twitter */, ); path = FeedProvider; @@ -1114,6 +1117,7 @@ 846E77451F6EF9B900A165E2 /* Container.swift in Sources */, 9EA643D3239305680018A28C /* FeedlySearchOperation.swift in Sources */, 5150FFFE243823B800C1A442 /* CloudKitError.swift in Sources */, + 516896352448EBEA00185AC5 /* FeedProviderManager.swift in Sources */, 9E5EC15D23E0D58500A4E503 /* FeedlyFeedParser.swift in Sources */, 9E1D15532334304B00F4944C /* FeedlyGetStreamContentsOperation.swift in Sources */, 9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/FeedProviderManager.swift b/Frameworks/Account/FeedProvider/FeedProviderManager.swift new file mode 100644 index 000000000..486c232c1 --- /dev/null +++ b/Frameworks/Account/FeedProvider/FeedProviderManager.swift @@ -0,0 +1,42 @@ +// +// FeedProviderManager.swift +// Account +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +public protocol FeedProviderManagerDelegate: class { + var activeFeedProviders: [FeedProvider] { get } +} + +public final class FeedProviderManager { + + public static let shared = FeedProviderManager() + public weak var delegate: FeedProviderManagerDelegate? + + public func best(for offered: URLComponents, with username: String?) -> FeedProvider? { + if let owner = feedProviderMatching(offered, forUsername: username, ability: .owner) { + return owner + } + return feedProviderMatching(offered, forUsername: username, ability: .available) + } + +} + +private extension FeedProviderManager { + + func feedProviderMatching(_ offered: URLComponents, forUsername username: String?, ability: FeedProviderAbility) -> FeedProvider? { + if let delegate = delegate { + for feedProvider in delegate.activeFeedProviders { + if feedProvider.ability(offered, forUsername: username) == ability { + return feedProvider + } + } + } + return nil + } + +} diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 09e6682ab..4de0e011d 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -104,7 +104,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, super.init() AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!) - + FeedProviderManager.shared.delegate = ExtensionPointManager.shared + NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(inspectableObjectsDidChange(_:)), name: .InspectableObjectsDidChange, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(didWakeNotification(_:)), name: NSWorkspace.didWakeNotification, object: nil) diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift index f620a5b04..ee87e1c89 100644 --- a/Shared/ExtensionPoints/ExtensionPointManager.swift +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -15,7 +15,7 @@ public extension Notification.Name { static let ActiveExtensionPointsDidChange = Notification.Name(rawValue: "ActiveExtensionPointsDidChange") } -final class ExtensionPointManager { +final class ExtensionPointManager: FeedProviderManagerDelegate { static let shared = ExtensionPointManager() @@ -75,13 +75,6 @@ final class ExtensionPointManager { saveExtensionPointIDs() } - func bestFeedProvider(for offered: URLComponents, with username: String?) -> FeedProvider? { - if let owner = feedProviderMatching(offered, forUsername: username, ability: .owner) { - return owner - } - return feedProviderMatching(offered, forUsername: username, ability: .available) - } - } private extension ExtensionPointManager { diff --git a/Shared/Images/WebFeedIconDownloader.swift b/Shared/Images/WebFeedIconDownloader.swift index cb476b7cb..f3148570d 100644 --- a/Shared/Images/WebFeedIconDownloader.swift +++ b/Shared/Images/WebFeedIconDownloader.swift @@ -118,7 +118,7 @@ public final class WebFeedIconDownloader { return nil } - if let components = URLComponents(string: feed.url), let feedProvider = ExtensionPointManager.shared.bestFeedProvider(for: components, with: nil) { + if let components = URLComponents(string: feed.url), let feedProvider = FeedProviderManager.shared.best(for: components, with: nil) { feedProvider.iconURL(components) { result in switch result { case .success(let feedProviderURL): diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index a16e92ab2..66e21f46e 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -64,6 +64,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let documentAccountsFolder = documentAccountURL.appendingPathComponent("Accounts").absoluteString let documentAccountsFolderPath = String(documentAccountsFolder.suffix(from: documentAccountsFolder.index(documentAccountsFolder.startIndex, offsetBy: 7))) AccountManager.shared = AccountManager(accountsFolder: documentAccountsFolderPath) + FeedProviderManager.shared.delegate = ExtensionPointManager.shared NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountRefreshDidFinish(_:)), name: .AccountRefreshDidFinish, object: nil) From 49734e2bd97624c3873e0c9610b593deebfd99f6 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 15:17:22 -0500 Subject: [PATCH 033/108] Allow for username to be in the url on ability check --- .../Account/FeedProvider/Twitter/TwitterFeedProvider.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 60102040f..89ee48d8b 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -81,7 +81,9 @@ public struct TwitterFeedProvider: FeedProvider { return .none } - if let username = username, username == userID { + let bestUserName = username != nil ? username : urlComponents.user + + if bestUserName == userID { return .owner } From 3d1bfc0ca275073baaadf5482bab8839851c64c7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 16:19:32 -0500 Subject: [PATCH 034/108] Integrate Feed Provider add feed into local account delegate. --- .../LocalAccount/LocalAccountDelegate.swift | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index e3157d154..c6e64a324 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -106,11 +106,31 @@ final class LocalAccountDelegate: AccountDelegate { } func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { - guard let url = URL(string: urlString) else { + guard let url = URL(string: urlString), let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) return } + // Use a Feed Provider to create the feed if one is available for it + // Username should be part of the URL on new feed adds + if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { + + refreshProgress.addToNumberOfTasksAndRemaining(1) + feedProvider.provide(urlComponents) { result in + self.refreshProgress.completeTask() + switch result { + case .success(let parsedFeed): + let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) + account.update(feed, with: parsedFeed, {_ in}) + case .failure: + completion(.failure(AccountError.createErrorNotFound)) + } + } + + return + } + + // Use the standard feed finder to download and process the RSS feed refreshProgress.addToNumberOfTasksAndRemaining(1) FeedFinder.find(url: url) { result in From d43bf5d57a7a8d61a044d9ed0401184e0a3ac907 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 18:00:27 -0500 Subject: [PATCH 035/108] Add new feed naming logic --- .../Account/Account.xcodeproj/project.pbxproj | 4 ++ .../Account/FeedProvider/FeedProvider.swift | 4 +- .../Twitter/TwitterFeedProvider.swift | 70 ++++++++++++++++--- .../FeedProvider/Twitter/TwitterUser.swift | 21 ++++++ .../LocalAccount/LocalAccountDelegate.swift | 29 ++++++-- 5 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 7a5ce7774..7eea1c7c3 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 512DD4CD2431098700C17B1F /* CloudKitAccountZoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512DD4CC2431098700C17B1F /* CloudKitAccountZoneDelegate.swift */; }; 5132AAC42448BAD90077840A /* FeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC12448BAD90077840A /* FeedProvider.swift */; }; 5132AAC52448BAD90077840A /* TwitterFeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */; }; + 5132DE812449159100806ADE /* TwitterUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132DE802449159100806ADE /* TwitterUser.swift */; }; 513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; }; 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; }; 5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; }; @@ -276,6 +277,7 @@ 512DD4CC2431098700C17B1F /* CloudKitAccountZoneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitAccountZoneDelegate.swift; sourceTree = ""; }; 5132AAC12448BAD90077840A /* FeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedProvider.swift; sourceTree = ""; }; 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwitterFeedProvider.swift; sourceTree = ""; }; + 5132DE802449159100806ADE /* TwitterUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterUser.swift; sourceTree = ""; }; 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = ""; }; 513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = ""; }; 5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = ""; }; @@ -563,6 +565,7 @@ isa = PBXGroup; children = ( 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */, + 5132DE802449159100806ADE /* TwitterUser.swift */, ); path = Twitter; sourceTree = ""; @@ -1217,6 +1220,7 @@ 84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */, 9EF2602C23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift in Sources */, 3B826DAA2385C81C00FC1ADB /* FeedWranglerSubscription.swift in Sources */, + 5132DE812449159100806ADE /* TwitterUser.swift in Sources */, 3B826DAC2385C81C00FC1ADB /* FeedWranglerAccountDelegate.swift in Sources */, 769F295938E5A30D03DFF88F /* NewsBlurAccountDelegate.swift in Sources */, 769F2BA02EF5F329CDE45F5A /* NewsBlurAPICaller.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/FeedProvider.swift b/Frameworks/Account/FeedProvider/FeedProvider.swift index 47ab5d468..a11858d39 100644 --- a/Frameworks/Account/FeedProvider/FeedProvider.swift +++ b/Frameworks/Account/FeedProvider/FeedProvider.swift @@ -24,8 +24,8 @@ public protocol FeedProvider { /// Provide the iconURL of the given URL func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) - /// Construct a ParsedFeed that can be used to create and store a new Feed - func provide(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) + /// Construct a Name for the new feed + func assignName(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) /// Refresh all the article entries (ParsedItems) func refresh(_ webFeed: WebFeed, completion: @escaping (Result, Error>) -> Void) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 89ee48d8b..654810e5b 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -82,7 +82,6 @@ public struct TwitterFeedProvider: FeedProvider { } let bestUserName = username != nil ? username : urlComponents.user - if bestUserName == userID { return .owner } @@ -92,14 +91,65 @@ public struct TwitterFeedProvider: FeedProvider { public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { if let screenName = deriveScreenName(urlComponents) { - fetchIconURL(screenName: screenName, completion: completion) + fetchUser(screenName: screenName) { result in + switch result { + case .success(let user): + if let avatarURL = user.avatarURL { + completion(.success(avatarURL)) + } else { + completion(.failure(TwitterFeedProviderError.screenNameNotFound)) + } + case .failure(let error): + completion(.failure(error)) + } + } } else { completion(.failure(TwitterFeedProviderError.screenNameNotFound)) } } - public func provide(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { - // TODO: Finish implementation + public func assignName(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { + switch urlComponents.path { + + case "/", "/home": + let name = NSLocalizedString("Twitter Timeline", comment: "Twitter Timeline") + completion(.success(name)) + + case "/notifications/mentions": + let name = NSLocalizedString("Twitter Mentions", comment: "Twitter Mentions") + completion(.success(name)) + + case "/search": + if let query = urlComponents.queryItems?.first(where: { $0.name == "q" })?.value { + let localized = NSLocalizedString("Twitter Search: %@", comment: "Twitter Search") + let searchName = NSString.localizedStringWithFormat(localized as NSString, query) as String + completion(.success(searchName)) + } else { + let name = NSLocalizedString("Twitter Search", comment: "Twitter Search") + completion(.success(name)) + } + + default: + if let screenName = deriveScreenName(urlComponents) { + fetchUser(screenName: screenName) { result in + switch result { + case .success(let user): + if let userName = user.name { + let localized = NSLocalizedString("%@ on Twitter", comment: "Twitter Name") + let onName = NSString.localizedStringWithFormat(localized as NSString, userName) as String + completion(.success(onName)) + } else { + completion(.failure(TwitterFeedProviderError.screenNameNotFound)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } else { + completion(.failure(TwitterFeedProviderError.unknown)) + } + + } } public func refresh(_ webFeed: WebFeed, completion: @escaping (Result, Error>) -> Void) { @@ -139,17 +189,19 @@ private extension TwitterFeedProvider { } } - func fetchIconURL(screenName: String, completion: @escaping (Result) -> Void) { + func fetchUser(screenName: String, completion: @escaping (Result) -> Void) { let url = "\(Self.apiBase)users/show.json" let parameters = ["screen_name": screenName] client.get(url, parameters: parameters) { result in switch result { case .success(let response): - if let json = try? response.jsonObject() as? [String: Any], let url = json["profile_image_url_https"] as? String { - completion(.success(url)) - } else { - completion(.failure(TwitterFeedProviderError.unknown)) + let decoder = JSONDecoder() + do { + let user = try decoder.decode(TwitterUser.self, from: response.data) + completion(.success(user)) + } catch { + completion(.failure(error)) } case .failure(let error): completion(.failure(error)) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift new file mode 100644 index 000000000..2b456f4cb --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift @@ -0,0 +1,21 @@ +// +// TwitterUser.swift +// Account +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterUser: Codable { + + let name: String? + let avatarURL: String? + + enum CodingKeys: String, CodingKey { + case name = "name" + case avatarURL = "profile_image_url_https" + } + +} diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index c6e64a324..e5c269478 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -115,15 +115,30 @@ final class LocalAccountDelegate: AccountDelegate { // Username should be part of the URL on new feed adds if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { - refreshProgress.addToNumberOfTasksAndRemaining(1) - feedProvider.provide(urlComponents) { result in + refreshProgress.addToNumberOfTasksAndRemaining(2) + + feedProvider.assignName(urlComponents) { result in self.refreshProgress.completeTask() switch result { - case .success(let parsedFeed): - let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) - account.update(feed, with: parsedFeed, {_ in}) - case .failure: - completion(.failure(AccountError.createErrorNotFound)) + + case .success(let name): + let feed = account.createWebFeed(with: name, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) + feed.editedName = name + + feedProvider.refresh(feed) { result in + self.refreshProgress.completeTask() + switch result { + case .success(let parsedItems): + account.update(urlString, with: parsedItems) { _ in + container.addWebFeed(feed) + } + case .failure: + completion(.failure(AccountError.createErrorNotFound)) + } + } + + case .failure(let error): + completion(.failure(error)) } } From 660cf2930568073769455b2f39598cd55065ffed Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 18:19:49 -0500 Subject: [PATCH 036/108] Add username storage on WebFeed --- .../Twitter/TwitterFeedProvider.swift | 18 +++++++----------- .../LocalAccount/LocalAccountDelegate.swift | 12 +++++++++++- Frameworks/Account/WebFeed.swift | 9 +++++++++ Frameworks/Account/WebFeedMetadata.swift | 9 +++++++++ .../ExtensionPointIdentifer.swift | 12 +++++------- .../ExtensionPointManager.swift | 5 +++-- .../TwitterFeedProvider-Extensions.swift | 2 +- 7 files changed, 45 insertions(+), 22 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 654810e5b..a5d326ceb 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -25,7 +25,6 @@ public struct TwitterFeedProvider: FeedProvider { private static let userPaths = ["/home", "/notifications"] private static let reservedPaths = ["/search", "/explore", "/messages", "/i", "/compose"] - public var userID: String public var screenName: String private var oauthToken: String @@ -34,20 +33,18 @@ public struct TwitterFeedProvider: FeedProvider { private var client: OAuthSwiftClient public init?(tokenSuccess: OAuthSwift.TokenSuccess) { - guard let userID = tokenSuccess.parameters["user_id"] as? String, - let screenName = tokenSuccess.parameters["screen_name"] as? String else { + guard let screenName = tokenSuccess.parameters["screen_name"] as? String else { return nil } - self.userID = userID self.screenName = screenName self.oauthToken = tokenSuccess.credential.oauthToken self.oauthTokenSecret = tokenSuccess.credential.oauthTokenSecret - let tokenCredentials = Credentials(type: .oauthAccessToken, username: userID, secret: oauthToken) + let tokenCredentials = Credentials(type: .oauthAccessToken, username: screenName, secret: oauthToken) try? CredentialsManager.storeCredentials(tokenCredentials, server: Self.server) - let tokenSecretCredentials = Credentials(type: .oauthAccessTokenSecret, username: userID, secret: oauthTokenSecret) + let tokenSecretCredentials = Credentials(type: .oauthAccessTokenSecret, username: screenName, secret: oauthTokenSecret) try? CredentialsManager.storeCredentials(tokenSecretCredentials, server: Self.server) client = OAuthSwiftClient(consumerKey: Secrets.twitterConsumerKey, @@ -57,12 +54,11 @@ public struct TwitterFeedProvider: FeedProvider { version: .oauth1) } - public init?(userID: String, screenName: String) { - self.userID = userID + public init?(screenName: String) { self.screenName = screenName - guard let tokenCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessToken, server: Self.server, username: userID), - let tokenSecretCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessTokenSecret, server: Self.server, username: userID) else { + guard let tokenCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessToken, server: Self.server, username: screenName), + let tokenSecretCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessTokenSecret, server: Self.server, username: screenName) else { return nil } @@ -82,7 +78,7 @@ public struct TwitterFeedProvider: FeedProvider { } let bestUserName = username != nil ? username : urlComponents.user - if bestUserName == userID { + if bestUserName == screenName { return .owner } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index e5c269478..cc59efa67 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -122,8 +122,18 @@ final class LocalAccountDelegate: AccountDelegate { switch result { case .success(let name): - let feed = account.createWebFeed(with: name, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) + + // Move the user to the WebFeed and out of the URL + var newURLComponents = urlComponents + newURLComponents.user = nil + guard let newURL = newURLComponents.url else { + completion(.failure(AccountError.createErrorNotFound)) + return + } + + let feed = account.createWebFeed(with: name, url: newURL.absoluteString, webFeedID: newURL.absoluteString, homePageURL: nil) feed.editedName = name + feed.username = urlComponents.user feedProvider.refresh(feed) { result in self.refreshProgress.completeTask() diff --git a/Frameworks/Account/WebFeed.swift b/Frameworks/Account/WebFeed.swift index a964bc70b..1d3dec88d 100644 --- a/Frameworks/Account/WebFeed.swift +++ b/Frameworks/Account/WebFeed.swift @@ -77,6 +77,15 @@ public final class WebFeed: Feed, Renamable, Hashable { } } + public var username: String? { + get { + return metadata.username + } + set { + metadata.username = newValue + } + } + public var name: String? public var authors: Set? { diff --git a/Frameworks/Account/WebFeedMetadata.swift b/Frameworks/Account/WebFeedMetadata.swift index 424cdc638..30323d8a7 100644 --- a/Frameworks/Account/WebFeedMetadata.swift +++ b/Frameworks/Account/WebFeedMetadata.swift @@ -21,6 +21,7 @@ final class WebFeedMetadata: Codable { case homePageURL case iconURL case faviconURL + case username case editedName case authors case contentHash @@ -63,6 +64,14 @@ final class WebFeedMetadata: Codable { } } + var username: String? { + didSet { + if username != oldValue { + valueDidChange(.username) + } + } + } + var editedName: String? { didSet { if editedName != oldValue { diff --git a/Shared/ExtensionPoints/ExtensionPointIdentifer.swift b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift index 4709ffd2b..a23328a48 100644 --- a/Shared/ExtensionPoints/ExtensionPointIdentifer.swift +++ b/Shared/ExtensionPoints/ExtensionPointIdentifer.swift @@ -15,7 +15,7 @@ enum ExtensionPointIdentifer: Hashable { case marsEdit case microblog #endif - case twitter(String, String) + case twitter(String) var extensionPointType: ExtensionPoint.Type { switch self { @@ -42,10 +42,9 @@ enum ExtensionPointIdentifer: Hashable { "type": "microblog" ] #endif - case .twitter(let userID, let screenName): + case .twitter(let screenName): return [ "type": "twitter", - "userID": userID, "screenName": screenName ] } @@ -62,8 +61,8 @@ enum ExtensionPointIdentifer: Hashable { self = ExtensionPointIdentifer.microblog #endif case "twitter": - guard let userID = userInfo["userID"] as? String, let screenName = userInfo["screenName"] as? String else { return nil } - self = ExtensionPointIdentifer.twitter(userID, screenName) + guard let screenName = userInfo["screenName"] as? String else { return nil } + self = ExtensionPointIdentifer.twitter(screenName) default: return nil } @@ -77,9 +76,8 @@ enum ExtensionPointIdentifer: Hashable { case .microblog: hasher.combine("microblog") #endif - case .twitter(let userID, let screenName): + case .twitter(let screenName): hasher.combine("twitter") - hasher.combine(userID) hasher.combine(screenName) } } diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift index ee87e1c89..723d3180c 100644 --- a/Shared/ExtensionPoints/ExtensionPointManager.swift +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -36,6 +36,7 @@ final class ExtensionPointManager: FeedProviderManagerDelegate { } return available + } var activeSendToCommands: [SendToCommand] { @@ -122,8 +123,8 @@ private extension ExtensionPointManager { case .microblog: return SendToMicroBlogCommand() #endif - case .twitter(let userID, let screenName): - return TwitterFeedProvider(userID: userID, screenName: screenName) + case .twitter(let screenName): + return TwitterFeedProvider(screenName: screenName) } } diff --git a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift index aa014d7c0..92bd77e20 100644 --- a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift +++ b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift @@ -19,7 +19,7 @@ extension TwitterFeedProvider: ExtensionPoint { }() var extensionPointID: ExtensionPointIdentifer { - return ExtensionPointIdentifer.twitter(userID, screenName) + return ExtensionPointIdentifer.twitter(screenName) } var title: String { From 9be641c094f9dcd3808f16b08c2c5693a4be512a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 20:18:47 -0500 Subject: [PATCH 037/108] Add initial tweet retrieval. --- .../Account/Account.xcodeproj/project.pbxproj | 4 ++ .../Account/FeedProvider/Twitter/Tweet.swift | 35 ++++++++++ .../Twitter/TwitterFeedProvider.swift | 65 +++++++++++++++++-- .../FeedProvider/Twitter/TwitterUser.swift | 2 + .../LocalAccount/LocalAccountDelegate.swift | 1 + 5 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 Frameworks/Account/FeedProvider/Twitter/Tweet.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 7eea1c7c3..450707e1c 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 5132AAC42448BAD90077840A /* FeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC12448BAD90077840A /* FeedProvider.swift */; }; 5132AAC52448BAD90077840A /* TwitterFeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */; }; 5132DE812449159100806ADE /* TwitterUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132DE802449159100806ADE /* TwitterUser.swift */; }; + 5132DE832449306F00806ADE /* Tweet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132DE822449306F00806ADE /* Tweet.swift */; }; 513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; }; 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; }; 5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; }; @@ -278,6 +279,7 @@ 5132AAC12448BAD90077840A /* FeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedProvider.swift; sourceTree = ""; }; 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwitterFeedProvider.swift; sourceTree = ""; }; 5132DE802449159100806ADE /* TwitterUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterUser.swift; sourceTree = ""; }; + 5132DE822449306F00806ADE /* Tweet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tweet.swift; sourceTree = ""; }; 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = ""; }; 513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = ""; }; 5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = ""; }; @@ -566,6 +568,7 @@ children = ( 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */, 5132DE802449159100806ADE /* TwitterUser.swift */, + 5132DE822449306F00806ADE /* Tweet.swift */, ); path = Twitter; sourceTree = ""; @@ -1127,6 +1130,7 @@ 552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */, 9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */, 9E5EC15B23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift in Sources */, + 5132DE832449306F00806ADE /* Tweet.swift in Sources */, 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */, 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift new file mode 100644 index 000000000..eb9bd50d5 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift @@ -0,0 +1,35 @@ +// +// Tweet.swift +// Account +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct Tweet: Codable { + + let createdAt: Date? + let idStr: String? + let text: String? + let user: TwitterUser + let truncated: Bool + let extendedTweet: ExtendedTweet? + + enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case idStr = "id_str" + case text = "text" + case user = "user" + case truncated = "truncated" + case extendedTweet = "extended_tweet" + } + +} + +struct ExtendedTweet: Codable { + + let full_text: String? + +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index a5d326ceb..0e7538334 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -87,7 +87,7 @@ public struct TwitterFeedProvider: FeedProvider { public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { if let screenName = deriveScreenName(urlComponents) { - fetchUser(screenName: screenName) { result in + retrieveUser(screenName: screenName) { result in switch result { case .success(let user): if let avatarURL = user.avatarURL { @@ -127,7 +127,7 @@ public struct TwitterFeedProvider: FeedProvider { default: if let screenName = deriveScreenName(urlComponents) { - fetchUser(screenName: screenName) { result in + retrieveUser(screenName: screenName) { result in switch result { case .success(let user): if let userName = user.name { @@ -149,7 +149,41 @@ public struct TwitterFeedProvider: FeedProvider { } public func refresh(_ webFeed: WebFeed, completion: @escaping (Result, Error>) -> Void) { - // TODO: Finish implementation + let api = "statuses/user_timeline.json" + + retrieveTweets(api: api) { result in + switch result { + case .success(let tweets): + + var parsedItems = Set() + for tweet in tweets { + guard let idStr = tweet.idStr, let userScreenName = tweet.user.screenName else { continue } + let parsedItem = ParsedItem(syncServiceID: idStr, + uniqueID: idStr, + feedURL: webFeed.url, + url: "https://twitter.com/\(userScreenName)/status/\(idStr)", + externalURL: nil, + title: nil, + language: nil, + contentHTML: tweet.text, + contentText: tweet.text, + summary: tweet.text, + imageURL: nil, + bannerImageURL: nil, + datePublished: tweet.createdAt, + dateModified: nil, + authors: nil, + tags: nil, + attachments: nil) + parsedItems.insert(parsedItem) + } + + completion(.success(parsedItems)) + + case .failure(let error): + completion(.failure(error)) + } + } } } @@ -185,7 +219,7 @@ private extension TwitterFeedProvider { } } - func fetchUser(screenName: String, completion: @escaping (Result) -> Void) { + func retrieveUser(screenName: String, completion: @escaping (Result) -> Void) { let url = "\(Self.apiBase)users/show.json" let parameters = ["screen_name": screenName] @@ -205,4 +239,27 @@ private extension TwitterFeedProvider { } } + func retrieveTweets(api: String, completion: @escaping (Result<[Tweet], Error>) -> Void) { + let url = "\(Self.apiBase)\(api)" + let parameters = [String: Any]() + + client.get(url, parameters: parameters) { result in + switch result { + case .success(let response): + let decoder = JSONDecoder() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy" + decoder.dateDecodingStrategy = .formatted(dateFormatter) + do { + let tweets = try decoder.decode([Tweet].self, from: response.data) + completion(.success(tweets)) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } + } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift index 2b456f4cb..5b2aa24c8 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift @@ -11,10 +11,12 @@ import Foundation struct TwitterUser: Codable { let name: String? + let screenName: String? let avatarURL: String? enum CodingKeys: String, CodingKey { case name = "name" + case screenName = "screen_name" case avatarURL = "profile_image_url_https" } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index cc59efa67..9b29c32c4 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -141,6 +141,7 @@ final class LocalAccountDelegate: AccountDelegate { case .success(let parsedItems): account.update(urlString, with: parsedItems) { _ in container.addWebFeed(feed) + completion(.success(feed)) } case .failure: completion(.failure(AccountError.createErrorNotFound)) From 7dc34e9655e13ce60875dbd0b654cdc4f480289e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 04:29:54 -0500 Subject: [PATCH 038/108] Fix web feed sidebar not showing sometimes bug. --- .../Account/LocalAccount/LocalAccountDelegate.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 9b29c32c4..68239af65 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -134,13 +134,13 @@ final class LocalAccountDelegate: AccountDelegate { let feed = account.createWebFeed(with: name, url: newURL.absoluteString, webFeedID: newURL.absoluteString, homePageURL: nil) feed.editedName = name feed.username = urlComponents.user - + container.addWebFeed(feed) + feedProvider.refresh(feed) { result in self.refreshProgress.completeTask() switch result { case .success(let parsedItems): account.update(urlString, with: parsedItems) { _ in - container.addWebFeed(feed) completion(.success(feed)) } case .failure: @@ -176,7 +176,8 @@ final class LocalAccountDelegate: AccountDelegate { } let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) - + container.addWebFeed(feed) + InitialFeedDownloader.download(url) { parsedFeed in self.refreshProgress.completeTask() @@ -186,7 +187,6 @@ final class LocalAccountDelegate: AccountDelegate { feed.editedName = name - container.addWebFeed(feed) completion(.success(feed)) } From de150ad281214cead1a9d093d224426fc0ac5fd7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 04:30:06 -0500 Subject: [PATCH 039/108] Add author to tweets. --- .../Twitter/TwitterFeedProvider.swift | 70 ++++++++++++------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 0e7538334..31c07d852 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -21,6 +21,7 @@ public struct TwitterFeedProvider: FeedProvider { private static let server = "api.twitter.com" private static let apiBase = "https://api.twitter.com/1.1/" + private static let dateFormat = "EEE MMM dd HH:mm:ss Z yyyy" private static let userPaths = ["/home", "/notifications"] private static let reservedPaths = ["/search", "/explore", "/messages", "/i", "/compose"] @@ -149,37 +150,13 @@ public struct TwitterFeedProvider: FeedProvider { } public func refresh(_ webFeed: WebFeed, completion: @escaping (Result, Error>) -> Void) { - let api = "statuses/user_timeline.json" + let api = "statuses/home_timeline.json" retrieveTweets(api: api) { result in switch result { case .success(let tweets): - - var parsedItems = Set() - for tweet in tweets { - guard let idStr = tweet.idStr, let userScreenName = tweet.user.screenName else { continue } - let parsedItem = ParsedItem(syncServiceID: idStr, - uniqueID: idStr, - feedURL: webFeed.url, - url: "https://twitter.com/\(userScreenName)/status/\(idStr)", - externalURL: nil, - title: nil, - language: nil, - contentHTML: tweet.text, - contentText: tweet.text, - summary: tweet.text, - imageURL: nil, - bannerImageURL: nil, - datePublished: tweet.createdAt, - dateModified: nil, - authors: nil, - tags: nil, - attachments: nil) - parsedItems.insert(parsedItem) - } - + let parsedItems = self.makeParsedItems(webFeed.url, tweets) completion(.success(parsedItems)) - case .failure(let error): completion(.failure(error)) } @@ -248,7 +225,7 @@ private extension TwitterFeedProvider { case .success(let response): let decoder = JSONDecoder() let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy" + dateFormatter.dateFormat = Self.dateFormat decoder.dateDecodingStrategy = .formatted(dateFormatter) do { let tweets = try decoder.decode([Tweet].self, from: response.data) @@ -262,4 +239,43 @@ private extension TwitterFeedProvider { } } + func makeParsedItems(_ webFeedURL: String, _ tweets: [Tweet]) -> Set { + var parsedItems = Set() + + for tweet in tweets { + guard let idStr = tweet.idStr, let userScreenName = tweet.user.screenName else { continue } + + let userURL = makeUserURL(userScreenName) + + let parsedItem = ParsedItem(syncServiceID: idStr, + uniqueID: idStr, + feedURL: webFeedURL, + url: "\(userURL)/status/\(idStr)", + externalURL: nil, + title: nil, + language: nil, + contentHTML: tweet.text, + contentText: tweet.text, + summary: tweet.text, + imageURL: nil, + bannerImageURL: nil, + datePublished: tweet.createdAt, + dateModified: nil, + authors: makeParsedAuthors(tweet.user), + tags: nil, + attachments: nil) + parsedItems.insert(parsedItem) + } + + return parsedItems + } + + func makeUserURL(_ screenName: String) -> String { + return "https://twitter.com/\(screenName)" + } + + func makeParsedAuthors(_ user: TwitterUser) -> Set { + return Set([ParsedAuthor(name: user.name, url: makeUserURL(user.screenName!), avatarURL: user.avatarURL, emailAddress: nil)]) + } + } From a31db7e0245d3a51dbf23a515bdedfdbf5e22b43 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 04:30:54 -0500 Subject: [PATCH 040/108] Change to use the user name for status timeline. --- .../Account/FeedProvider/Twitter/TwitterFeedProvider.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 31c07d852..bd27fa81a 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -132,9 +132,7 @@ public struct TwitterFeedProvider: FeedProvider { switch result { case .success(let user): if let userName = user.name { - let localized = NSLocalizedString("%@ on Twitter", comment: "Twitter Name") - let onName = NSString.localizedStringWithFormat(localized as NSString, userName) as String - completion(.success(onName)) + completion(.success(userName)) } else { completion(.failure(TwitterFeedProviderError.screenNameNotFound)) } From 95f1f64aa70b698274e61ecd6dc254afac8ca662 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 05:32:52 -0500 Subject: [PATCH 041/108] Add Feed Provider refreshes to Local Account. --- .../LocalAccount/LocalAccountDelegate.swift | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 68239af65..29ae9a64d 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -7,6 +7,7 @@ // import Foundation +import os.log import RSCore import RSParser import Articles @@ -20,6 +21,8 @@ public enum LocalAccountDelegateError: String, Error { final class LocalAccountDelegate: AccountDelegate { + private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "LocalAccount") + weak var account: Account? private lazy var refresher: LocalAccountRefresher? = { @@ -36,23 +39,54 @@ final class LocalAccountDelegate: AccountDelegate { var accountMetadata: AccountMetadata? let refreshProgress = DownloadProgress(numberOfTasks: 0) - var refreshAllCompletion: ((Result) -> Void)? = nil func receiveRemoteNotification(for account: Account, userInfo: [AnyHashable : Any], completion: @escaping () -> Void) { completion() } func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { - guard refreshAllCompletion == nil else { + guard refreshProgress.isComplete else { completion(.success(())) return } - - refreshAllCompletion = completion - + + var refresherWebFeeds = Set() let webFeeds = account.flattenedWebFeeds() - refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count) - refresher?.refreshFeeds(webFeeds) + + let group = DispatchGroup() + + for webFeed in webFeeds { + if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components, with: webFeed.username) { + refreshProgress.addToNumberOfTasksAndRemaining(1) + group.enter() + feedProvider.refresh(webFeed) { result in + switch result { + case .success(let parsedItems): + account.update(webFeed.webFeedID, with: parsedItems) { _ in + self.refreshProgress.completeTask() + group.leave() + } + case .failure(let error): + os_log(.error, log: self.log, "Feed Provider refresh error: %@.", error.localizedDescription) + self.refreshProgress.completeTask() + group.leave() + } + } + } else { + refresherWebFeeds.insert(webFeed) + } + } + + refreshProgress.addToNumberOfTasksAndRemaining(refresherWebFeeds.count) + group.enter() + refresher?.refreshFeeds(refresherWebFeeds) { + group.leave() + } + + group.notify(queue: DispatchQueue.main) { + completion(.success(())) + } + } func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { @@ -176,19 +210,20 @@ final class LocalAccountDelegate: AccountDelegate { } let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) - container.addWebFeed(feed) + feed.editedName = name InitialFeedDownloader.download(url) { parsedFeed in self.refreshProgress.completeTask() if let parsedFeed = parsedFeed { - account.update(feed, with: parsedFeed, {_ in}) + account.update(feed, with: parsedFeed, {_ in + container.addWebFeed(feed) + completion(.success(feed)) + }) + } else { + completion(.failure(AccountError.createErrorNotFound)) } - feed.editedName = name - - completion(.success(feed)) - } case .failure: @@ -292,8 +327,6 @@ extension LocalAccountDelegate: LocalAccountRefresherDelegate { func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher) { self.refreshProgress.clear() account?.metadata.lastArticleFetchEndTime = Date() - refreshAllCompletion?(.success(())) - refreshAllCompletion = nil } } From 8331d37dc1ba3474dece84fbecef25249c77a6e2 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 08:58:06 -0500 Subject: [PATCH 042/108] Fix add new feed to keep sidebar consistent. --- .../Account/LocalAccount/LocalAccountDelegate.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 29ae9a64d..eb44b564e 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -192,6 +192,7 @@ final class LocalAccountDelegate: AccountDelegate { // Use the standard feed finder to download and process the RSS feed refreshProgress.addToNumberOfTasksAndRemaining(1) + BatchUpdate.shared.start() FeedFinder.find(url: url) { result in switch result { @@ -199,34 +200,39 @@ final class LocalAccountDelegate: AccountDelegate { guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), let url = URL(string: bestFeedSpecifier.urlString) else { self.refreshProgress.completeTask() + BatchUpdate.shared.end() completion(.failure(AccountError.createErrorNotFound)) return } if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { self.refreshProgress.completeTask() + BatchUpdate.shared.end() completion(.failure(AccountError.createErrorAlreadySubscribed)) return } let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) feed.editedName = name + container.addWebFeed(feed) InitialFeedDownloader.download(url) { parsedFeed in self.refreshProgress.completeTask() if let parsedFeed = parsedFeed { account.update(feed, with: parsedFeed, {_ in - container.addWebFeed(feed) + BatchUpdate.shared.end() completion(.success(feed)) }) } else { + BatchUpdate.shared.end() completion(.failure(AccountError.createErrorNotFound)) } } case .failure: + BatchUpdate.shared.end() self.refreshProgress.completeTask() completion(.failure(AccountError.createErrorNotFound)) } From 655e30069fc4cf5d5e10811e2e303ee51bd45da9 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 09:11:37 -0500 Subject: [PATCH 043/108] Refactor to make create feed code more readable. --- .../LocalAccount/LocalAccountDelegate.swift | 190 +++++++++--------- 1 file changed, 99 insertions(+), 91 deletions(-) diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index eb44b564e..41efd18ff 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -145,100 +145,12 @@ final class LocalAccountDelegate: AccountDelegate { return } - // Use a Feed Provider to create the feed if one is available for it // Username should be part of the URL on new feed adds if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { - - refreshProgress.addToNumberOfTasksAndRemaining(2) - - feedProvider.assignName(urlComponents) { result in - self.refreshProgress.completeTask() - switch result { - - case .success(let name): - - // Move the user to the WebFeed and out of the URL - var newURLComponents = urlComponents - newURLComponents.user = nil - guard let newURL = newURLComponents.url else { - completion(.failure(AccountError.createErrorNotFound)) - return - } - - let feed = account.createWebFeed(with: name, url: newURL.absoluteString, webFeedID: newURL.absoluteString, homePageURL: nil) - feed.editedName = name - feed.username = urlComponents.user - container.addWebFeed(feed) - - feedProvider.refresh(feed) { result in - self.refreshProgress.completeTask() - switch result { - case .success(let parsedItems): - account.update(urlString, with: parsedItems) { _ in - completion(.success(feed)) - } - case .failure: - completion(.failure(AccountError.createErrorNotFound)) - } - } - - case .failure(let error): - completion(.failure(error)) - } - } - - return + createProviderWebFeed(for: account, urlComponents: urlComponents, name: name, container: container, feedProvider: feedProvider, completion: completion) + } else { + createRSSWebFeed(for: account, url: url, name: name, container: container, completion: completion) } - - // Use the standard feed finder to download and process the RSS feed - refreshProgress.addToNumberOfTasksAndRemaining(1) - BatchUpdate.shared.start() - FeedFinder.find(url: url) { result in - - switch result { - case .success(let feedSpecifiers): - guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), - let url = URL(string: bestFeedSpecifier.urlString) else { - self.refreshProgress.completeTask() - BatchUpdate.shared.end() - completion(.failure(AccountError.createErrorNotFound)) - return - } - - if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { - self.refreshProgress.completeTask() - BatchUpdate.shared.end() - completion(.failure(AccountError.createErrorAlreadySubscribed)) - return - } - - let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) - feed.editedName = name - container.addWebFeed(feed) - - InitialFeedDownloader.download(url) { parsedFeed in - self.refreshProgress.completeTask() - - if let parsedFeed = parsedFeed { - account.update(feed, with: parsedFeed, {_ in - BatchUpdate.shared.end() - completion(.success(feed)) - }) - } else { - BatchUpdate.shared.end() - completion(.failure(AccountError.createErrorNotFound)) - } - - } - - case .failure: - BatchUpdate.shared.end() - self.refreshProgress.completeTask() - completion(.failure(AccountError.createErrorNotFound)) - } - - } - } func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { @@ -336,3 +248,99 @@ extension LocalAccountDelegate: LocalAccountRefresherDelegate { } } + +private extension LocalAccountDelegate { + + func createProviderWebFeed(for account: Account, urlComponents: URLComponents, name: String?, container: Container, feedProvider: FeedProvider, completion: @escaping (Result) -> Void) { + refreshProgress.addToNumberOfTasksAndRemaining(2) + + feedProvider.assignName(urlComponents) { result in + self.refreshProgress.completeTask() + switch result { + + case .success(let name): + + // Move the user to the WebFeed and out of the URL + var newURLComponents = urlComponents + newURLComponents.user = nil + guard let newURLString = newURLComponents.url?.absoluteString else { + completion(.failure(AccountError.createErrorNotFound)) + return + } + + let feed = account.createWebFeed(with: name, url: newURLString, webFeedID: newURLString, homePageURL: nil) + feed.editedName = name + feed.username = urlComponents.user + container.addWebFeed(feed) + + feedProvider.refresh(feed) { result in + self.refreshProgress.completeTask() + switch result { + case .success(let parsedItems): + account.update(newURLString, with: parsedItems) { _ in + completion(.success(feed)) + } + case .failure: + completion(.failure(AccountError.createErrorNotFound)) + } + } + + case .failure(let error): + completion(.failure(error)) + } + } + } + + func createRSSWebFeed(for account: Account, url: URL, name: String?, container: Container, completion: @escaping (Result) -> Void) { + + refreshProgress.addToNumberOfTasksAndRemaining(1) + BatchUpdate.shared.start() + FeedFinder.find(url: url) { result in + + switch result { + case .success(let feedSpecifiers): + guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), + let url = URL(string: bestFeedSpecifier.urlString) else { + self.refreshProgress.completeTask() + BatchUpdate.shared.end() + completion(.failure(AccountError.createErrorNotFound)) + return + } + + if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { + self.refreshProgress.completeTask() + BatchUpdate.shared.end() + completion(.failure(AccountError.createErrorAlreadySubscribed)) + return + } + + let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) + feed.editedName = name + container.addWebFeed(feed) + + InitialFeedDownloader.download(url) { parsedFeed in + self.refreshProgress.completeTask() + + if let parsedFeed = parsedFeed { + account.update(feed, with: parsedFeed, {_ in + BatchUpdate.shared.end() + completion(.success(feed)) + }) + } else { + BatchUpdate.shared.end() + completion(.failure(AccountError.createErrorNotFound)) + } + + } + + case .failure: + BatchUpdate.shared.end() + self.refreshProgress.completeTask() + completion(.failure(AccountError.createErrorNotFound)) + } + + } + + } + +} From 984322453f63db0f6f4a4338656125f54d909efd Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 09:14:27 -0500 Subject: [PATCH 044/108] Commented why we use BatchUpdate on RSS feed adds because I keep forgetting why. --- Frameworks/Account/LocalAccount/LocalAccountDelegate.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 41efd18ff..c88fa7685 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -292,9 +292,12 @@ private extension LocalAccountDelegate { } func createRSSWebFeed(for account: Account, url: URL, name: String?, container: Container, completion: @escaping (Result) -> Void) { - - refreshProgress.addToNumberOfTasksAndRemaining(1) + + // We need to use a batch update here because we need to assign add the feed to the + // container before the name has been downloaded. This will put it in the sidebar + // with an Untitled name if we don't delay it being added to the sidebar. BatchUpdate.shared.start() + refreshProgress.addToNumberOfTasksAndRemaining(1) FeedFinder.find(url: url) { result in switch result { From 687e1105f01990fde2675dd1d375ad84518a54f7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 10:18:59 -0500 Subject: [PATCH 045/108] Change tweets to only show display text now. --- .../Account/FeedProvider/Twitter/Tweet.swift | 12 +++++++- .../Twitter/TwitterFeedProvider.swift | 30 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift index eb9bd50d5..c01734593 100644 --- a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift +++ b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift @@ -13,16 +13,20 @@ struct Tweet: Codable { let createdAt: Date? let idStr: String? let text: String? + let displayTextRange: [Int]? let user: TwitterUser let truncated: Bool + let retweeted: Bool let extendedTweet: ExtendedTweet? enum CodingKeys: String, CodingKey { case createdAt = "created_at" case idStr = "id_str" case text = "text" + case displayTextRange = "display_text_range" case user = "user" case truncated = "truncated" + case retweeted = "retweeted" case extendedTweet = "extended_tweet" } @@ -30,6 +34,12 @@ struct Tweet: Codable { struct ExtendedTweet: Codable { - let full_text: String? + let fullText: String? + let displayTextRange: [Int]? + + enum CodingKeys: String, CodingKey { + case fullText = "full_text" + case displayTextRange = "display_text_range" + } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index bd27fa81a..4cf6a75ed 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -252,9 +252,9 @@ private extension TwitterFeedProvider { externalURL: nil, title: nil, language: nil, - contentHTML: tweet.text, - contentText: tweet.text, - summary: tweet.text, + contentHTML: makeTweetHTML(tweet), + contentText: makeTweetText(tweet), + summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: tweet.createdAt, @@ -276,4 +276,28 @@ private extension TwitterFeedProvider { return Set([ParsedAuthor(name: user.name, url: makeUserURL(user.screenName!), avatarURL: user.avatarURL, emailAddress: nil)]) } + func makeTweetText(_ tweet: Tweet) -> String? { + if tweet.truncated, let extendedText = tweet.extendedTweet?.fullText { + if let displayRange = tweet.extendedTweet?.displayTextRange, displayRange.count > 1 { + let startIndex = extendedText.index(extendedText.startIndex, offsetBy: displayRange[0]) + let endIndex = extendedText.index(extendedText.startIndex, offsetBy: displayRange[1]) + return String(extendedText[startIndex...endIndex]) + } else { + return extendedText + } + } else { + if let text = tweet.text, let displayRange = tweet.displayTextRange { + let startIndex = text.index(text.startIndex, offsetBy: displayRange[0]) + let endIndex = text.index(text.startIndex, offsetBy: displayRange[1]) + return String(text[startIndex...endIndex]) + } else { + return tweet.text + } + } + } + + func makeTweetHTML(_ tweet: Tweet) -> String? { + return nil + } + } From 2d617984579bbcc4d6906a5fc9cdc58e9862fa88 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 11:20:22 -0500 Subject: [PATCH 046/108] Changed to using extended tweets. --- .../Account/FeedProvider/Twitter/Tweet.swift | 18 ++---------- .../Twitter/TwitterFeedProvider.swift | 28 ++++++++----------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift index c01734593..6e8aecbdc 100644 --- a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift +++ b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift @@ -12,34 +12,20 @@ struct Tweet: Codable { let createdAt: Date? let idStr: String? - let text: String? + let fullText: String? let displayTextRange: [Int]? let user: TwitterUser let truncated: Bool let retweeted: Bool - let extendedTweet: ExtendedTweet? enum CodingKeys: String, CodingKey { case createdAt = "created_at" case idStr = "id_str" - case text = "text" + case fullText = "full_text" case displayTextRange = "display_text_range" case user = "user" case truncated = "truncated" case retweeted = "retweeted" - case extendedTweet = "extended_tweet" - } - -} - -struct ExtendedTweet: Codable { - - let fullText: String? - let displayTextRange: [Int]? - - enum CodingKeys: String, CodingKey { - case fullText = "full_text" - case displayTextRange = "display_text_range" } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 4cf6a75ed..4626b6cd8 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -216,7 +216,7 @@ private extension TwitterFeedProvider { func retrieveTweets(api: String, completion: @escaping (Result<[Tweet], Error>) -> Void) { let url = "\(Self.apiBase)\(api)" - let parameters = [String: Any]() + let parameters = ["tweet_mode": "extended"] client.get(url, parameters: parameters) { result in switch result { @@ -225,6 +225,12 @@ private extension TwitterFeedProvider { let dateFormatter = DateFormatter() dateFormatter.dateFormat = Self.dateFormat decoder.dateDecodingStrategy = .formatted(dateFormatter) + let jsonString = String(data: response.data, encoding: .utf8) + + let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("twitter.json") + print("******** writing to: \(url.path)") + try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) + do { let tweets = try decoder.decode([Tweet].self, from: response.data) completion(.success(tweets)) @@ -277,22 +283,12 @@ private extension TwitterFeedProvider { } func makeTweetText(_ tweet: Tweet) -> String? { - if tweet.truncated, let extendedText = tweet.extendedTweet?.fullText { - if let displayRange = tweet.extendedTweet?.displayTextRange, displayRange.count > 1 { - let startIndex = extendedText.index(extendedText.startIndex, offsetBy: displayRange[0]) - let endIndex = extendedText.index(extendedText.startIndex, offsetBy: displayRange[1]) - return String(extendedText[startIndex...endIndex]) - } else { - return extendedText - } + if let text = tweet.fullText, let displayRange = tweet.displayTextRange, displayRange.count > 1, + let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), + let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) { + return String(text[startIndex.. Date: Fri, 17 Apr 2020 15:02:52 -0500 Subject: [PATCH 047/108] Change to use retweet text for plain text when available. --- Frameworks/Account/FeedProvider/Twitter/Tweet.swift | 4 +++- .../Account/FeedProvider/Twitter/TwitterFeedProvider.swift | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift index 6e8aecbdc..3da8930c0 100644 --- a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift +++ b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift @@ -8,7 +8,7 @@ import Foundation -struct Tweet: Codable { +final class Tweet: Codable { let createdAt: Date? let idStr: String? @@ -17,6 +17,7 @@ struct Tweet: Codable { let user: TwitterUser let truncated: Bool let retweeted: Bool + let retweetedStatus: Tweet? enum CodingKeys: String, CodingKey { case createdAt = "created_at" @@ -26,6 +27,7 @@ struct Tweet: Codable { case user = "user" case truncated = "truncated" case retweeted = "retweeted" + case retweetedStatus = "retweeted_status" } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 4626b6cd8..d10852912 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -283,7 +283,8 @@ private extension TwitterFeedProvider { } func makeTweetText(_ tweet: Tweet) -> String? { - if let text = tweet.fullText, let displayRange = tweet.displayTextRange, displayRange.count > 1, + let tweetToUse = tweet.retweetedStatus != nil ? tweet.retweetedStatus! : tweet + if let text = tweetToUse.fullText, let displayRange = tweetToUse.displayTextRange, displayRange.count > 1, let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) { return String(text[startIndex.. Date: Fri, 17 Apr 2020 15:31:13 -0500 Subject: [PATCH 048/108] Renamed Tweet to TwitterStatus since that matches the API better. --- .../Account/Account.xcodeproj/project.pbxproj | 8 +-- .../Account/FeedProvider/Twitter/Tweet.swift | 6 +- .../Twitter/TwitterFeedProvider.swift | 42 +++++--------- .../FeedProvider/Twitter/TwitterStatus.swift | 55 +++++++++++++++++++ .../FeedProvider/Twitter/TwitterUser.swift | 4 ++ 5 files changed, 80 insertions(+), 35 deletions(-) create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 450707e1c..e956f2bba 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -39,7 +39,7 @@ 5132AAC42448BAD90077840A /* FeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC12448BAD90077840A /* FeedProvider.swift */; }; 5132AAC52448BAD90077840A /* TwitterFeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */; }; 5132DE812449159100806ADE /* TwitterUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132DE802449159100806ADE /* TwitterUser.swift */; }; - 5132DE832449306F00806ADE /* Tweet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132DE822449306F00806ADE /* Tweet.swift */; }; + 5132DE832449306F00806ADE /* TwitterStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132DE822449306F00806ADE /* TwitterStatus.swift */; }; 513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; }; 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; }; 5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; }; @@ -279,7 +279,7 @@ 5132AAC12448BAD90077840A /* FeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedProvider.swift; sourceTree = ""; }; 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwitterFeedProvider.swift; sourceTree = ""; }; 5132DE802449159100806ADE /* TwitterUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterUser.swift; sourceTree = ""; }; - 5132DE822449306F00806ADE /* Tweet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tweet.swift; sourceTree = ""; }; + 5132DE822449306F00806ADE /* TwitterStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterStatus.swift; sourceTree = ""; }; 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = ""; }; 513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = ""; }; 5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = ""; }; @@ -568,7 +568,7 @@ children = ( 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */, 5132DE802449159100806ADE /* TwitterUser.swift */, - 5132DE822449306F00806ADE /* Tweet.swift */, + 5132DE822449306F00806ADE /* TwitterStatus.swift */, ); path = Twitter; sourceTree = ""; @@ -1130,7 +1130,7 @@ 552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */, 9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */, 9E5EC15B23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift in Sources */, - 5132DE832449306F00806ADE /* Tweet.swift in Sources */, + 5132DE832449306F00806ADE /* TwitterStatus.swift in Sources */, 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */, 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift index 3da8930c0..f71853a4c 100644 --- a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift +++ b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift @@ -8,7 +8,7 @@ import Foundation -final class Tweet: Codable { +final class TwitterStatus: Codable { let createdAt: Date? let idStr: String? @@ -17,7 +17,8 @@ final class Tweet: Codable { let user: TwitterUser let truncated: Bool let retweeted: Bool - let retweetedStatus: Tweet? + let retweetedStatus: TwitterStatus? + let quotedStatus: TwitterStatus? enum CodingKeys: String, CodingKey { case createdAt = "created_at" @@ -28,6 +29,7 @@ final class Tweet: Codable { case truncated = "truncated" case retweeted = "retweeted" case retweetedStatus = "retweeted_status" + case quotedStatus = "quoted_status" } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index d10852912..0d6c33bec 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -214,7 +214,7 @@ private extension TwitterFeedProvider { } } - func retrieveTweets(api: String, completion: @escaping (Result<[Tweet], Error>) -> Void) { + func retrieveTweets(api: String, completion: @escaping (Result<[TwitterStatus], Error>) -> Void) { let url = "\(Self.apiBase)\(api)" let parameters = ["tweet_mode": "extended"] @@ -232,7 +232,7 @@ private extension TwitterFeedProvider { try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) do { - let tweets = try decoder.decode([Tweet].self, from: response.data) + let tweets = try decoder.decode([TwitterStatus].self, from: response.data) completion(.success(tweets)) } catch { completion(.failure(error)) @@ -243,29 +243,27 @@ private extension TwitterFeedProvider { } } - func makeParsedItems(_ webFeedURL: String, _ tweets: [Tweet]) -> Set { + func makeParsedItems(_ webFeedURL: String, _ statuses: [TwitterStatus]) -> Set { var parsedItems = Set() - for tweet in tweets { - guard let idStr = tweet.idStr, let userScreenName = tweet.user.screenName else { continue } - - let userURL = makeUserURL(userScreenName) + for status in statuses { + guard let idStr = status.idStr, let statusURL = status.url else { continue } let parsedItem = ParsedItem(syncServiceID: idStr, uniqueID: idStr, feedURL: webFeedURL, - url: "\(userURL)/status/\(idStr)", + url: statusURL, externalURL: nil, title: nil, language: nil, - contentHTML: makeTweetHTML(tweet), - contentText: makeTweetText(tweet), + contentHTML: status.renderAsHTML(), + contentText: status.renderAsText(), summary: nil, imageURL: nil, bannerImageURL: nil, - datePublished: tweet.createdAt, + datePublished: status.createdAt, dateModified: nil, - authors: makeParsedAuthors(tweet.user), + authors: makeParsedAuthors(status.user), tags: nil, attachments: nil) parsedItems.insert(parsedItem) @@ -278,23 +276,9 @@ private extension TwitterFeedProvider { return "https://twitter.com/\(screenName)" } - func makeParsedAuthors(_ user: TwitterUser) -> Set { - return Set([ParsedAuthor(name: user.name, url: makeUserURL(user.screenName!), avatarURL: user.avatarURL, emailAddress: nil)]) - } - - func makeTweetText(_ tweet: Tweet) -> String? { - let tweetToUse = tweet.retweetedStatus != nil ? tweet.retweetedStatus! : tweet - if let text = tweetToUse.fullText, let displayRange = tweetToUse.displayTextRange, displayRange.count > 1, - let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), - let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) { - return String(text[startIndex.. String? { - return nil + func makeParsedAuthors(_ user: TwitterUser?) -> Set? { + guard let user = user else { return nil } + return Set([ParsedAuthor(name: user.name, url: user.url, avatarURL: user.avatarURL, emailAddress: nil)]) } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift new file mode 100644 index 000000000..31d22f06d --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -0,0 +1,55 @@ +// +// TwitterStatus.swift +// Account +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +final class TwitterStatus: Codable { + + let createdAt: Date? + let idStr: String? + let fullText: String? + let displayTextRange: [Int]? + let user: TwitterUser? + let truncated: Bool? + let retweeted: Bool? + let retweetedStatus: TwitterStatus? + let quotedStatus: TwitterStatus? + + enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case idStr = "id_str" + case fullText = "full_text" + case displayTextRange = "display_text_range" + case user = "user" + case truncated = "truncated" + case retweeted = "retweeted" + case retweetedStatus = "retweeted_status" + case quotedStatus = "quoted_status" + } + + var url: String? { + guard let userURL = user?.url, let idStr = idStr else { return nil } + return "\(userURL)/status/\(idStr)" + } + + func renderAsText() -> String? { + let statusToRender = retweetedStatus != nil ? retweetedStatus! : self + if let text = statusToRender.fullText, let displayRange = statusToRender.displayTextRange, displayRange.count > 1, + let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), + let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) { + return String(text[startIndex.. String? { + return nil + } + +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift index 5b2aa24c8..6d10fac7b 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift @@ -20,4 +20,8 @@ struct TwitterUser: Codable { case avatarURL = "profile_image_url_https" } + var url: String { + return "https://twitter.com/\(screenName ?? "")" + } + } From 67434c2d0b6d38fe2a3ce8141fad79b16236dd94 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 Apr 2020 15:34:03 -0500 Subject: [PATCH 049/108] Made the displayText a property of a Status. --- .../Account/FeedProvider/Twitter/TwitterStatus.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index 31d22f06d..b675d1be7 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -37,9 +37,8 @@ final class TwitterStatus: Codable { return "\(userURL)/status/\(idStr)" } - func renderAsText() -> String? { - let statusToRender = retweetedStatus != nil ? retweetedStatus! : self - if let text = statusToRender.fullText, let displayRange = statusToRender.displayTextRange, displayRange.count > 1, + var displayText: String? { + if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1, let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) { return String(text[startIndex.. String? { + let statusToRender = retweetedStatus != nil ? retweetedStatus! : self + return statusToRender.displayText + } + func renderAsHTML() -> String? { return nil } From 1778a270d64edf384c7b29585e13ed304c622886 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 07:53:56 -0500 Subject: [PATCH 050/108] Show byline in timeline for single feed timelines when available. --- .../Timeline/Cell/TimelineCellData.swift | 17 ++++-- .../Timeline/Cell/TimelineCellLayout.swift | 2 +- .../Timeline/Cell/TimelineTableCellView.swift | 9 ++- .../Timeline/TimelineViewController.swift | 55 +++++++++++-------- ...asterTimelineAccessibilityCellLayout.swift | 2 +- .../Cell/MasterTimelineCellData.swift | 14 ++++- .../Cell/MasterTimelineTableViewCell.swift | 10 +++- .../MasterTimelineViewController.swift | 4 +- iOS/SceneCoordinator.swift | 21 +++++-- .../TimelinePreviewTableViewController.swift | 2 +- 10 files changed, 91 insertions(+), 45 deletions(-) diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift index 814815eff..b6ca525bc 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift @@ -15,14 +15,15 @@ struct TimelineCellData { let text: String let dateString: String let feedName: String - let showFeedName: Bool + let byline: String + let showFeedName: TimelineShowFeedName let iconImage: IconImage? // feed icon, user avatar, or favicon let showIcon: Bool // Make space even when icon is nil let featuredImage: NSImage? // image from within the article let read: Bool let starred: Bool - init(article: Article, showFeedName: Bool, feedName: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: NSImage?) { + init(article: Article, showFeedName: TimelineShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: NSImage?) { self.title = ArticleStringFormatter.truncatedTitle(article) self.text = ArticleStringFormatter.truncatedSummary(article) @@ -31,10 +32,15 @@ struct TimelineCellData { if let feedName = feedName { self.feedName = ArticleStringFormatter.truncatedFeedName(feedName) - } - else { + } else { self.feedName = "" } + + if let byline = byline { + self.byline = byline + } else { + self.byline = "" + } self.showFeedName = showFeedName @@ -51,7 +57,8 @@ struct TimelineCellData { self.text = "" self.dateString = "" self.feedName = "" - self.showFeedName = false + self.byline = "" + self.showFeedName = .none self.showIcon = false self.iconImage = nil self.featuredImage = nil diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift index 8dddd1bef..07bb08c9b 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift @@ -171,7 +171,7 @@ private extension TimelineCellLayout { } static func rectForFeedName(_ textBoxRect: NSRect, _ dateRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect { - if !cellData.showFeedName { + if cellData.showFeedName == .none { return NSZeroRect } diff --git a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 60854027c..eb1e8a79e 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -248,11 +248,14 @@ private extension TimelineTableCellView { } func updateFeedNameView() { - if cellData.showFeedName { + switch cellData.showFeedName { + case .byline: + showView(feedNameView) + updateTextFieldText(feedNameView, cellData.byline) + case .feed: showView(feedNameView) updateTextFieldText(feedNameView, cellData.feedName) - } - else { + case .none: hideView(feedNameView) } } diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index aaecbe097..4a79c36b8 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -18,6 +18,12 @@ protocol TimelineDelegate: class { func timelineInvalidatedRestorationState(_: TimelineViewController) } +enum TimelineShowFeedName { + case none + case byline + case feed +} + final class TimelineViewController: NSViewController, UndoableCommandRunner, UnreadCountProvider { @IBOutlet var tableView: TimelineTableView! @@ -41,23 +47,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr didSet { if !representedObjectArraysAreEqual(oldValue, representedObjects) { unreadCount = 0 - if let representedObjects = representedObjects { - if representedObjects.count == 1 && representedObjects.first is WebFeed { - showFeedNames = false - } - else { - showFeedNames = true - } - } - else { - showFeedNames = false - } selectionDidChange(nil) if showsSearchResults { fetchAndReplaceArticlesAsync() - } - else { + } else { fetchAndReplaceArticlesSync() if articles.count > 0 { tableView.scrollRowToVisible(0) @@ -85,9 +79,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr defer { updateUnreadCount() } + if articles == oldValue { return } + if articles.representSameArticlesInSameOrder(as: oldValue) { // When the array is the same — same articles, same order — // but some data in some of the articles may have changed. @@ -96,7 +92,20 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr reloadVisibleCells() return } - updateShowIcons() + + if let representedObjects = representedObjects, representedObjects.count == 1 && representedObjects.first is WebFeed { + showFeedNames = { + for article in articles { + if article.authors?.contains(where: { $0.name != nil }) ?? false { + return .byline + } + } + return .none + }() + } else { + showFeedNames = .feed + } + articleRowMap = [String: Int]() tableView.reloadData() } @@ -117,7 +126,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr private var articleRowMap = [String: Int]() // articleID: rowIndex private var cellAppearance: TimelineCellAppearance! private var cellAppearanceWithIcon: TimelineCellAppearance! - private var showFeedNames = false { + private var showFeedNames: TimelineShowFeedName = .none { didSet { if showFeedNames != oldValue { updateShowIcons() @@ -663,7 +672,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, dateArrived: Date()) let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status) - let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil) + let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil) let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance) return height } @@ -810,7 +819,7 @@ extension TimelineViewController: NSTableViewDelegate { private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) { cell.objectValue = article let iconImage = article.iconImage() - cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil) + cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcons, featuredImage: nil) } private func iconFor(_ article: Article) -> IconImage? { @@ -946,20 +955,20 @@ private extension TimelineViewController { } func updateShowIcons() { - if showFeedNames { + if showFeedNames != .none { self.showIcons = true return } for article in articles { if let authors = article.authors { - for author in authors { - if author.avatarURL != nil { - self.showIcons = true - return + for author in authors { + if author.avatarURL != nil { + self.showIcons = true + return + } } } - } } self.showIcons = false diff --git a/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift b/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift index 60c8355f3..7441e5ae8 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift @@ -55,7 +55,7 @@ struct MasterTimelineAccessibilityCellLayout: MasterTimelineCellLayout { currentPoint.y = [self.titleRect, self.summaryRect].maxY() - if cellData.showFeedName { + if cellData.showFeedName != .none { self.feedNameRect = MasterTimelineAccessibilityCellLayout.rectForFeedName(cellData, currentPoint, textAreaWidth) currentPoint.y = self.feedNameRect.maxY } else { diff --git a/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift b/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift index 707cea1f4..8996654f8 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift @@ -15,7 +15,8 @@ struct MasterTimelineCellData { let summary: String let dateString: String let feedName: String - let showFeedName: Bool + let byline: String + let showFeedName: ShowFeedName let iconImage: IconImage? // feed icon, user avatar, or favicon let showIcon: Bool // Make space even when icon is nil let featuredImage: UIImage? // image from within the article @@ -24,7 +25,7 @@ struct MasterTimelineCellData { let numberOfLines: Int let iconSize: IconSize - init(article: Article, showFeedName: Bool, feedName: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int, iconSize: IconSize) { + init(article: Article, showFeedName: ShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int, iconSize: IconSize) { self.title = ArticleStringFormatter.truncatedTitle(article) self.summary = ArticleStringFormatter.truncatedSummary(article) @@ -37,6 +38,12 @@ struct MasterTimelineCellData { else { self.feedName = "" } + + if let byline = byline { + self.byline = byline + } else { + self.byline = "" + } self.showFeedName = showFeedName @@ -56,7 +63,8 @@ struct MasterTimelineCellData { self.summary = "" self.dateString = "" self.feedName = "" - self.showFeedName = false + self.byline = "" + self.showFeedName = .none self.showIcon = false self.iconImage = nil self.featuredImage = nil diff --git a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift index aa964b218..863ee7631 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift @@ -172,12 +172,18 @@ private extension MasterTimelineTableViewCell { } func updateFeedNameView() { - if cellData.showFeedName { + switch cellData.showFeedName { + case .feed: showView(feedNameView) feedNameView.font = MasterTimelineDefaultCellLayout.feedNameFont feedNameView.textColor = secondaryLabelColor updateTextFieldText(feedNameView, cellData.feedName) - } else { + case .byline: + showView(feedNameView) + feedNameView.font = MasterTimelineDefaultCellLayout.feedNameFont + feedNameView.textColor = secondaryLabelColor + updateTextFieldText(feedNameView, cellData.byline) + case .none: hideView(feedNameView) } } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index fdd148c81..11fd51a17 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -489,7 +489,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, dateArrived: Date()) let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status) - let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize) + let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize) if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory { let layout = MasterTimelineAccessibilityCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData) @@ -649,7 +649,7 @@ private extension MasterTimelineViewController { let showFeedNames = coordinator.showFeedNames let showIcon = coordinator.showIcons && iconImage != nil - cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize) + cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize) } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index fb2ad7e27..d384f53ee 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -23,6 +23,12 @@ enum SearchScope: Int { case global = 1 } +enum ShowFeedName { + case none + case byline + case feed +} + class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { var undoableCommands = [UndoableCommand]() @@ -159,7 +165,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { var timelineMiddleIndexPath: IndexPath? - private(set) var showFeedNames = false + private(set) var showFeedNames = ShowFeedName.none private(set) var showIcons = false var prevFeedIndexPath: IndexPath? { @@ -1454,12 +1460,19 @@ private extension SceneCoordinator { func updateShowNamesAndIcons() { if timelineFeed is WebFeed { - showFeedNames = false + showFeedNames = { + for article in articles { + if article.authors?.contains(where: { $0.name != nil }) ?? false { + return .byline + } + } + return .none + }() } else { - showFeedNames = true + showFeedNames = .feed } - if showFeedNames { + if showFeedNames == .feed { self.showIcons = true return } diff --git a/iOS/Settings/TimelinePreviewTableViewController.swift b/iOS/Settings/TimelinePreviewTableViewController.swift index 8d3fa87b9..525c560de 100644 --- a/iOS/Settings/TimelinePreviewTableViewController.swift +++ b/iOS/Settings/TimelinePreviewTableViewController.swift @@ -71,7 +71,7 @@ private extension TimelinePreviewTableViewController { let iconImage = IconImage(AppAssets.faviconTemplateImage.withTintColor(AppAssets.secondaryAccentColor)) - return MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Feed Name", iconImage: iconImage, showIcon: true, featuredImage: nil, numberOfLines: AppDefaults.timelineNumberOfLines, iconSize: AppDefaults.timelineIconSize) + return MasterTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Feed Name", byline: nil, iconImage: iconImage, showIcon: true, featuredImage: nil, numberOfLines: AppDefaults.timelineNumberOfLines, iconSize: AppDefaults.timelineIconSize) } } From 2a0d75cf2304b854ef980cb3d2302b6fdff50383 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 10:41:18 -0500 Subject: [PATCH 051/108] Add rendering as HTML --- .../FeedProvider/Twitter/TwitterStatus.swift | 35 ++++++++++++++++++- .../FeedProvider/Twitter/TwitterUser.swift | 17 +++++++++ Mac/MainWindow/Detail/styleSheet.css | 15 ++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index b675d1be7..d0037df5f 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -53,7 +53,40 @@ final class TwitterStatus: Codable { } func renderAsHTML() -> String? { - return nil + if let retweetedStatus = retweetedStatus { + return renderAsRetweetHTML(retweetedStatus) + } + if let quotedStatus = quotedStatus { + return renderAsQuoteHTML(quotedStatus) + } + return renderAsTweetHTML(self) + } + + func renderAsTweetHTML(_ status: TwitterStatus) -> String? { + return status.displayText + } + + func renderAsRetweetHTML(_ status: TwitterStatus) -> String { + var html = String() + html += "
" + if let userHTML = status.user?.renderHTML() { + html += userHTML + } + html += renderAsTweetHTML(status) ?? "" + html += "
" + return html + } + + func renderAsQuoteHTML(_ quotedStatus: TwitterStatus) -> String { + var html = String() + html += renderAsTweetHTML(self) ?? "" + html += "
" + if let userHTML = quotedStatus.user?.renderHTML() { + html += userHTML + } + html += renderAsTweetHTML(quotedStatus) ?? "" + html += "
" + return html } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift index 6d10fac7b..1d4942416 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift @@ -24,4 +24,21 @@ struct TwitterUser: Codable { return "https://twitter.com/\(screenName ?? "")" } + func renderHTML() -> String? { + var html = String() + html += "
" + return html + } + } diff --git a/Mac/MainWindow/Detail/styleSheet.css b/Mac/MainWindow/Detail/styleSheet.css index ea9723bb9..32e3e3926 100644 --- a/Mac/MainWindow/Detail/styleSheet.css +++ b/Mac/MainWindow/Detail/styleSheet.css @@ -243,6 +243,21 @@ blockquote { max-height: 1em; } +/* Twitter */ + +.twitterAvatar { + vertical-align: middle; + border-radius: 4px; + height: 24px; + width: 24px; +} + +.twitterUsername { + margin-left: 4px; + display: inline-block; + vertical-align: middle; +} + /*Block ads and junk*/ iframe[src*="feedads"], From 8d64a96489b98477c100a393a425053a91ffe325 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 12:03:16 -0500 Subject: [PATCH 052/108] Add Twitter entities --- .../Account/Account.xcodeproj/project.pbxproj | 20 ++++++++++++++ .../FeedProvider/Twitter/TwitterHashtag.swift | 21 +++++++++++++++ .../FeedProvider/Twitter/TwitterMention.swift | 27 +++++++++++++++++++ .../FeedProvider/Twitter/TwitterStatus.swift | 4 ++- .../FeedProvider/Twitter/TwitterSymbol.swift | 21 +++++++++++++++ 5 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index e956f2bba..6d2f140db 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -62,6 +62,11 @@ 519E84A62433D49000D238B0 /* OPMLNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A52433D49000D238B0 /* OPMLNormalizer.swift */; }; 519E84A82434C5EF00D238B0 /* CloudKitArticlesZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */; }; 519E84AC2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84AB2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift */; }; + 51B36305244B6135000DEF2A /* TwitterEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36304244B6135000DEF2A /* TwitterEntities.swift */; }; + 51B36307244B6234000DEF2A /* TwitterHashtag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36306244B6234000DEF2A /* TwitterHashtag.swift */; }; + 51B36309244B62A5000DEF2A /* TwitterURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36308244B62A5000DEF2A /* TwitterURL.swift */; }; + 51B3630B244B634A000DEF2A /* TwitterMention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630A244B634A000DEF2A /* TwitterMention.swift */; }; + 51B3630D244B6428000DEF2A /* TwitterSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */; }; 51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */; }; 51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC8FCB237EC055004F8B56 /* Feed.swift */; }; 51BFDECE238B508D00216323 /* ContainerIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */; }; @@ -303,6 +308,11 @@ 519E84A52433D49000D238B0 /* OPMLNormalizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLNormalizer.swift; sourceTree = ""; }; 519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitArticlesZone.swift; sourceTree = ""; }; 519E84AB2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudKitArticlesZoneDelegate.swift; sourceTree = ""; }; + 51B36304244B6135000DEF2A /* TwitterEntities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterEntities.swift; sourceTree = ""; }; + 51B36306244B6234000DEF2A /* TwitterHashtag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterHashtag.swift; sourceTree = ""; }; + 51B36308244B62A5000DEF2A /* TwitterURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterURL.swift; sourceTree = ""; }; + 51B3630A244B634A000DEF2A /* TwitterMention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterMention.swift; sourceTree = ""; }; + 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterSymbol.swift; sourceTree = ""; }; 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = ""; }; 51BC8FCB237EC055004F8B56 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = ""; }; 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerIdentifier.swift; sourceTree = ""; }; @@ -569,6 +579,11 @@ 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */, 5132DE802449159100806ADE /* TwitterUser.swift */, 5132DE822449306F00806ADE /* TwitterStatus.swift */, + 51B36304244B6135000DEF2A /* TwitterEntities.swift */, + 51B36306244B6234000DEF2A /* TwitterHashtag.swift */, + 51B36308244B62A5000DEF2A /* TwitterURL.swift */, + 51B3630A244B634A000DEF2A /* TwitterMention.swift */, + 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */, ); path = Twitter; sourceTree = ""; @@ -1115,6 +1130,7 @@ 5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */, 5132AAC52448BAD90077840A /* TwitterFeedProvider.swift in Sources */, 519E84AC2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift in Sources */, + 51B3630B244B634A000DEF2A /* TwitterMention.swift in Sources */, 512DD4CB2431000600C17B1F /* CKRecord+Extensions.swift in Sources */, 3B826DAF2385C81C00FC1ADB /* FeedWranglerGenericResult.swift in Sources */, 9ECC9A85234DC16E009B5144 /* FeedlyAccountDelegateError.swift in Sources */, @@ -1130,11 +1146,13 @@ 552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */, 9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */, 9E5EC15B23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift in Sources */, + 51B36305244B6135000DEF2A /* TwitterEntities.swift in Sources */, 5132DE832449306F00806ADE /* TwitterStatus.swift in Sources */, 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */, 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */, + 51B36309244B62A5000DEF2A /* TwitterURL.swift in Sources */, 5103A9D92422546800410853 /* CloudKitAccountDelegate.swift in Sources */, 5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */, 9E784EBE237E890600099B1B /* FeedlyLogoutOperation.swift in Sources */, @@ -1184,6 +1202,7 @@ 9E5DE60E23C3F4B70064DA30 /* FeedlyFetchIdsForMissingArticlesOperation.swift in Sources */, 3B826DA92385C81C00FC1ADB /* FeedWranglerAPICaller.swift in Sources */, 9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */, + 51B36307244B6234000DEF2A /* TwitterHashtag.swift in Sources */, 51E3EB41229AF61B00645299 /* AccountError.swift in Sources */, 9E1D155D233447F000F4944C /* FeedlyUpdateAccountFeedsWithItemsOperation.swift in Sources */, 51E59599228C77BC00FCC42B /* FeedbinUnreadEntry.swift in Sources */, @@ -1227,6 +1246,7 @@ 5132DE812449159100806ADE /* TwitterUser.swift in Sources */, 3B826DAC2385C81C00FC1ADB /* FeedWranglerAccountDelegate.swift in Sources */, 769F295938E5A30D03DFF88F /* NewsBlurAccountDelegate.swift in Sources */, + 51B3630D244B6428000DEF2A /* TwitterSymbol.swift in Sources */, 769F2BA02EF5F329CDE45F5A /* NewsBlurAPICaller.swift in Sources */, 51C034DF242D65D20014DC71 /* CloudKitZoneResult.swift in Sources */, 179DB28CF49F73A945EBF5DB /* NewsBlurLoginResponse.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift new file mode 100644 index 000000000..92f6c4237 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift @@ -0,0 +1,21 @@ +// +// TwitterHashtag.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterHashtag: Codable { + + let text: String? + let indices: [Int]? + + enum CodingKeys: String, CodingKey { + case text = "text" + case indices = "indices" + } + +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift new file mode 100644 index 000000000..a923b4bdb --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift @@ -0,0 +1,27 @@ +// +// TwitterMention.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterMention: Codable { + + let name: String? + let indices: [Int]? + let screenName: String? + let expandedURL: String? + let idStr: String? + + enum CodingKeys: String, CodingKey { + case name = "url" + case indices = "indices" + case screenName = "screen_name" + case expandedURL = "expandedURL" + case idStr = "idStr" + } + +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index d0037df5f..8ee5d275a 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -19,7 +19,8 @@ final class TwitterStatus: Codable { let retweeted: Bool? let retweetedStatus: TwitterStatus? let quotedStatus: TwitterStatus? - + let entities: TwitterEntities? + enum CodingKeys: String, CodingKey { case createdAt = "created_at" case idStr = "id_str" @@ -30,6 +31,7 @@ final class TwitterStatus: Codable { case retweeted = "retweeted" case retweetedStatus = "retweeted_status" case quotedStatus = "quoted_status" + case entities = "entities" } var url: String? { diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift new file mode 100644 index 000000000..881204cec --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift @@ -0,0 +1,21 @@ +// +// TwitterSymbol.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterSymbol: Codable { + + let name: String? + let indices: [Int]? + + enum CodingKeys: String, CodingKey { + case name = "name" + case indices = "indices" + } + +} From d9f6125561f65992202a1b9d09f3138dd8518093 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 12:03:37 -0500 Subject: [PATCH 053/108] Add twitter entities --- .../Twitter/TwitterEntities.swift | 25 +++++++++++++++++ .../Twitter/TwitterMentions.swift | 27 +++++++++++++++++++ .../FeedProvider/Twitter/TwitterURL.swift | 25 +++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterMentions.swift create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift new file mode 100644 index 000000000..79d7703d7 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift @@ -0,0 +1,25 @@ +// +// TwitterEntities.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterEntities: Codable { + + let hashtags: [TwitterHashtag]? + let urls: [TwitterURL]? + let userMentions: [TwitterMention]? + let symbols: [TwitterSymbol]? + + enum CodingKeys: String, CodingKey { + case hashtags = "hashtags" + case urls = "urls" + case userMentions = "user_mentions" + case symbols = "symbols" + } + +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMentions.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMentions.swift new file mode 100644 index 000000000..05f6b339f --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterMentions.swift @@ -0,0 +1,27 @@ +// +// TwitterMentions.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterMention { + + let name: String? + let indices: [Int]? + let screenName: String? + let expandedURL: String? + let idStr: String? + + enum CodingKeys: String, CodingKey { + case name = "url" + case indices = "indices" + case screenName = "screen_name" + case expandedURL = "expandedURL" + case idStr = "idStr" + } + +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift new file mode 100644 index 000000000..f0be16646 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift @@ -0,0 +1,25 @@ +// +// TwitterURL.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterURL: Codable { + + let url: String? + let indices: [Int]? + let displayURL: String? + let expandedURL: String? + + enum CodingKeys: String, CodingKey { + case url = "url" + case indices = "indices" + case displayURL = "displayURL" + case expandedURL = "expandedURL" + } + +} From 8f71b09440a8e3a513df7942b2e50e06b7a7f609 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 13:46:28 -0500 Subject: [PATCH 054/108] Add photo media showing for twitter. --- .../Account/Account.xcodeproj/project.pbxproj | 16 +++-- .../Twitter/TwitterExtendedEntities.swift | 28 +++++++++ .../FeedProvider/Twitter/TwitterMedia.swift | 62 +++++++++++++++++++ .../FeedProvider/Twitter/TwitterStatus.swift | 35 +++++++---- .../FeedProvider/Twitter/TwitterUser.swift | 2 +- 5 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 6d2f140db..a4347fbb9 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -67,6 +67,8 @@ 51B36309244B62A5000DEF2A /* TwitterURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36308244B62A5000DEF2A /* TwitterURL.swift */; }; 51B3630B244B634A000DEF2A /* TwitterMention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630A244B634A000DEF2A /* TwitterMention.swift */; }; 51B3630D244B6428000DEF2A /* TwitterSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */; }; + 51B3630F244B6CB9000DEF2A /* TwitterExtendedEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */; }; + 51B36311244B6CFB000DEF2A /* TwitterMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */; }; 51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */; }; 51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC8FCB237EC055004F8B56 /* Feed.swift */; }; 51BFDECE238B508D00216323 /* ContainerIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */; }; @@ -313,6 +315,8 @@ 51B36308244B62A5000DEF2A /* TwitterURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterURL.swift; sourceTree = ""; }; 51B3630A244B634A000DEF2A /* TwitterMention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterMention.swift; sourceTree = ""; }; 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterSymbol.swift; sourceTree = ""; }; + 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterExtendedEntities.swift; sourceTree = ""; }; + 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterMedia.swift; sourceTree = ""; }; 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = ""; }; 51BC8FCB237EC055004F8B56 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = ""; }; 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerIdentifier.swift; sourceTree = ""; }; @@ -576,14 +580,16 @@ 5132AAC22448BAD90077840A /* Twitter */ = { isa = PBXGroup; children = ( - 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */, - 5132DE802449159100806ADE /* TwitterUser.swift */, - 5132DE822449306F00806ADE /* TwitterStatus.swift */, 51B36304244B6135000DEF2A /* TwitterEntities.swift */, + 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */, + 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */, 51B36306244B6234000DEF2A /* TwitterHashtag.swift */, - 51B36308244B62A5000DEF2A /* TwitterURL.swift */, + 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */, 51B3630A244B634A000DEF2A /* TwitterMention.swift */, + 5132DE822449306F00806ADE /* TwitterStatus.swift */, 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */, + 51B36308244B62A5000DEF2A /* TwitterURL.swift */, + 5132DE802449159100806ADE /* TwitterUser.swift */, ); path = Twitter; sourceTree = ""; @@ -1191,6 +1197,7 @@ 9EEEF7212355277F009E9D80 /* FeedlyIngestStarredArticleIdsOperation.swift in Sources */, 3BC23AB92385ECB100371CBA /* FeedWranglerSubscriptionResult.swift in Sources */, 5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */, + 51B3630F244B6CB9000DEF2A /* TwitterExtendedEntities.swift in Sources */, 84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */, 51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */, 846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */, @@ -1249,6 +1256,7 @@ 51B3630D244B6428000DEF2A /* TwitterSymbol.swift in Sources */, 769F2BA02EF5F329CDE45F5A /* NewsBlurAPICaller.swift in Sources */, 51C034DF242D65D20014DC71 /* CloudKitZoneResult.swift in Sources */, + 51B36311244B6CFB000DEF2A /* TwitterMedia.swift in Sources */, 179DB28CF49F73A945EBF5DB /* NewsBlurLoginResponse.swift in Sources */, 179DBF4DE2562D4C532F6008 /* NewsBlurFeed.swift in Sources */, 179DB02FFBC17AC9798F0EBC /* NewsBlurStory.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift new file mode 100644 index 000000000..1dedd3052 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift @@ -0,0 +1,28 @@ +// +// TwitterExtendedEntities.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterExtendedEntities: Codable { + + let medias: [TwitterMedia]? + + enum CodingKeys: String, CodingKey { + case medias = "media" + } + + func renderAsHTML() -> String { + var html = String() + if let medias = medias { + for media in medias { + html += media.renderAsHTML() + } + } + return html + } +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift new file mode 100644 index 000000000..1f83311a7 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift @@ -0,0 +1,62 @@ +// +// TwitterMedia.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterMedia: Codable { + + let idStr: String? + let indices: [Int]? + let mediaURL: String? + let httpsMediaURL: String? + let url: String? + let displayURL: String? + let type: String? + + enum CodingKeys: String, CodingKey { + case idStr = "idStr" + case indices = "indices" + case mediaURL = "media_url" + case httpsMediaURL = "media_url_https" + case url = "url" + case displayURL = "display_url" + case type = "type" + } + + func renderAsHTML() -> String { + var html = String() + + switch type { + case "photo": + if let url = url { + html += "" + html += renderPhotoAsHTML() + html += "" + } + default: + return "" + } + + return html + } + +} + +private extension TwitterMedia { + + func renderPhotoAsHTML() -> String { + if let httpsMediaURL = httpsMediaURL { + return "" + } + if let mediaURL = mediaURL { + return "" + } + return "" + } + +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index 8ee5d275a..e5d0bb185 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -20,6 +20,7 @@ final class TwitterStatus: Codable { let retweetedStatus: TwitterStatus? let quotedStatus: TwitterStatus? let entities: TwitterEntities? + let extendedEntities: TwitterExtendedEntities? enum CodingKeys: String, CodingKey { case createdAt = "created_at" @@ -32,6 +33,7 @@ final class TwitterStatus: Codable { case retweetedStatus = "retweeted_status" case quotedStatus = "quoted_status" case entities = "entities" + case extendedEntities = "extended_entities" } var url: String? { @@ -54,40 +56,51 @@ final class TwitterStatus: Codable { return statusToRender.displayText } - func renderAsHTML() -> String? { + func renderAsHTML() -> String { if let retweetedStatus = retweetedStatus { return renderAsRetweetHTML(retweetedStatus) } if let quotedStatus = quotedStatus { return renderAsQuoteHTML(quotedStatus) } - return renderAsTweetHTML(self) + return renderAsOriginalHTML() } - func renderAsTweetHTML(_ status: TwitterStatus) -> String? { - return status.displayText + func renderAsTweetHTML(_ status: TwitterStatus) -> String { + var html = "

" + html += status.displayText ?? "" + html += "

" + return html + } + + func renderAsOriginalHTML() -> String { + var html = renderAsTweetHTML(self) + html += extendedEntities?.renderAsHTML() ?? "" + return html } func renderAsRetweetHTML(_ status: TwitterStatus) -> String { - var html = String() - html += "
" - if let userHTML = status.user?.renderHTML() { + var html = "
" + if let userHTML = status.user?.renderAsHTML() { html += userHTML } - html += renderAsTweetHTML(status) ?? "" + html += renderAsTweetHTML(status) html += "
" + html += status.extendedEntities?.renderAsHTML() ?? "" return html } func renderAsQuoteHTML(_ quotedStatus: TwitterStatus) -> String { var html = String() - html += renderAsTweetHTML(self) ?? "" + html += renderAsTweetHTML(self) html += "
" - if let userHTML = quotedStatus.user?.renderHTML() { + if let userHTML = quotedStatus.user?.renderAsHTML() { html += userHTML } - html += renderAsTweetHTML(quotedStatus) ?? "" + html += renderAsTweetHTML(quotedStatus) html += "
" + html += self.extendedEntities?.renderAsHTML() ?? "" + html += quotedStatus.extendedEntities?.renderAsHTML() ?? "" return html } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift index 1d4942416..58806431c 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift @@ -24,7 +24,7 @@ struct TwitterUser: Codable { return "https://twitter.com/\(screenName ?? "")" } - func renderHTML() -> String? { + func renderAsHTML() -> String? { var html = String() html += "
" if let avatarURL = avatarURL { From dd94212c9f615db25d7bb9ca2dfd7c2d27e2a0c5 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 14:48:12 -0500 Subject: [PATCH 055/108] Add inline video support to Tweets --- .../Account/Account.xcodeproj/project.pbxproj | 4 ++ .../FeedProvider/Twitter/TwitterMedia.swift | 40 ++++++++++++++++++- .../FeedProvider/Twitter/TwitterVideo.swift | 34 ++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterVideo.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index a4347fbb9..70f38d9b3 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ 51B3630D244B6428000DEF2A /* TwitterSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */; }; 51B3630F244B6CB9000DEF2A /* TwitterExtendedEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */; }; 51B36311244B6CFB000DEF2A /* TwitterMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */; }; + 51B36313244B8B5E000DEF2A /* TwitterVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36312244B8B5E000DEF2A /* TwitterVideo.swift */; }; 51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */; }; 51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC8FCB237EC055004F8B56 /* Feed.swift */; }; 51BFDECE238B508D00216323 /* ContainerIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */; }; @@ -317,6 +318,7 @@ 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterSymbol.swift; sourceTree = ""; }; 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterExtendedEntities.swift; sourceTree = ""; }; 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterMedia.swift; sourceTree = ""; }; + 51B36312244B8B5E000DEF2A /* TwitterVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterVideo.swift; sourceTree = ""; }; 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = ""; }; 51BC8FCB237EC055004F8B56 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = ""; }; 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerIdentifier.swift; sourceTree = ""; }; @@ -590,6 +592,7 @@ 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */, 51B36308244B62A5000DEF2A /* TwitterURL.swift */, 5132DE802449159100806ADE /* TwitterUser.swift */, + 51B36312244B8B5E000DEF2A /* TwitterVideo.swift */, ); path = Twitter; sourceTree = ""; @@ -1215,6 +1218,7 @@ 51E59599228C77BC00FCC42B /* FeedbinUnreadEntry.swift in Sources */, 552032F8229D5D5A009559E0 /* ReaderAPIEntry.swift in Sources */, 552032FB229D5D5A009559E0 /* ReaderAPITag.swift in Sources */, + 51B36313244B8B5E000DEF2A /* TwitterVideo.swift in Sources */, 5165D72822835F7800D9D53D /* FeedFinder.swift in Sources */, 9EBD49C023C67602005AD5CD /* FeedlyDownloadArticlesOperation.swift in Sources */, 51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift index 1f83311a7..ff10b5034 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift @@ -17,6 +17,7 @@ struct TwitterMedia: Codable { let url: String? let displayURL: String? let type: String? + let video: TwitterVideo? enum CodingKeys: String, CodingKey { case idStr = "idStr" @@ -26,6 +27,7 @@ struct TwitterMedia: Codable { case url = "url" case displayURL = "display_url" case type = "type" + case video = "video_info" } func renderAsHTML() -> String { @@ -37,9 +39,13 @@ struct TwitterMedia: Codable { html += "" html += renderPhotoAsHTML() html += "" + } else { + html += renderPhotoAsHTML() } + case "video": + html += renderVideoAsHTML() default: - return "" + break } return html @@ -58,5 +64,37 @@ private extension TwitterMedia { } return "" } + + func renderVideoAsHTML() -> String { + guard let bestVariantURL = findBestVariant()?.url else { return "" } + + var html = "" + return html + } + func findBestVariant() -> TwitterVideo.Variant? { + var best: TwitterVideo.Variant? = nil + if let variants = video?.variants { + for variant in variants { + if let currentBest = best { + if variant.bitrate ?? 0 > currentBest.bitrate ?? 0 { + best = variant + } + } else { + best = variant + } + } + } + return best + } + +// } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterVideo.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterVideo.swift new file mode 100644 index 000000000..a6df96292 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterVideo.swift @@ -0,0 +1,34 @@ +// +// TwitterVideoInfo.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + + +struct TwitterVideo: Codable { + + let variants: [Variant]? + + enum CodingKeys: String, CodingKey { + case variants = "variants" + } + + struct Variant: Codable { + + let bitrate: Int? + let contentType: String? + let url: String? + + enum CodingKeys: String, CodingKey { + case bitrate = "bitrate" + case contentType = "content_type" + case url = "url" + } + + } + +} From 41d14b4b46f1c9d789627d6b867954c02eb0edaf Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 15:08:57 -0500 Subject: [PATCH 056/108] removed some trash that Xcode's refactoring seems to leave behind now --- .../Account/FeedProvider/Twitter/Tweet.swift | 35 ------------------- .../FeedProvider/Twitter/TwitterMedia.swift | 4 +-- .../Twitter/TwitterMentions.swift | 27 -------------- 3 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 Frameworks/Account/FeedProvider/Twitter/Tweet.swift delete mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterMentions.swift diff --git a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift deleted file mode 100644 index f71853a4c..000000000 --- a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Tweet.swift -// Account -// -// Created by Maurice Parker on 4/16/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -final class TwitterStatus: Codable { - - let createdAt: Date? - let idStr: String? - let fullText: String? - let displayTextRange: [Int]? - let user: TwitterUser - let truncated: Bool - let retweeted: Bool - let retweetedStatus: TwitterStatus? - let quotedStatus: TwitterStatus? - - enum CodingKeys: String, CodingKey { - case createdAt = "created_at" - case idStr = "id_str" - case fullText = "full_text" - case displayTextRange = "display_text_range" - case user = "user" - case truncated = "truncated" - case retweeted = "retweeted" - case retweetedStatus = "retweeted_status" - case quotedStatus = "quoted_status" - } - -} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift index ff10b5034..619ab6209 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift @@ -57,10 +57,10 @@ private extension TwitterMedia { func renderPhotoAsHTML() -> String { if let httpsMediaURL = httpsMediaURL { - return "" + return "
" } if let mediaURL = mediaURL { - return "" + return "
" } return "" } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMentions.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMentions.swift deleted file mode 100644 index 05f6b339f..000000000 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterMentions.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// TwitterMentions.swift -// Account -// -// Created by Maurice Parker on 4/18/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct TwitterMention { - - let name: String? - let indices: [Int]? - let screenName: String? - let expandedURL: String? - let idStr: String? - - enum CodingKeys: String, CodingKey { - case name = "url" - case indices = "indices" - case screenName = "screen_name" - case expandedURL = "expandedURL" - case idStr = "idStr" - } - -} From ad95cef6bbcffe1fd1463d098ff160932de5e609 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 15:17:56 -0500 Subject: [PATCH 057/108] Add iOS twitter stylings --- iOS/Resources/styleSheet.css | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/iOS/Resources/styleSheet.css b/iOS/Resources/styleSheet.css index 5f403c4de..3138bf79a 100644 --- a/iOS/Resources/styleSheet.css +++ b/iOS/Resources/styleSheet.css @@ -272,6 +272,21 @@ blockquote { max-height: 1em; } +/* Twitter */ + +.twitterAvatar { + vertical-align: middle; + border-radius: 4px; + height: 24px; + width: 24px; +} + +.twitterUsername { + margin-left: 4px; + display: inline-block; + vertical-align: middle; +} + /*Block ads and junk*/ iframe[src*="feedads"], From b31e43419cc62da2f974265e18bf12f91ccce07f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 16:01:18 -0500 Subject: [PATCH 058/108] Add support for retweeted retweets --- .../FeedProvider/Twitter/TwitterStatus.swift | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index e5d0bb185..c661217fa 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -56,51 +56,54 @@ final class TwitterStatus: Codable { return statusToRender.displayText } - func renderAsHTML() -> String { + func renderAsHTML(topLevel: Bool = true) -> String { if let retweetedStatus = retweetedStatus { - return renderAsRetweetHTML(retweetedStatus) + return renderAsRetweetHTML(retweetedStatus, topLevel: topLevel) } if let quotedStatus = quotedStatus { - return renderAsQuoteHTML(quotedStatus) + return renderAsQuoteHTML(quotedStatus, topLevel: topLevel) } - return renderAsOriginalHTML() + return renderAsOriginalHTML(topLevel: topLevel) } func renderAsTweetHTML(_ status: TwitterStatus) -> String { - var html = "

" - html += status.displayText ?? "" - html += "

" - return html + return status.displayText ?? "" } - func renderAsOriginalHTML() -> String { + func renderAsOriginalHTML(topLevel: Bool) -> String { var html = renderAsTweetHTML(self) - html += extendedEntities?.renderAsHTML() ?? "" + if topLevel { + html += extendedEntities?.renderAsHTML() ?? "" + } return html } - func renderAsRetweetHTML(_ status: TwitterStatus) -> String { + func renderAsRetweetHTML(_ status: TwitterStatus, topLevel: Bool) -> String { var html = "
" if let userHTML = status.user?.renderAsHTML() { html += userHTML } - html += renderAsTweetHTML(status) + html += status.renderAsHTML(topLevel: false) html += "
" - html += status.extendedEntities?.renderAsHTML() ?? "" + if topLevel { + html += status.extendedEntities?.renderAsHTML() ?? "" + } return html } - func renderAsQuoteHTML(_ quotedStatus: TwitterStatus) -> String { + func renderAsQuoteHTML(_ quotedStatus: TwitterStatus, topLevel: Bool) -> String { var html = String() html += renderAsTweetHTML(self) html += "
" if let userHTML = quotedStatus.user?.renderAsHTML() { html += userHTML } - html += renderAsTweetHTML(quotedStatus) + html += quotedStatus.renderAsHTML(topLevel: false) html += "
" html += self.extendedEntities?.renderAsHTML() ?? "" - html += quotedStatus.extendedEntities?.renderAsHTML() ?? "" + if topLevel { + html += quotedStatus.extendedEntities?.renderAsHTML() ?? "" + } return html } From ac41c7af69f849056691354d2ded3f21c76b7b97 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 16:27:19 -0500 Subject: [PATCH 059/108] Added timestamp to retweets --- .../FeedProvider/Twitter/TwitterStatus.swift | 17 +++++++++++++---- Mac/MainWindow/Detail/styleSheet.css | 4 ++++ iOS/Resources/styleSheet.css | 4 ++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index c661217fa..62f429371 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -66,12 +66,21 @@ final class TwitterStatus: Codable { return renderAsOriginalHTML(topLevel: topLevel) } - func renderAsTweetHTML(_ status: TwitterStatus) -> String { - return status.displayText ?? "" + func renderAsTweetHTML(_ status: TwitterStatus, topLevel: Bool) -> String { + var html = "
\(status.displayText ?? "")
" + + if !topLevel, let createdAt = status.createdAt { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .medium + dateFormatter.timeStyle = .short + html += "
\(dateFormatter.string(from: createdAt))
" + } + + return html } func renderAsOriginalHTML(topLevel: Bool) -> String { - var html = renderAsTweetHTML(self) + var html = renderAsTweetHTML(self, topLevel: topLevel) if topLevel { html += extendedEntities?.renderAsHTML() ?? "" } @@ -93,7 +102,7 @@ final class TwitterStatus: Codable { func renderAsQuoteHTML(_ quotedStatus: TwitterStatus, topLevel: Bool) -> String { var html = String() - html += renderAsTweetHTML(self) + html += renderAsTweetHTML(self, topLevel: topLevel) html += "
" if let userHTML = quotedStatus.user?.renderAsHTML() { html += userHTML diff --git a/Mac/MainWindow/Detail/styleSheet.css b/Mac/MainWindow/Detail/styleSheet.css index 32e3e3926..c5cc8557b 100644 --- a/Mac/MainWindow/Detail/styleSheet.css +++ b/Mac/MainWindow/Detail/styleSheet.css @@ -258,6 +258,10 @@ blockquote { vertical-align: middle; } +.twitterTimestamp { + font-size: 66%; +} + /*Block ads and junk*/ iframe[src*="feedads"], diff --git a/iOS/Resources/styleSheet.css b/iOS/Resources/styleSheet.css index 3138bf79a..4efbc456d 100644 --- a/iOS/Resources/styleSheet.css +++ b/iOS/Resources/styleSheet.css @@ -287,6 +287,10 @@ blockquote { vertical-align: middle; } +.twitterTimestamp { + font-size: 66%; +} + /*Block ads and junk*/ iframe[src*="feedads"], From 3e7a3b001b94e7d35bc53222c31e2ad4f0b4ec51 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 16:48:37 -0500 Subject: [PATCH 060/108] Add user timeline refresh support. --- .../Twitter/TwitterFeedProvider.swift | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 0d6c33bec..32df07b16 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -148,9 +148,28 @@ public struct TwitterFeedProvider: FeedProvider { } public func refresh(_ webFeed: WebFeed, completion: @escaping (Result, Error>) -> Void) { - let api = "statuses/home_timeline.json" + guard let urlComponents = URLComponents(string: webFeed.url) else { + completion(.failure(TwitterFeedProviderError.unknown)) + return + } + + let api: String + var parameters = [String: Any]() + + switch urlComponents.path { + case "/", "/home": + api = "statuses/home_timeline.json" + default: + api = "statuses/user_timeline.json" + if let screenName = deriveScreenName(urlComponents) { + parameters["screen_name"] = screenName + } else { + completion(.failure(TwitterFeedProviderError.unknown)) + return + } + } - retrieveTweets(api: api) { result in + retrieveTweets(api: api, parameters: parameters) { result in switch result { case .success(let tweets): let parsedItems = self.makeParsedItems(webFeed.url, tweets) @@ -214,11 +233,12 @@ private extension TwitterFeedProvider { } } - func retrieveTweets(api: String, completion: @escaping (Result<[TwitterStatus], Error>) -> Void) { + func retrieveTweets(api: String, parameters: [String: Any], completion: @escaping (Result<[TwitterStatus], Error>) -> Void) { let url = "\(Self.apiBase)\(api)" - let parameters = ["tweet_mode": "extended"] + var expandedParameters = parameters + expandedParameters["tweet_mode"] = "extended" - client.get(url, parameters: parameters) { result in + client.get(url, parameters: expandedParameters) { result in switch result { case .success(let response): let decoder = JSONDecoder() From ba73881f21f6a7b148e4aa38b619a71007ddd794 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 17:30:58 -0500 Subject: [PATCH 061/108] Add Feed Provider support to the CloudKit account type. --- .../CloudKit/CloudKitAccountDelegate.swift | 222 +++++++++++++----- .../CloudKitAccountZoneDelegate.swift | 55 ++++- .../LocalAccount/LocalAccountDelegate.swift | 14 +- 3 files changed, 212 insertions(+), 79 deletions(-) diff --git a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift index 54da8c74e..88c788388 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift @@ -212,72 +212,20 @@ final class CloudKitAccountDelegate: AccountDelegate { } func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { - let editedName = name == nil || name!.isEmpty ? nil : name - - guard let url = URL(string: urlString) else { + guard let url = URL(string: urlString), let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) return } - BatchUpdate.shared.start() - refreshProgress.addToNumberOfTasksAndRemaining(3) - FeedFinder.find(url: url) { result in - - self.refreshProgress.completeTask() - switch result { - case .success(let feedSpecifiers): - guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), let url = URL(string: bestFeedSpecifier.urlString) else { - BatchUpdate.shared.end() - self.refreshProgress.clear() - completion(.failure(AccountError.createErrorNotFound)) - return - } - - if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { - BatchUpdate.shared.end() - self.refreshProgress.clear() - completion(.failure(AccountError.createErrorAlreadySubscribed)) - return - } - - self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: editedName, container: container) { result in + let editedName = name == nil || name!.isEmpty ? nil : name - self.refreshProgress.completeTask() - switch result { - case .success(let externalID): - - let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) - feed.editedName = editedName - feed.externalID = externalID - container.addWebFeed(feed) - - InitialFeedDownloader.download(url) { parsedFeed in - self.refreshProgress.completeTask() - - if let parsedFeed = parsedFeed { - account.update(feed, with: parsedFeed, {_ in - BatchUpdate.shared.end() - completion(.success(feed)) - }) - } - - } - - case .failure(let error): - BatchUpdate.shared.end() - self.refreshProgress.clear() - completion(.failure(error)) - } - } - - case .failure: - BatchUpdate.shared.end() - self.refreshProgress.clear() - completion(.failure(AccountError.createErrorNotFound)) - } - + // Username should be part of the URL on new feed adds + if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { + createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: editedName, container: container, feedProvider: feedProvider, completion: completion) + } else { + createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, completion: completion) } - + } func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { @@ -594,7 +542,7 @@ private extension CloudKitAccountDelegate { self.refreshProgress.completeTask() - self.refresher.refreshFeeds(webFeeds) { + self.refreshWebFeeds(account, webFeeds) { account.metadata.lastArticleFetchEndTime = Date() } @@ -615,6 +563,158 @@ private extension CloudKitAccountDelegate { } } + func refreshWebFeeds(_ account: Account, _ webFeeds: Set, completion: @escaping () -> Void) { + var refresherWebFeeds = Set() + let group = DispatchGroup() + + for webFeed in webFeeds { + if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components, with: webFeed.username) { + refreshProgress.addToNumberOfTasksAndRemaining(1) + group.enter() + feedProvider.refresh(webFeed) { result in + switch result { + case .success(let parsedItems): + account.update(webFeed.webFeedID, with: parsedItems) { _ in + self.refreshProgress.completeTask() + group.leave() + } + case .failure(let error): + os_log(.error, log: self.log, "Feed Provider refresh error: %@.", error.localizedDescription) + self.refreshProgress.completeTask() + group.leave() + } + } + } else { + refresherWebFeeds.insert(webFeed) + } + } + + refreshProgress.addToNumberOfTasksAndRemaining(refresherWebFeeds.count) + group.enter() + refresher.refreshFeeds(refresherWebFeeds) { + group.leave() + } + + group.notify(queue: DispatchQueue.main) { + completion() + } + } + + func createProviderWebFeed(for account: Account, urlComponents: URLComponents, editedName: String?, container: Container, feedProvider: FeedProvider, completion: @escaping (Result) -> Void) { + refreshProgress.addToNumberOfTasksAndRemaining(3) + + feedProvider.assignName(urlComponents) { result in + self.refreshProgress.completeTask() + switch result { + + case .success(let name): + + // Move the user to the WebFeed and out of the URL + var newURLComponents = urlComponents + newURLComponents.user = nil + guard let newURLString = newURLComponents.url?.absoluteString else { + completion(.failure(AccountError.createErrorNotFound)) + return + } + + self.accountZone.createWebFeed(url: newURLString, editedName: editedName, container: container) { result in + + self.refreshProgress.completeTask() + switch result { + case .success(let externalID): + + let feed = account.createWebFeed(with: name, url: newURLString, webFeedID: newURLString, homePageURL: nil) + feed.editedName = editedName + feed.username = urlComponents.user + feed.externalID = externalID + container.addWebFeed(feed) + + feedProvider.refresh(feed) { result in + self.refreshProgress.completeTask() + switch result { + case .success(let parsedItems): + account.update(newURLString, with: parsedItems) { _ in + completion(.success(feed)) + } + case .failure: + completion(.failure(AccountError.createErrorNotFound)) + } + } + + case .failure(let error): + self.refreshProgress.clear() + completion(.failure(error)) + } + } + + case .failure(let error): + self.refreshProgress.clear() + completion(.failure(error)) + } + } + } + + func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result) -> Void) { + BatchUpdate.shared.start() + refreshProgress.addToNumberOfTasksAndRemaining(3) + FeedFinder.find(url: url) { result in + + self.refreshProgress.completeTask() + switch result { + case .success(let feedSpecifiers): + guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), let url = URL(string: bestFeedSpecifier.urlString) else { + BatchUpdate.shared.end() + self.refreshProgress.clear() + completion(.failure(AccountError.createErrorNotFound)) + return + } + + if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { + BatchUpdate.shared.end() + self.refreshProgress.clear() + completion(.failure(AccountError.createErrorAlreadySubscribed)) + return + } + + self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: editedName, container: container) { result in + + self.refreshProgress.completeTask() + switch result { + case .success(let externalID): + + let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) + feed.editedName = editedName + feed.externalID = externalID + container.addWebFeed(feed) + + InitialFeedDownloader.download(url) { parsedFeed in + self.refreshProgress.completeTask() + + if let parsedFeed = parsedFeed { + account.update(feed, with: parsedFeed, {_ in + BatchUpdate.shared.end() + completion(.success(feed)) + }) + } + + } + + case .failure(let error): + BatchUpdate.shared.end() + self.refreshProgress.clear() + completion(.failure(error)) + } + } + + case .failure: + BatchUpdate.shared.end() + self.refreshProgress.clear() + completion(.failure(AccountError.createErrorNotFound)) + } + + } + } + func processAccountError(_ account: Account, _ error: Error) { if case CloudKitZoneError.userDeletedZone = error { account.removeFeeds(account.topLevelWebFeeds) diff --git a/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift index 8415bd5d0..a4361e44c 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift @@ -180,28 +180,61 @@ private extension CloudKitAcountZoneDelegate { } func createWebFeedIfNecessary(url: URL, editedName: String?, webFeedExternalID: String, container: Container, completion: @escaping (WebFeed) -> Void) { - guard let account = account else { return } + guard let account = account, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return } if let webFeed = account.existingWebFeed(withExternalID: webFeedExternalID) { completion(webFeed) return } - let webFeed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) + let webFeed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) webFeed.editedName = editedName webFeed.externalID = webFeedExternalID - container.addWebFeed(webFeed) - refreshProgress?.addToNumberOfTasksAndRemaining(1) - InitialFeedDownloader.download(url) { parsedFeed in - self.refreshProgress?.completeTask() - if let parsedFeed = parsedFeed { - account.update(webFeed, with: parsedFeed, { _ in + if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { + + refreshProgress?.addToNumberOfTasksAndRemaining(2) + feedProvider.assignName(urlComponents) { result in + self.refreshProgress?.completeTask() + switch result { + case .success(let name): + + webFeed.name = name + container.addWebFeed(webFeed) + + feedProvider.refresh(webFeed) { result in + self.refreshProgress?.completeTask() + switch result { + case .success(let parsedItems): + account.update(url.absoluteString, with: parsedItems) { _ in + completion(webFeed) + } + case .failure: + completion(webFeed) + } + } + + case .failure: completion(webFeed) - }) - } else { - completion(webFeed) + } } + + } else { + + container.addWebFeed(webFeed) + + refreshProgress?.addToNumberOfTasksAndRemaining(1) + InitialFeedDownloader.download(url) { parsedFeed in + self.refreshProgress?.completeTask() + if let parsedFeed = parsedFeed { + account.update(webFeed, with: parsedFeed, { _ in + completion(webFeed) + }) + } else { + completion(webFeed) + } + } + } } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index c88fa7685..9a12c4801 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -84,6 +84,7 @@ final class LocalAccountDelegate: AccountDelegate { } group.notify(queue: DispatchQueue.main) { + account.metadata.lastArticleFetchEndTime = Date() completion(.success(())) } @@ -147,9 +148,9 @@ final class LocalAccountDelegate: AccountDelegate { // Username should be part of the URL on new feed adds if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { - createProviderWebFeed(for: account, urlComponents: urlComponents, name: name, container: container, feedProvider: feedProvider, completion: completion) + createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: name, container: container, feedProvider: feedProvider, completion: completion) } else { - createRSSWebFeed(for: account, url: url, name: name, container: container, completion: completion) + createRSSWebFeed(for: account, url: url, editedName: name, container: container, completion: completion) } } @@ -244,14 +245,13 @@ extension LocalAccountDelegate: LocalAccountRefresherDelegate { func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher) { self.refreshProgress.clear() - account?.metadata.lastArticleFetchEndTime = Date() } } private extension LocalAccountDelegate { - func createProviderWebFeed(for account: Account, urlComponents: URLComponents, name: String?, container: Container, feedProvider: FeedProvider, completion: @escaping (Result) -> Void) { + func createProviderWebFeed(for account: Account, urlComponents: URLComponents, editedName: String?, container: Container, feedProvider: FeedProvider, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(2) feedProvider.assignName(urlComponents) { result in @@ -269,7 +269,7 @@ private extension LocalAccountDelegate { } let feed = account.createWebFeed(with: name, url: newURLString, webFeedID: newURLString, homePageURL: nil) - feed.editedName = name + feed.editedName = editedName feed.username = urlComponents.user container.addWebFeed(feed) @@ -291,7 +291,7 @@ private extension LocalAccountDelegate { } } - func createRSSWebFeed(for account: Account, url: URL, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result) -> Void) { // We need to use a batch update here because we need to assign add the feed to the // container before the name has been downloaded. This will put it in the sidebar @@ -318,7 +318,7 @@ private extension LocalAccountDelegate { } let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) - feed.editedName = name + feed.editedName = editedName container.addWebFeed(feed) InitialFeedDownloader.download(url) { parsedFeed in From 4bfec2d550736a1c0e20ada95dc9cc3ef0e0eb41 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 17:35:24 -0500 Subject: [PATCH 062/108] Added animated gif as a video type. --- Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift index 619ab6209..2b825a069 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift @@ -42,7 +42,7 @@ struct TwitterMedia: Codable { } else { html += renderPhotoAsHTML() } - case "video": + case "video", "animated_gif": html += renderVideoAsHTML() default: break From 06d69eb05d65f1f66b903ef26bd884018572bcef Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 17:40:04 -0500 Subject: [PATCH 063/108] Add exclude replies to user timelines. --- .../Account/FeedProvider/Twitter/TwitterFeedProvider.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 32df07b16..8fba12679 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -161,6 +161,7 @@ public struct TwitterFeedProvider: FeedProvider { api = "statuses/home_timeline.json" default: api = "statuses/user_timeline.json" + parameters["exclude_replies"] = true if let screenName = deriveScreenName(urlComponents) { parameters["screen_name"] = screenName } else { From d66b2fd4fe4748de9d8138a164c652359078d21f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 19:14:08 -0500 Subject: [PATCH 064/108] Add support for Twitter search URL's --- .../Account/Account.xcodeproj/project.pbxproj | 4 +++ .../Twitter/TwitterFeedProvider.swift | 33 ++++++++++++++----- .../Twitter/TwitterSearchResult.swift | 19 +++++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterSearchResult.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 70f38d9b3..3659abf65 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -70,6 +70,7 @@ 51B3630F244B6CB9000DEF2A /* TwitterExtendedEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */; }; 51B36311244B6CFB000DEF2A /* TwitterMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */; }; 51B36313244B8B5E000DEF2A /* TwitterVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36312244B8B5E000DEF2A /* TwitterVideo.swift */; }; + 51B36315244BCCA4000DEF2A /* TwitterSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36314244BCCA4000DEF2A /* TwitterSearchResult.swift */; }; 51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */; }; 51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC8FCB237EC055004F8B56 /* Feed.swift */; }; 51BFDECE238B508D00216323 /* ContainerIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */; }; @@ -319,6 +320,7 @@ 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterExtendedEntities.swift; sourceTree = ""; }; 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterMedia.swift; sourceTree = ""; }; 51B36312244B8B5E000DEF2A /* TwitterVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterVideo.swift; sourceTree = ""; }; + 51B36314244BCCA4000DEF2A /* TwitterSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterSearchResult.swift; sourceTree = ""; }; 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = ""; }; 51BC8FCB237EC055004F8B56 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = ""; }; 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerIdentifier.swift; sourceTree = ""; }; @@ -588,6 +590,7 @@ 51B36306244B6234000DEF2A /* TwitterHashtag.swift */, 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */, 51B3630A244B634A000DEF2A /* TwitterMention.swift */, + 51B36314244BCCA4000DEF2A /* TwitterSearchResult.swift */, 5132DE822449306F00806ADE /* TwitterStatus.swift */, 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */, 51B36308244B62A5000DEF2A /* TwitterURL.swift */, @@ -1186,6 +1189,7 @@ 9E1773D7234575AB0056A5A8 /* FeedlyTag.swift in Sources */, 3B826DAB2385C81C00FC1ADB /* FeedWranglerConfig.swift in Sources */, 515E4EB62324FF8C0057B0E7 /* URLRequest+RSWeb.swift in Sources */, + 51B36315244BCCA4000DEF2A /* TwitterSearchResult.swift in Sources */, 9EB1D576238E6A3900A753D7 /* FeedlyAddNewFeedOperation.swift in Sources */, 3B826DA82385C81C00FC1ADB /* FeedWranglerFeedItem.swift in Sources */, 9E672396236F7E68000BE141 /* OAuthAcessTokenRefreshing.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 8fba12679..c6e24cc1f 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -155,10 +155,17 @@ public struct TwitterFeedProvider: FeedProvider { let api: String var parameters = [String: Any]() + var isSearch = false switch urlComponents.path { case "/", "/home": api = "statuses/home_timeline.json" + case "/search": + api = "search/tweets.json" + if let query = urlComponents.queryItems?.first(where: { $0.name == "q" })?.value { + parameters["q"] = query + } + isSearch = true default: api = "statuses/user_timeline.json" parameters["exclude_replies"] = true @@ -170,7 +177,7 @@ public struct TwitterFeedProvider: FeedProvider { } } - retrieveTweets(api: api, parameters: parameters) { result in + retrieveTweets(api: api, parameters: parameters, isSearch: isSearch) { result in switch result { case .success(let tweets): let parsedItems = self.makeParsedItems(webFeed.url, tweets) @@ -234,7 +241,7 @@ private extension TwitterFeedProvider { } } - func retrieveTweets(api: String, parameters: [String: Any], completion: @escaping (Result<[TwitterStatus], Error>) -> Void) { + func retrieveTweets(api: String, parameters: [String: Any], isSearch: Bool, completion: @escaping (Result<[TwitterStatus], Error>) -> Void) { let url = "\(Self.apiBase)\(api)" var expandedParameters = parameters expandedParameters["tweet_mode"] = "extended" @@ -246,14 +253,24 @@ private extension TwitterFeedProvider { let dateFormatter = DateFormatter() dateFormatter.dateFormat = Self.dateFormat decoder.dateDecodingStrategy = .formatted(dateFormatter) - let jsonString = String(data: response.data, encoding: .utf8) - - let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("twitter.json") - print("******** writing to: \(url.path)") - try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) + +// let jsonString = String(data: response.data, encoding: .utf8) +// let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("twitter.json") +// print("******** writing to: \(url.path)") +// try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) do { - let tweets = try decoder.decode([TwitterStatus].self, from: response.data) + let tweets: [TwitterStatus] + if isSearch { + let searchResult = try decoder.decode(TwitterSearchResult.self, from: response.data) + if let statuses = searchResult.statuses { + tweets = statuses + } else { + tweets = [TwitterStatus]() + } + } else { + tweets = try decoder.decode([TwitterStatus].self, from: response.data) + } completion(.success(tweets)) } catch { completion(.failure(error)) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterSearchResult.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterSearchResult.swift new file mode 100644 index 000000000..6a8a1373f --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterSearchResult.swift @@ -0,0 +1,19 @@ +// +// TwitterSearchResult.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterSearchResult: Codable { + + let statuses: [TwitterStatus]? + + enum CodingKeys: String, CodingKey { + case statuses = "statuses" + } +} + From 6bd4c84c3c3d0308a080acf33c7c970febd59049 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 18 Apr 2020 19:49:05 -0500 Subject: [PATCH 065/108] Fix article uniqueness bug caused by populating sync service id --- .../Account/FeedProvider/Twitter/TwitterFeedProvider.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index c6e24cc1f..4bcfac8fe 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -287,7 +287,7 @@ private extension TwitterFeedProvider { for status in statuses { guard let idStr = status.idStr, let statusURL = status.url else { continue } - let parsedItem = ParsedItem(syncServiceID: idStr, + let parsedItem = ParsedItem(syncServiceID: nil, uniqueID: idStr, feedURL: webFeedURL, url: statusURL, From b80270f65b3ff199607712603253e26fb5efc406 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 19 Apr 2020 17:28:00 -0500 Subject: [PATCH 066/108] Fix iOS timeline layout issue with truncated bylines --- iOS/MasterTimeline/Cell/MasterTimelineCellLayout.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iOS/MasterTimeline/Cell/MasterTimelineCellLayout.swift b/iOS/MasterTimeline/Cell/MasterTimelineCellLayout.swift index c235670c9..6913a3989 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineCellLayout.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineCellLayout.swift @@ -98,7 +98,8 @@ extension MasterTimelineCellLayout { var r = CGRect.zero r.origin = point - let size = SingleLineUILabelSizer.size(for: cellData.feedName, font: MasterTimelineDefaultCellLayout.feedNameFont) + let feedName = cellData.showFeedName == .feed ? cellData.feedName : cellData.byline + let size = SingleLineUILabelSizer.size(for: feedName, font: MasterTimelineDefaultCellLayout.feedNameFont) r.size = size if r.size.width > textAreaWidth { From eeb7b518a86ef9ee0f15ad4d9b10f3234684c5d1 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 19 Apr 2020 17:29:11 -0500 Subject: [PATCH 067/108] Change how showIcons is determined to take into consideration empty bylines --- Mac/MainWindow/Timeline/TimelineViewController.swift | 9 +++++++-- iOS/SceneCoordinator.swift | 7 ++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 4a79c36b8..ab1e9642d 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -96,7 +96,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr if let representedObjects = representedObjects, representedObjects.count == 1 && representedObjects.first is WebFeed { showFeedNames = { for article in articles { - if article.authors?.contains(where: { $0.name != nil }) ?? false { + if !article.byline().isEmpty { return .byline } } @@ -955,11 +955,16 @@ private extension TimelineViewController { } func updateShowIcons() { - if showFeedNames != .none { + if showFeedNames == .feed { self.showIcons = true return } + if showFeedNames == .none { + self.showIcons = false + return + } + for article in articles { if let authors = article.authors { for author in authors { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index d384f53ee..509a42425 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -1462,7 +1462,7 @@ private extension SceneCoordinator { if timelineFeed is WebFeed { showFeedNames = { for article in articles { - if article.authors?.contains(where: { $0.name != nil }) ?? false { + if !article.byline().isEmpty { return .byline } } @@ -1477,6 +1477,11 @@ private extension SceneCoordinator { return } + if showFeedNames == .none { + self.showIcons = false + return + } + for article in articles { if let authors = article.authors { for author in authors { From 19a6ff883d4feb13a169eb8d899c4bcad1d171cb Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 19 Apr 2020 17:37:32 -0500 Subject: [PATCH 068/108] Fix CloudKit feed double counting in progress indicator. --- Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift index 88c788388..a7c2be710 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift @@ -569,7 +569,6 @@ private extension CloudKitAccountDelegate { for webFeed in webFeeds { if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components, with: webFeed.username) { - refreshProgress.addToNumberOfTasksAndRemaining(1) group.enter() feedProvider.refresh(webFeed) { result in switch result { @@ -589,7 +588,6 @@ private extension CloudKitAccountDelegate { } } - refreshProgress.addToNumberOfTasksAndRemaining(refresherWebFeeds.count) group.enter() refresher.refreshFeeds(refresherWebFeeds) { group.leave() From bb61b15265a2ba30cc1119ea886d898d7e5f612f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 19 Apr 2020 19:19:39 -0500 Subject: [PATCH 069/108] Stub out support for Twitter entities --- .../Twitter/TwitterEntities.swift | 39 +++++++++++++++++++ .../FeedProvider/Twitter/TwitterHashtag.swift | 5 ++- .../FeedProvider/Twitter/TwitterMention.swift | 6 ++- .../FeedProvider/Twitter/TwitterSymbol.swift | 6 ++- .../FeedProvider/Twitter/TwitterURL.swift | 6 ++- 5 files changed, 58 insertions(+), 4 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift index 79d7703d7..1096af93a 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift @@ -8,6 +8,28 @@ import Foundation +protocol TwitterEntity { + var indices: [Int]? { get } + func renderAsHTML() -> String +} + +extension TwitterEntity { + var startIndex: Int? { + if indices?.count ?? 0 > 0 { + return indices?[0] + } + return nil + } + + var endIndex: Int? { + if indices?.count ?? 0 > 1 { + return indices?[1] + } + return nil + } + +} + struct TwitterEntities: Codable { let hashtags: [TwitterHashtag]? @@ -22,4 +44,21 @@ struct TwitterEntities: Codable { case symbols = "symbols" } + func combineAndSort() -> [TwitterEntity] { + var entities = [TwitterEntity]() + if let hashtags = hashtags { + entities.append(contentsOf: hashtags) + } + if let urls = urls { + entities.append(contentsOf: urls) + } + if let userMentions = userMentions { + entities.append(contentsOf: userMentions) + } + if let symbols = symbols { + entities.append(contentsOf: symbols) + } + return entities + } + } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift index 92f6c4237..1877347ef 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift @@ -8,7 +8,7 @@ import Foundation -struct TwitterHashtag: Codable { +struct TwitterHashtag: Codable, TwitterEntity { let text: String? let indices: [Int]? @@ -18,4 +18,7 @@ struct TwitterHashtag: Codable { case indices = "indices" } + func renderAsHTML() -> String { + return "" + } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift index a923b4bdb..72310c8ae 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift @@ -8,7 +8,7 @@ import Foundation -struct TwitterMention: Codable { +struct TwitterMention: Codable, TwitterEntity { let name: String? let indices: [Int]? @@ -24,4 +24,8 @@ struct TwitterMention: Codable { case idStr = "idStr" } + func renderAsHTML() -> String { + return "" + } + } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift index 881204cec..2071c0b47 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift @@ -8,7 +8,7 @@ import Foundation -struct TwitterSymbol: Codable { +struct TwitterSymbol: Codable, TwitterEntity { let name: String? let indices: [Int]? @@ -18,4 +18,8 @@ struct TwitterSymbol: Codable { case indices = "indices" } + func renderAsHTML() -> String { + return "" + } + } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift index f0be16646..5c8c8d2da 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift @@ -8,7 +8,7 @@ import Foundation -struct TwitterURL: Codable { +struct TwitterURL: Codable, TwitterEntity { let url: String? let indices: [Int]? @@ -22,4 +22,8 @@ struct TwitterURL: Codable { case expandedURL = "expandedURL" } + func renderAsHTML() -> String { + return "" + } + } From 45f56f01e37124e7766f604198099cf7d8704133 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 19 Apr 2020 19:27:37 -0500 Subject: [PATCH 070/108] Sort returned entities --- .../Twitter/TwitterEntities.swift | 19 ++++++++++--------- .../FeedProvider/Twitter/TwitterHashtag.swift | 7 +++++++ .../FeedProvider/Twitter/TwitterSymbol.swift | 7 +++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift index 1096af93a..92d53e020 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift @@ -14,18 +14,19 @@ protocol TwitterEntity { } extension TwitterEntity { - var startIndex: Int? { - if indices?.count ?? 0 > 0 { - return indices?[0] + + var startIndex: Int { + if let indices = indices, indices.count > 0 { + return indices[0] } - return nil + return 0 } - var endIndex: Int? { - if indices?.count ?? 0 > 1 { - return indices?[1] + var endIndex: Int { + if let indices = indices, indices.count > 1 { + return indices[1] } - return nil + return 0 } } @@ -58,7 +59,7 @@ struct TwitterEntities: Codable { if let symbols = symbols { entities.append(contentsOf: symbols) } - return entities + return entities.sorted(by: { $0.startIndex < $1.startIndex }) } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift index 1877347ef..a2a614e61 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift @@ -18,6 +18,13 @@ struct TwitterHashtag: Codable, TwitterEntity { case indices = "indices" } + var startIndex: Int { + if let indices = indices, indices.count > 0 { + return indices[0] - 1 + } + return 0 + } + func renderAsHTML() -> String { return "" } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift index 2071c0b47..7c90d9300 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift @@ -18,6 +18,13 @@ struct TwitterSymbol: Codable, TwitterEntity { case indices = "indices" } + var startIndex: Int { + if let indices = indices, indices.count > 0 { + return indices[0] - 1 + } + return 0 + } + func renderAsHTML() -> String { return "" } From f305cc50e2e7077dd4f0ceed5bfc0fc6fe3c76c8 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 19 Apr 2020 20:29:43 -0500 Subject: [PATCH 071/108] Implement twitter entities as html links --- .../FeedProvider/Twitter/TwitterHashtag.swift | 13 ++--- .../FeedProvider/Twitter/TwitterMention.swift | 8 ++- .../FeedProvider/Twitter/TwitterStatus.swift | 58 +++++++++++++++---- .../FeedProvider/Twitter/TwitterSymbol.swift | 15 ++--- .../FeedProvider/Twitter/TwitterURL.swift | 10 +++- 5 files changed, 70 insertions(+), 34 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift index a2a614e61..029f56c43 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterHashtag.swift @@ -18,14 +18,11 @@ struct TwitterHashtag: Codable, TwitterEntity { case indices = "indices" } - var startIndex: Int { - if let indices = indices, indices.count > 0 { - return indices[0] - 1 - } - return 0 - } - func renderAsHTML() -> String { - return "" + var html = String() + if let text = text { + html += "#\(text)" + } + return html } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift index 72310c8ae..4cd00551f 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterMention.swift @@ -13,19 +13,21 @@ struct TwitterMention: Codable, TwitterEntity { let name: String? let indices: [Int]? let screenName: String? - let expandedURL: String? let idStr: String? enum CodingKeys: String, CodingKey { case name = "url" case indices = "indices" case screenName = "screen_name" - case expandedURL = "expandedURL" case idStr = "idStr" } func renderAsHTML() -> String { - return "" + var html = String() + if let screenName = screenName { + html += "@\(screenName)" + } + return html } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index 62f429371..cb9c2886c 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -41,16 +41,6 @@ final class TwitterStatus: Codable { return "\(userURL)/status/\(idStr)" } - var displayText: String? { - if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1, - let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), - let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) { - return String(text[startIndex.. String? { let statusToRender = retweetedStatus != nil ? retweetedStatus! : self return statusToRender.displayText @@ -66,8 +56,54 @@ final class TwitterStatus: Codable { return renderAsOriginalHTML(topLevel: topLevel) } +} + +private extension TwitterStatus { + + var displayText: String? { + if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1, + let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), + let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) { + return String(text[startIndex.. 1, + let displayStartIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), + let displayEndIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex), + let entities = entities?.combineAndSort() { + + var html = String() + var prevIndex = displayStartIndex + + for entity in entities { + if let entityStartIndex = text.index(text.startIndex, offsetBy: entity.startIndex, limitedBy: text.endIndex), + let entityEndIndex = text.index(text.startIndex, offsetBy: entity.endIndex, limitedBy: text.endIndex) { + + if prevIndex < entityStartIndex { + html += String(text[prevIndex.. String { - var html = "
\(status.displayText ?? "")
" + var html = "
\(status.displayHTML ?? "")
" if !topLevel, let createdAt = status.createdAt { let dateFormatter = DateFormatter() diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift index 7c90d9300..525a1a595 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterSymbol.swift @@ -17,16 +17,13 @@ struct TwitterSymbol: Codable, TwitterEntity { case name = "name" case indices = "indices" } - - var startIndex: Int { - if let indices = indices, indices.count > 0 { - return indices[0] - 1 - } - return 0 - } - + func renderAsHTML() -> String { - return "" + var html = String() + if let name = name { + html += "$\(name)" + } + return html } } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift index 5c8c8d2da..d7f80c6b6 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterURL.swift @@ -18,12 +18,16 @@ struct TwitterURL: Codable, TwitterEntity { enum CodingKeys: String, CodingKey { case url = "url" case indices = "indices" - case displayURL = "displayURL" - case expandedURL = "expandedURL" + case displayURL = "display_url" + case expandedURL = "expanded_url" } func renderAsHTML() -> String { - return "" + var html = String() + if let expandedURL = expandedURL, let displayURL = displayURL { + html += "\(displayURL)" + } + return html } } From a54af07ec763051744a14035283f06234d80c08a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 19 Apr 2020 20:54:11 -0500 Subject: [PATCH 072/108] Make the retweet timestamp a link back to the original status. --- Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index cb9c2886c..9fb9ad047 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -105,11 +105,13 @@ private extension TwitterStatus { func renderAsTweetHTML(_ status: TwitterStatus, topLevel: Bool) -> String { var html = "
\(status.displayHTML ?? "")
" - if !topLevel, let createdAt = status.createdAt { + if !topLevel, let createdAt = status.createdAt, let url = status.url { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .short + html += "" html += "
\(dateFormatter.string(from: createdAt))
" + html += "
" } return html From 8fcc46191aafc3a25bc91c2b6be65ab2c9eb7251 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 20 Apr 2020 07:27:20 -0500 Subject: [PATCH 073/108] Suppress any URL links that are just pointing to the quoted status. --- .../Account/FeedProvider/Twitter/TwitterStatus.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index 9fb9ad047..a33b481a7 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -86,9 +86,17 @@ private extension TwitterStatus { if prevIndex < entityStartIndex { html += String(text[prevIndex.. Date: Mon, 20 Apr 2020 07:37:06 -0500 Subject: [PATCH 074/108] Simplified html for tweet timestamp. --- Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index a33b481a7..30d532c19 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -117,9 +117,7 @@ private extension TwitterStatus { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .short - html += "" - html += "
\(dateFormatter.string(from: createdAt))
" - html += "
" + html += "\(dateFormatter.string(from: createdAt))" } return html From 56c8dad895bce7d545769d74bbb0921552c3f66a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 20 Apr 2020 10:21:29 -0500 Subject: [PATCH 075/108] Improve tweet formatting when they contain emoji with multiple scalars --- .../Twitter/TwitterFeedProvider.swift | 8 ++-- .../FeedProvider/Twitter/TwitterStatus.swift | 48 ++++++++++++------- submodules/RSCore | 2 +- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 4bcfac8fe..10892371f 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -254,10 +254,10 @@ private extension TwitterFeedProvider { dateFormatter.dateFormat = Self.dateFormat decoder.dateDecodingStrategy = .formatted(dateFormatter) -// let jsonString = String(data: response.data, encoding: .utf8) -// let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("twitter.json") -// print("******** writing to: \(url.path)") -// try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) + let jsonString = String(data: response.data, encoding: .utf8) + let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("twitter.json") + print("******** writing to: \(url.path)") + try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) do { let tweets: [TwitterStatus] diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index 30d532c19..713537b7e 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -71,33 +71,47 @@ private extension TwitterStatus { } var displayHTML: String? { - if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1, - let displayStartIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex), - let displayEndIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex), - let entities = entities?.combineAndSort() { + if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1, let entities = entities?.combineAndSort() { + let displayStartIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex) ?? text.startIndex + let displayEndIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) ?? text.endIndex + var html = String() var prevIndex = displayStartIndex for entity in entities { - if let entityStartIndex = text.index(text.startIndex, offsetBy: entity.startIndex, limitedBy: text.endIndex), - let entityEndIndex = text.index(text.startIndex, offsetBy: entity.endIndex, limitedBy: text.endIndex) { - - if prevIndex < entityStartIndex { - html += String(text[prevIndex..") + } + + // We drop off any URL which is just pointing to the quoted status. It is redundant. + if let twitterURL = entity as? TwitterURL, let expandedURL = twitterURL.expandedURL, let quotedURL = quotedStatus?.url { + if expandedURL.caseInsensitiveCompare(quotedURL) != .orderedSame { html += entity.renderAsHTML() } - - prevIndex = entityEndIndex + } else { + html += entity.renderAsHTML() } + + prevIndex = entityEndIndex + } if prevIndex < displayEndIndex { diff --git a/submodules/RSCore b/submodules/RSCore index a742db73c..3dfa570a4 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit a742db73c4f4007f0d7097746c88ce2074400045 +Subproject commit 3dfa570a4600690290cd946b8e122b0b99da0a13 From 675165b4daa6eb15e2dc884a0478f94f3242d51a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 20 Apr 2020 10:32:36 -0500 Subject: [PATCH 076/108] Display quote and retweet entities. --- Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index 713537b7e..463a63b1e 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -141,6 +141,8 @@ private extension TwitterStatus { var html = renderAsTweetHTML(self, topLevel: topLevel) if topLevel { html += extendedEntities?.renderAsHTML() ?? "" + html += retweetedStatus?.extendedEntities?.renderAsHTML() ?? "" + html += quotedStatus?.extendedEntities?.renderAsHTML() ?? "" } return html } @@ -154,6 +156,8 @@ private extension TwitterStatus { html += "
" if topLevel { html += status.extendedEntities?.renderAsHTML() ?? "" + html += status.retweetedStatus?.extendedEntities?.renderAsHTML() ?? "" + html += status.quotedStatus?.extendedEntities?.renderAsHTML() ?? "" } return html } @@ -170,6 +174,8 @@ private extension TwitterStatus { html += self.extendedEntities?.renderAsHTML() ?? "" if topLevel { html += quotedStatus.extendedEntities?.renderAsHTML() ?? "" + html += quotedStatus.retweetedStatus?.extendedEntities?.renderAsHTML() ?? "" + html += quotedStatus.quotedStatus?.extendedEntities?.renderAsHTML() ?? "" } return html } From e459fe92b0d81c8212be291bfde1cb3bf603b7b4 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 20 Apr 2020 12:05:57 -0500 Subject: [PATCH 077/108] Add TwitterMedia type to delete media URL's shown in tweets. --- .../Account/Account.xcodeproj/project.pbxproj | 12 ++- .../Twitter/TwitterEntities.swift | 7 +- .../Twitter/TwitterExtendedEntities.swift | 2 +- .../Twitter/TwitterExtendedMedia.swift | 100 ++++++++++++++++++ .../Twitter/TwitterFeedProvider.swift | 8 +- .../FeedProvider/Twitter/TwitterMedia.swift | 84 +-------------- 6 files changed, 122 insertions(+), 91 deletions(-) create mode 100644 Frameworks/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 3659abf65..5f2eaa40e 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -33,6 +33,7 @@ 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; }; 510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; }; 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */; }; + 510E3317244E0CED00E7A6AF /* TwitterMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510E3316244E0CED00E7A6AF /* TwitterMedia.swift */; }; 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* FeedIdentifier.swift */; }; 512DD4CB2431000600C17B1F /* CKRecord+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512DD4CA2431000600C17B1F /* CKRecord+Extensions.swift */; }; 512DD4CD2431098700C17B1F /* CloudKitAccountZoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512DD4CC2431098700C17B1F /* CloudKitAccountZoneDelegate.swift */; }; @@ -68,7 +69,7 @@ 51B3630B244B634A000DEF2A /* TwitterMention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630A244B634A000DEF2A /* TwitterMention.swift */; }; 51B3630D244B6428000DEF2A /* TwitterSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */; }; 51B3630F244B6CB9000DEF2A /* TwitterExtendedEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */; }; - 51B36311244B6CFB000DEF2A /* TwitterMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */; }; + 51B36311244B6CFB000DEF2A /* TwitterExtendedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36310244B6CFA000DEF2A /* TwitterExtendedMedia.swift */; }; 51B36313244B8B5E000DEF2A /* TwitterVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36312244B8B5E000DEF2A /* TwitterVideo.swift */; }; 51B36315244BCCA4000DEF2A /* TwitterSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B36314244BCCA4000DEF2A /* TwitterSearchResult.swift */; }; 51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */; }; @@ -280,6 +281,7 @@ 5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = ""; }; 510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = ""; }; 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadataFile.swift; sourceTree = ""; }; + 510E3316244E0CED00E7A6AF /* TwitterMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterMedia.swift; sourceTree = ""; }; 511076A3243BD33100D97C8C /* .framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = .framework; sourceTree = BUILT_PRODUCTS_DIR; }; 511076F4243BD96D00D97C8C /* FeedProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FeedProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 511B9803237CD4270028BCAA /* FeedIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIdentifier.swift; sourceTree = ""; }; @@ -318,7 +320,7 @@ 51B3630A244B634A000DEF2A /* TwitterMention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterMention.swift; sourceTree = ""; }; 51B3630C244B6428000DEF2A /* TwitterSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterSymbol.swift; sourceTree = ""; }; 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterExtendedEntities.swift; sourceTree = ""; }; - 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterMedia.swift; sourceTree = ""; }; + 51B36310244B6CFA000DEF2A /* TwitterExtendedMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterExtendedMedia.swift; sourceTree = ""; }; 51B36312244B8B5E000DEF2A /* TwitterVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterVideo.swift; sourceTree = ""; }; 51B36314244BCCA4000DEF2A /* TwitterSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterSearchResult.swift; sourceTree = ""; }; 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = ""; }; @@ -588,7 +590,7 @@ 51B3630E244B6CB9000DEF2A /* TwitterExtendedEntities.swift */, 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */, 51B36306244B6234000DEF2A /* TwitterHashtag.swift */, - 51B36310244B6CFA000DEF2A /* TwitterMedia.swift */, + 51B36310244B6CFA000DEF2A /* TwitterExtendedMedia.swift */, 51B3630A244B634A000DEF2A /* TwitterMention.swift */, 51B36314244BCCA4000DEF2A /* TwitterSearchResult.swift */, 5132DE822449306F00806ADE /* TwitterStatus.swift */, @@ -596,6 +598,7 @@ 51B36308244B62A5000DEF2A /* TwitterURL.swift */, 5132DE802449159100806ADE /* TwitterUser.swift */, 51B36312244B8B5E000DEF2A /* TwitterVideo.swift */, + 510E3316244E0CED00E7A6AF /* TwitterMedia.swift */, ); path = Twitter; sourceTree = ""; @@ -1256,6 +1259,7 @@ 84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */, 9EF1B10723590D61000A486A /* FeedlyGetStreamIdsOperation.swift in Sources */, 84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */, + 510E3317244E0CED00E7A6AF /* TwitterMedia.swift in Sources */, 9EF2602C23C91FFE006D160C /* FeedlyGetUpdatedArticleIdsOperation.swift in Sources */, 3B826DAA2385C81C00FC1ADB /* FeedWranglerSubscription.swift in Sources */, 5132DE812449159100806ADE /* TwitterUser.swift in Sources */, @@ -1264,7 +1268,7 @@ 51B3630D244B6428000DEF2A /* TwitterSymbol.swift in Sources */, 769F2BA02EF5F329CDE45F5A /* NewsBlurAPICaller.swift in Sources */, 51C034DF242D65D20014DC71 /* CloudKitZoneResult.swift in Sources */, - 51B36311244B6CFB000DEF2A /* TwitterMedia.swift in Sources */, + 51B36311244B6CFB000DEF2A /* TwitterExtendedMedia.swift in Sources */, 179DB28CF49F73A945EBF5DB /* NewsBlurLoginResponse.swift in Sources */, 179DBF4DE2562D4C532F6008 /* NewsBlurFeed.swift in Sources */, 179DB02FFBC17AC9798F0EBC /* NewsBlurStory.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift index 92d53e020..15d4c39ee 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterEntities.swift @@ -37,12 +37,14 @@ struct TwitterEntities: Codable { let urls: [TwitterURL]? let userMentions: [TwitterMention]? let symbols: [TwitterSymbol]? - + let media: [TwitterMedia]? + enum CodingKeys: String, CodingKey { case hashtags = "hashtags" case urls = "urls" case userMentions = "user_mentions" case symbols = "symbols" + case media = "media" } func combineAndSort() -> [TwitterEntity] { @@ -59,6 +61,9 @@ struct TwitterEntities: Codable { if let symbols = symbols { entities.append(contentsOf: symbols) } + if let media = media { + entities.append(contentsOf: media) + } return entities.sorted(by: { $0.startIndex < $1.startIndex }) } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift index 1dedd3052..e449e66ca 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedEntities.swift @@ -10,7 +10,7 @@ import Foundation struct TwitterExtendedEntities: Codable { - let medias: [TwitterMedia]? + let medias: [TwitterExtendedMedia]? enum CodingKeys: String, CodingKey { case medias = "media" diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift new file mode 100644 index 000000000..442f4043c --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift @@ -0,0 +1,100 @@ +// +// TwitterExtendedMedia.swift +// Account +// +// Created by Maurice Parker on 4/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct TwitterExtendedMedia: Codable { + + let idStr: String? + let indices: [Int]? + let mediaURL: String? + let httpsMediaURL: String? + let url: String? + let displayURL: String? + let type: String? + let video: TwitterVideo? + + enum CodingKeys: String, CodingKey { + case idStr = "idStr" + case indices = "indices" + case mediaURL = "media_url" + case httpsMediaURL = "media_url_https" + case url = "url" + case displayURL = "display_url" + case type = "type" + case video = "video_info" + } + + func renderAsHTML() -> String { + var html = String() + + switch type { + case "photo": + if let url = url { + html += "" + html += renderPhotoAsHTML() + html += "" + } else { + html += renderPhotoAsHTML() + } + case "video", "animated_gif": + html += renderVideoAsHTML() + default: + break + } + + return html + } + +} + +private extension TwitterExtendedMedia { + + func renderPhotoAsHTML() -> String { + if let httpsMediaURL = httpsMediaURL { + return "
" + } + if let mediaURL = mediaURL { + return "
" + } + return "" + } + + func renderVideoAsHTML() -> String { + guard let bestVariantURL = findBestVariant()?.url else { return "" } + + var html = "" + return html + } + + func findBestVariant() -> TwitterVideo.Variant? { + var best: TwitterVideo.Variant? = nil + if let variants = video?.variants { + for variant in variants { + if let currentBest = best { + if variant.bitrate ?? 0 > currentBest.bitrate ?? 0 { + best = variant + } + } else { + best = variant + } + } + } + return best + } + +// +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 10892371f..4bcfac8fe 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -254,10 +254,10 @@ private extension TwitterFeedProvider { dateFormatter.dateFormat = Self.dateFormat decoder.dateDecodingStrategy = .formatted(dateFormatter) - let jsonString = String(data: response.data, encoding: .utf8) - let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("twitter.json") - print("******** writing to: \(url.path)") - try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) +// let jsonString = String(data: response.data, encoding: .utf8) +// let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("twitter.json") +// print("******** writing to: \(url.path)") +// try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) do { let tweets: [TwitterStatus] diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift index 2b825a069..57f279a19 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterMedia.swift @@ -2,99 +2,21 @@ // TwitterMedia.swift // Account // -// Created by Maurice Parker on 4/18/20. +// Created by Maurice Parker on 4/20/20. // Copyright © 2020 Ranchero Software, LLC. All rights reserved. // import Foundation -struct TwitterMedia: Codable { +struct TwitterMedia: Codable, TwitterEntity { - let idStr: String? let indices: [Int]? - let mediaURL: String? - let httpsMediaURL: String? - let url: String? - let displayURL: String? - let type: String? - let video: TwitterVideo? enum CodingKeys: String, CodingKey { - case idStr = "idStr" case indices = "indices" - case mediaURL = "media_url" - case httpsMediaURL = "media_url_https" - case url = "url" - case displayURL = "display_url" - case type = "type" - case video = "video_info" } func renderAsHTML() -> String { - var html = String() - - switch type { - case "photo": - if let url = url { - html += "" - html += renderPhotoAsHTML() - html += "" - } else { - html += renderPhotoAsHTML() - } - case "video", "animated_gif": - html += renderVideoAsHTML() - default: - break - } - - return html + return String() } - -} - -private extension TwitterMedia { - - func renderPhotoAsHTML() -> String { - if let httpsMediaURL = httpsMediaURL { - return "
" - } - if let mediaURL = mediaURL { - return "
" - } - return "" - } - - func renderVideoAsHTML() -> String { - guard let bestVariantURL = findBestVariant()?.url else { return "" } - - var html = "" - return html - } - - func findBestVariant() -> TwitterVideo.Variant? { - var best: TwitterVideo.Variant? = nil - if let variants = video?.variants { - for variant in variants { - if let currentBest = best { - if variant.bitrate ?? 0 > currentBest.bitrate ?? 0 { - best = variant - } - } else { - best = variant - } - } - } - return best - } - -// } From b7a37e2a25d01de2eb64308b26fc91fb41b39f4d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 20 Apr 2020 12:14:43 -0500 Subject: [PATCH 078/108] Make mouseover/mouseout work with anchors that nest --- Mac/MainWindow/Detail/main_mac.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Mac/MainWindow/Detail/main_mac.js b/Mac/MainWindow/Detail/main_mac.js index ae73b4db3..90329f7b4 100644 --- a/Mac/MainWindow/Detail/main_mac.js +++ b/Mac/MainWindow/Detail/main_mac.js @@ -1,13 +1,15 @@ // Add the mouse listeners for the above functions function linkHover() { window.onmouseover = function(event) { - if (event.target.matches('a')) { - window.webkit.messageHandlers.mouseDidEnter.postMessage(event.target.href); + var closestAnchor = event.target.closest('a') + if (closestAnchor) { + window.webkit.messageHandlers.mouseDidEnter.postMessage(closestAnchor.href); } } window.onmouseout = function(event) { - if (event.target.matches('a')) { - window.webkit.messageHandlers.mouseDidExit.postMessage(event.target.href); + var closestAnchor = event.target.closest('a') + if (closestAnchor) { + window.webkit.messageHandlers.mouseDidExit.postMessage(closestAnchor.href); } } } From 91aa2b3a321bf9305e07b436bae92734fb1d2061 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 20 Apr 2020 14:24:14 -0500 Subject: [PATCH 079/108] Fix bug that was causing duplicate media to be inlined --- Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index 463a63b1e..6d054d21b 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -171,7 +171,6 @@ private extension TwitterStatus { } html += quotedStatus.renderAsHTML(topLevel: false) html += "
" - html += self.extendedEntities?.renderAsHTML() ?? "" if topLevel { html += quotedStatus.extendedEntities?.renderAsHTML() ?? "" html += quotedStatus.retweetedStatus?.extendedEntities?.renderAsHTML() ?? "" From a535aced062077a8b2c1c6dc9ba1841223986c48 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 20 Apr 2020 15:06:03 -0500 Subject: [PATCH 080/108] Add support for refreshing the mentions timeline. --- .../Account/FeedProvider/Twitter/TwitterFeedProvider.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 4bcfac8fe..aada4c2ce 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -160,6 +160,8 @@ public struct TwitterFeedProvider: FeedProvider { switch urlComponents.path { case "/", "/home": api = "statuses/home_timeline.json" + case "/notifications/mentions": + api = "statuses/mentions_timeline.json" case "/search": api = "search/tweets.json" if let query = urlComponents.queryItems?.first(where: { $0.name == "q" })?.value { From 5f8249ec54f17bd524de432c4bc7945955096d1b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 20 Apr 2020 15:11:09 -0500 Subject: [PATCH 081/108] Bump the number of items in the home timeline to 100. --- .../Account/FeedProvider/Twitter/TwitterFeedProvider.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index aada4c2ce..a7dd2771f 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -159,6 +159,7 @@ public struct TwitterFeedProvider: FeedProvider { switch urlComponents.path { case "/", "/home": + parameters["count"] = 100 api = "statuses/home_timeline.json" case "/notifications/mentions": api = "statuses/mentions_timeline.json" From 77fa966a23d72398df8fb0f134e44fee0fbd39b5 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 20 Apr 2020 15:42:22 -0500 Subject: [PATCH 082/108] Add hashtag support --- .../Twitter/TwitterFeedProvider.swift | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index a7dd2771f..7dde373fa 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -127,7 +127,9 @@ public struct TwitterFeedProvider: FeedProvider { } default: - if let screenName = deriveScreenName(urlComponents) { + if let hashtag = deriveHashtag(urlComponents) { + completion(.success("#\(hashtag)")) + } else if let screenName = deriveScreenName(urlComponents) { retrieveUser(screenName: screenName) { result in switch result { case .success(let user): @@ -170,13 +172,19 @@ public struct TwitterFeedProvider: FeedProvider { } isSearch = true default: - api = "statuses/user_timeline.json" - parameters["exclude_replies"] = true - if let screenName = deriveScreenName(urlComponents) { - parameters["screen_name"] = screenName + if let hashtag = deriveHashtag(urlComponents) { + api = "search/tweets.json" + parameters["q"] = "#\(hashtag)" + isSearch = true } else { - completion(.failure(TwitterFeedProviderError.unknown)) - return + api = "statuses/user_timeline.json" + parameters["exclude_replies"] = true + if let screenName = deriveScreenName(urlComponents) { + parameters["screen_name"] = screenName + } else { + completion(.failure(TwitterFeedProviderError.unknown)) + return + } } } @@ -213,6 +221,14 @@ extension TwitterFeedProvider: OAuth1SwiftProvider { private extension TwitterFeedProvider { + func deriveHashtag(_ urlComponents: URLComponents) -> String? { + let path = urlComponents.path + if path.starts(with: "/hashtag/"), let startIndex = path.index(path.startIndex, offsetBy: 9, limitedBy: path.endIndex), startIndex < path.endIndex { + return String(path[startIndex.. String? { let path = urlComponents.path guard !Self.reservedPaths.contains(path) else { return nil } From 0369bc5a45b43ac56830de1f05499d706f6b0c7c Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 21 Apr 2020 01:26:58 -0500 Subject: [PATCH 083/108] Fix emoji unicode adjustment bug and made emoji detection more efficient --- .../FeedProvider/Twitter/TwitterStatus.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift index 6d054d21b..59f3dfc58 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift @@ -78,21 +78,21 @@ private extension TwitterStatus { var html = String() var prevIndex = displayStartIndex + var emojiOffset = 0 for entity in entities { - // The twitter indices are messed up by emoji with more than one scalar, we are trying to adjust for that here. - var offset = 0 + // The twitter indices are messed up by emoji with more than one scalar, we are going to adjust for that here. let emojiEndIndex = text.index(text.startIndex, offsetBy: entity.endIndex, limitedBy: text.endIndex) ?? text.endIndex - if text.startIndex < prevIndex { - let emojis = String(text[text.startIndex.. Date: Tue, 21 Apr 2020 18:33:07 -0500 Subject: [PATCH 084/108] Refactored add window code to allow for multiple types of feed add window --- .../{AddFeedSheet.xib => AddWebFeedSheet.xib} | 21 ++++---- .../AddFeed/AddFeedController.swift | 7 ++- .../AddFeed/AddFeedWIndowController.swift | 25 +++++++++ ...swift => AddWebFeedWindowController.swift} | 14 ++--- NetNewsWire.xcodeproj/project.pbxproj | 52 +++++++++++-------- 5 files changed, 70 insertions(+), 49 deletions(-) rename Mac/Base.lproj/{AddFeedSheet.xib => AddWebFeedSheet.xib} (95%) create mode 100644 Mac/MainWindow/AddFeed/AddFeedWIndowController.swift rename Mac/MainWindow/AddFeed/{AddFeedWindowController.swift => AddWebFeedWindowController.swift} (88%) diff --git a/Mac/Base.lproj/AddFeedSheet.xib b/Mac/Base.lproj/AddWebFeedSheet.xib similarity index 95% rename from Mac/Base.lproj/AddFeedSheet.xib rename to Mac/Base.lproj/AddWebFeedSheet.xib index 4806b7f5b..e8e8660a5 100644 --- a/Mac/Base.lproj/AddFeedSheet.xib +++ b/Mac/Base.lproj/AddWebFeedSheet.xib @@ -1,12 +1,11 @@ - + - - + - + @@ -23,11 +22,11 @@ - + - + @@ -35,7 +34,7 @@ - + @@ -50,7 +49,7 @@ - + @@ -58,7 +57,7 @@ - + @@ -66,7 +65,7 @@ - + @@ -77,7 +76,7 @@ - + diff --git a/Mac/MainWindow/AddFeed/AddFeedController.swift b/Mac/MainWindow/AddFeed/AddFeedController.swift index 8282126ac..9c03c3e23 100644 --- a/Mac/MainWindow/AddFeed/AddFeedController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedController.swift @@ -38,13 +38,13 @@ class AddFeedController: AddFeedWindowControllerDelegate { let folderTreeControllerDelegate = FolderTreeControllerDelegate() let folderTreeController = TreeController(delegate: folderTreeControllerDelegate) - addFeedWindowController = AddFeedWindowController(urlString: urlString ?? urlStringFromPasteboard, name: name, account: account, folder: folder, folderTreeController: folderTreeController, delegate: self) + addFeedWindowController = AddWebFeedWindowController(urlString: urlString ?? urlStringFromPasteboard, name: name, account: account, folder: folder, folderTreeController: folderTreeController, delegate: self) addFeedWindowController!.runSheetOnWindow(hostWindow) } // MARK: AddFeedWindowControllerDelegate - func addFeedWindowController(_: AddFeedWindowController, userEnteredURL url: URL, userEnteredTitle title: String?, container: Container) { + func addFeedWindowController(_: AddWebFeedWindowController, userEnteredURL url: URL, userEnteredTitle title: String?, container: Container) { closeAddFeedSheet(NSApplication.ModalResponse.OK) @@ -84,8 +84,7 @@ class AddFeedController: AddFeedWindowControllerDelegate { } - func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController) { - + func addFeedWindowControllerUserDidCancel(_: AddWebFeedWindowController) { closeAddFeedSheet(NSApplication.ModalResponse.cancel) } diff --git a/Mac/MainWindow/AddFeed/AddFeedWIndowController.swift b/Mac/MainWindow/AddFeed/AddFeedWIndowController.swift new file mode 100644 index 000000000..e1fd3732e --- /dev/null +++ b/Mac/MainWindow/AddFeed/AddFeedWIndowController.swift @@ -0,0 +1,25 @@ +// +// AddFeedWIndowController.swift +// NetNewsWire +// +// Created by Maurice Parker on 4/21/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import Account + +protocol AddFeedWindowControllerDelegate: class { + + // userEnteredURL will have already been validated and normalized. + func addFeedWindowController(_: AddWebFeedWindowController, userEnteredURL: URL, userEnteredTitle: String?, container: Container) + func addFeedWindowControllerUserDidCancel(_: AddWebFeedWindowController) + +} + +protocol AddFeedWindowController { + + var window: NSWindow? { get } + func runSheetOnWindow(_ hostWindow: NSWindow) + +} diff --git a/Mac/MainWindow/AddFeed/AddFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift similarity index 88% rename from Mac/MainWindow/AddFeed/AddFeedWindowController.swift rename to Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift index f5ed2c9e8..82fcf2301 100644 --- a/Mac/MainWindow/AddFeed/AddFeedWindowController.swift +++ b/Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift @@ -12,15 +12,7 @@ import RSTree import Articles import Account -protocol AddFeedWindowControllerDelegate: class { - - // userEnteredURL will have already been validated and normalized. - func addFeedWindowController(_: AddFeedWindowController, userEnteredURL: URL, userEnteredTitle: String?, container: Container) - - func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController) -} - -class AddFeedWindowController : NSWindowController { +class AddWebFeedWindowController : NSWindowController, AddFeedWindowController { @IBOutlet var urlTextField: NSTextField! @IBOutlet var nameTextField: NSTextField! @@ -46,7 +38,7 @@ class AddFeedWindowController : NSWindowController { var hostWindow: NSWindow! convenience init(urlString: String?, name: String?, account: Account?, folder: Folder?, folderTreeController: TreeController, delegate: AddFeedWindowControllerDelegate?) { - self.init(windowNibName: NSNib.Name("AddFeedSheet")) + self.init(windowNibName: NSNib.Name("AddWebFeedSheet")) self.urlString = urlString self.initialName = name self.initialAccount = account @@ -127,7 +119,7 @@ class AddFeedWindowController : NSWindowController { } } -private extension AddFeedWindowController { +private extension AddWebFeedWindowController { private func updateUI() { addButton.isEnabled = urlTextField.stringValue.mayBeURL diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index a294e88e3..6146eecc8 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -186,6 +186,8 @@ 519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; }; 519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */; }; 519ED47C24488C6F007F8E94 /* ExtensionInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47B24488C6F007F8E94 /* ExtensionInspectorViewController.swift */; }; + 51A052CE244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */; }; + 51A052CF244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */; }; 51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */; }; 51A1699A235E10D700EB091F /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51A16990235E10D600EB091F /* Settings.storyboard */; }; 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */; }; @@ -466,7 +468,7 @@ 65ED4027235DEF6C0081F399 /* UnreadIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */; }; 65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; 65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; - 65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */; }; + 65ED402A235DEF6C0081F399 /* AddWebFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */; }; 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */; }; 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A976A1ED9EBC8007D329B /* TimelineTableView.swift */; }; 65ED402D235DEF6C0081F399 /* DetailStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */; }; @@ -528,7 +530,7 @@ 65ED406B235DEF6C0081F399 /* CrashReporterWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84BAE64821CEDAF20046DB56 /* CrashReporterWindow.xib */; }; 65ED406C235DEF6C0081F399 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8922629E8F00D921D6 /* Credits.rtf */; }; 65ED406D235DEF6C0081F399 /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84BBB12B20142A4700F054F5 /* Inspector.storyboard */; }; - 65ED406E235DEF6C0081F399 /* AddFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */; }; + 65ED406E235DEF6C0081F399 /* AddWebFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */; }; 65ED4071235DEF6C0081F399 /* RSWeb.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9F20DD8D0500CA8CF5 /* RSWeb.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED4072235DEF6C0081F399 /* RSDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC020DD8E0C00CA8CF5 /* RSDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED4073235DEF6C0081F399 /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -593,7 +595,7 @@ 847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; }; 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 848362FC2262A30800DA1D35 /* styleSheet.css */; }; 848362FF2262A30E00DA1D35 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; - 848363022262A3BD00DA1D35 /* AddFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */; }; + 848363022262A3BD00DA1D35 /* AddWebFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */; }; 848363052262A3CC00DA1D35 /* AddFolderSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363032262A3CC00DA1D35 /* AddFolderSheet.xib */; }; 848363082262A3DD00DA1D35 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 848363062262A3DD00DA1D35 /* Main.storyboard */; }; 8483630B2262A3F000DA1D35 /* RenameSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363092262A3F000DA1D35 /* RenameSheet.xib */; }; @@ -603,7 +605,7 @@ 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; }; 849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; }; - 849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */; }; + 849A97541ED9EAC0007D329B /* AddWebFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */; }; 849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; 849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; 849A975E1ED9EB72007D329B /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A975D1ED9EB72007D329B /* MainWindowController.swift */; }; @@ -1483,6 +1485,7 @@ 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionPointViewController.swift; sourceTree = ""; }; 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointViewController.swift; sourceTree = ""; }; 519ED47B24488C6F007F8E94 /* ExtensionInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInspectorViewController.swift; sourceTree = ""; }; + 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedWIndowController.swift; path = AddFeed/AddFeedWIndowController.swift; sourceTree = ""; }; 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAccountViewController.swift; sourceTree = ""; }; 51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountInspectorViewController.swift; sourceTree = ""; }; @@ -1640,7 +1643,7 @@ 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = ""; }; 848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = ""; }; - 848363012262A3BC00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddFeedSheet.xib; sourceTree = SOURCE_ROOT; }; + 848363012262A3BC00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddWebFeedSheet.xib; sourceTree = SOURCE_ROOT; }; 848363042262A3CC00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddFolderSheet.xib; sourceTree = SOURCE_ROOT; }; 848363072262A3DD00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 8483630A2262A3F000DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/RenameSheet.xib; sourceTree = SOURCE_ROOT; }; @@ -1650,7 +1653,7 @@ 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = ""; }; 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = ""; }; 849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = ""; }; - 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedWindowController.swift; path = AddFeed/AddFeedWindowController.swift; sourceTree = ""; }; + 849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddWebFeedWindowController.swift; path = AddFeed/AddWebFeedWindowController.swift; sourceTree = ""; }; 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleUtilities.swift; sourceTree = ""; }; 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultFeedsImporter.swift; sourceTree = ""; }; 849A975D1ED9EB72007D329B /* MainWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = ""; }; @@ -2515,9 +2518,10 @@ 849A97551ED9EAC3007D329B /* Add Feed */ = { isa = PBXGroup; children = ( - 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */, + 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */, 849A97511ED9EAC0007D329B /* AddFeedController.swift */, - 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */, + 848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */, + 849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */, 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */, ); name = "Add Feed"; @@ -3252,36 +3256,36 @@ TargetAttributes = { 51314636235A7BBE00387FDC = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = SHJK2V3AJG; LastSwiftMigration = 1120; ProvisioningStyle = Automatic; }; 513C5CE5232571C2003D4054 = { CreatedOnToolsVersion = 11.0; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 518B2ED12351B3DD00400001 = { CreatedOnToolsVersion = 11.2; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; TestTargetID = 840D617B2029031C009BC708; }; 6581C73220CED60000F4AD34 = { - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 65ED3FA2235DEF6C0081F399 = { - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 65ED4090235DEF770081F399 = { - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -3291,7 +3295,7 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { @@ -3301,7 +3305,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = M72QZ9W58G; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -3774,7 +3778,7 @@ 65ED406B235DEF6C0081F399 /* CrashReporterWindow.xib in Resources */, 65ED406C235DEF6C0081F399 /* Credits.rtf in Resources */, 65ED406D235DEF6C0081F399 /* Inspector.storyboard in Resources */, - 65ED406E235DEF6C0081F399 /* AddFeedSheet.xib in Resources */, + 65ED406E235DEF6C0081F399 /* AddWebFeedSheet.xib in Resources */, B27EEBFA244D15F3000932E6 /* shared.css in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3870,7 +3874,7 @@ 84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */, 84C9FC8E22629E8F00D921D6 /* Credits.rtf in Resources */, 84BBB12D20142A4700F054F5 /* Inspector.storyboard in Resources */, - 848363022262A3BD00DA1D35 /* AddFeedSheet.xib in Resources */, + 848363022262A3BD00DA1D35 /* AddWebFeedSheet.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4078,6 +4082,7 @@ 65ED3FD6235DEF6C0081F399 /* MarkStatusCommand.swift in Sources */, 65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */, 65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */, + 51A052CF244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */, 5103A9F824225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */, 65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */, 65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */, @@ -4166,7 +4171,7 @@ 51A9A5F22380DE520033AADF /* AddWebFeedDefaultContainer.swift in Sources */, 65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */, 65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */, - 65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */, + 65ED402A235DEF6C0081F399 /* AddWebFeedWindowController.swift in Sources */, 515A5108243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */, 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */, 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */, @@ -4503,11 +4508,12 @@ 849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */, 51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */, 84B99C9D1FAE83C600ECDEDB /* DeleteCommand.swift in Sources */, - 849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */, + 849A97541ED9EAC0007D329B /* AddWebFeedWindowController.swift in Sources */, 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */, 849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */, 84D52E951FE588BB00D14F5B /* DetailStatusBarView.swift in Sources */, D5E4CC64202C1AC1009B4FFC /* MainWindowController+Scriptability.swift in Sources */, + 51A052CE244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */, 84C9FC7922629E1200D921D6 /* PreferencesWindowController.swift in Sources */, 84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */, 51FA73A42332BE110090D516 /* ArticleExtractor.swift in Sources */, @@ -4737,12 +4743,12 @@ name = SafariExtensionViewController.xib; sourceTree = ""; }; - 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */ = { + 848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */ = { isa = PBXVariantGroup; children = ( 848363012262A3BC00DA1D35 /* Base */, ); - name = AddFeedSheet.xib; + name = AddWebFeedSheet.xib; sourceTree = ""; }; 848363032262A3CC00DA1D35 /* AddFolderSheet.xib */ = { From 0ff0c87932cc8c018d7319aa552d4b0e9de84bfd Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 21 Apr 2020 21:25:45 -0500 Subject: [PATCH 085/108] Add the new twitter add dialog --- Mac/AppDelegate.swift | 23 ++- Mac/Base.lproj/AddTwitterFeedSheet.xib | 192 ++++++++++++++++++ Mac/Base.lproj/AddWebFeedSheet.xib | 2 +- Mac/Base.lproj/Main.storyboard | 12 +- Mac/Base.lproj/MainWindow.storyboard | 13 +- .../AddFeed/AddFeedController.swift | 35 ++-- .../AddFeed/AddFeedWIndowController.swift | 9 +- .../AddTwitterFeedWindowController.swift | 116 +++++++++++ .../Sidebar/SidebarOutlineDataSource.swift | 4 +- ...idebarViewController+ContextualMenus.swift | 2 +- .../AppDelegate+Scriptability.swift | 2 +- NetNewsWire.xcodeproj/project.pbxproj | 22 +- 12 files changed, 387 insertions(+), 45 deletions(-) create mode 100644 Mac/Base.lproj/AddTwitterFeedSheet.xib create mode 100644 Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 4de0e011d..e8bc956b3 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -135,10 +135,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, addFolderWindowController!.runSheetOnWindow(window) } - func showAddFeedSheetOnWindow(_ window: NSWindow, urlString: String?, name: String?, account: Account?, folder: Folder?) { - + func showAddWebFeedSheetOnWindow(_ window: NSWindow, urlString: String?, name: String?, account: Account?, folder: Folder?) { addFeedController = AddFeedController(hostWindow: window) - addFeedController?.showAddFeedSheet(urlString, name, account, folder) + addFeedController?.showAddFeedSheet(.webFeed, urlString, name, account, folder) } // MARK: - NSApplicationDelegate @@ -401,7 +400,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, if item.action == #selector(sortByNewestArticleOnTop(_:)) || item.action == #selector(sortByOldestArticleOnTop(_:)) { return mainWindowController?.isOpen ?? false } - if item.action == #selector(showAddFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) { + if item.action == #selector(showAddWebFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) { return !isDisplayingSheet && !AccountManager.shared.activeAccounts.isEmpty } #if !MAC_APP_STORE @@ -424,14 +423,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } // MARK: Add Feed - func addFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) { + func addWebFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) { createAndShowMainWindowIfNecessary() if mainWindowController!.isDisplayingSheet { return } - showAddFeedSheetOnWindow(mainWindowController!.window!, urlString: urlString, name: name, account: account, folder: folder) + showAddWebFeedSheetOnWindow(mainWindowController!.window!, urlString: urlString, name: name, account: account, folder: folder) } // MARK: - Dock Badge @@ -462,8 +461,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present) } - @IBAction func showAddFeedWindow(_ sender: Any?) { - addFeed(nil) + @IBAction func showAddWebFeedWindow(_ sender: Any?) { + addWebFeed(nil) + } + + @IBAction func showAddTwitterFeedWindow(_ sender: Any?) { + createAndShowMainWindowIfNecessary() + addFeedController = AddFeedController(hostWindow: mainWindowController!.window!) + addFeedController?.showAddFeedSheet(.twitterFeed) } @IBAction func showAddFolderWindow(_ sender: Any?) { @@ -536,7 +541,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, if AccountManager.shared.anyAccountHasFeedWithURL(appNewsURLString) { return } - addFeed(appNewsURLString, name: "NetNewsWire News") + addWebFeed(appNewsURLString, name: "NetNewsWire News") } @IBAction func openWebsite(_ sender: Any?) { diff --git a/Mac/Base.lproj/AddTwitterFeedSheet.xib b/Mac/Base.lproj/AddTwitterFeedSheet.xib new file mode 100644 index 000000000..c4c8cb500 --- /dev/null +++ b/Mac/Base.lproj/AddTwitterFeedSheet.xib @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mac/Base.lproj/AddWebFeedSheet.xib b/Mac/Base.lproj/AddWebFeedSheet.xib index e8e8660a5..812519436 100644 --- a/Mac/Base.lproj/AddWebFeedSheet.xib +++ b/Mac/Base.lproj/AddWebFeedSheet.xib @@ -16,7 +16,7 @@ - + diff --git a/Mac/Base.lproj/Main.storyboard b/Mac/Base.lproj/Main.storyboard index ce6a63050..5b2a00ebe 100644 --- a/Mac/Base.lproj/Main.storyboard +++ b/Mac/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -70,7 +70,13 @@ - + + + + + + + diff --git a/Mac/Base.lproj/MainWindow.storyboard b/Mac/Base.lproj/MainWindow.storyboard index eb5960822..57f6d53b4 100644 --- a/Mac/Base.lproj/MainWindow.storyboard +++ b/Mac/Base.lproj/MainWindow.storyboard @@ -1,7 +1,7 @@ - + - + @@ -59,7 +59,7 @@ - + @@ -318,7 +318,7 @@ - + @@ -329,7 +329,6 @@ - @@ -470,7 +469,7 @@ - + @@ -485,7 +484,7 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + @@ -171,22 +178,22 @@ DQ - - + - + + - + diff --git a/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift index 943b46eba..9ab8f8cc0 100644 --- a/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift +++ b/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift @@ -15,10 +15,13 @@ import Account class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowController { - @IBOutlet weak var accountPopupButton: NSPopUpButton! @IBOutlet weak var typePopupButton: NSPopUpButton! @IBOutlet weak var typeDescriptionLabel: NSTextField! + + @IBOutlet weak var accountLabel: NSTextField! + @IBOutlet weak var accountPopupButton: NSPopUpButton! @IBOutlet weak var screenSearchTextField: NSTextField! + @IBOutlet var nameTextField: NSTextField! @IBOutlet var addButton: NSButton! @IBOutlet var folderPopupButton: NSPopUpButton! @@ -54,9 +57,6 @@ class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowControll override func windowDidLoad() { - typeDescriptionLabel.stringValue = "Tweets from everyone you follow" - screenSearchTextField.isHidden = true - folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode) if let container = AddWebFeedDefaultContainer.defaultContainer { @@ -74,7 +74,12 @@ class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowControll // MARK: Actions - @IBAction func cancel(_ sender: Any?) { + @IBAction func selectedType(_ sender: Any) { + screenSearchTextField.stringValue = "" + updateUI() + } + + @IBAction func cancel(_ sender: Any?) { cancelSheet() } @@ -88,13 +93,11 @@ class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowControll delegate?.addFeedWindowController(self, userEnteredURL: url, userEnteredTitle: userEnteredTitle, container: container) } - // MARK: NSTextFieldDelegate +} - @objc func controlTextDidEndEditing(_ obj: Notification) { - updateUI() - } +extension AddTwitterFeedWindowController: NSTextFieldDelegate { - @objc func controlTextDidChange(_ obj: Notification) { + func controlTextDidChange(_ obj: Notification) { updateUI() } @@ -103,7 +106,59 @@ class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowControll private extension AddTwitterFeedWindowController { private func updateUI() { -// addButton.isEnabled = urlTextField.stringValue.mayBeURL + + switch typePopupButton.selectedItem?.tag ?? 0 { + case 0: + + accountLabel.isHidden = false + accountPopupButton.isHidden = false + typeDescriptionLabel.stringValue = NSLocalizedString("Tweets from everyone you follow", comment: "Home Timeline") + screenSearchTextField.isHidden = true + addButton.isEnabled = true + + case 1: + + accountLabel.isHidden = false + accountPopupButton.isHidden = false + typeDescriptionLabel.stringValue = NSLocalizedString("Tweets mentioning you", comment: "Mentions") + screenSearchTextField.isHidden = true + addButton.isEnabled = true + + case 2: + + accountLabel.isHidden = true + accountPopupButton.isHidden = true + + var screenName = screenSearchTextField.stringValue + if !screenName.isEmpty && !screenName.starts(with: "@") { + screenName = "@\(screenName)" + typeDescriptionLabel.stringValue = NSLocalizedString("Tweets from \(screenName)", comment: "Home Timeline") + } else { + typeDescriptionLabel.stringValue = "" + } + + screenSearchTextField.placeholderString = NSLocalizedString("@name", comment: "@name") + screenSearchTextField.isHidden = false + addButton.isEnabled = !screenSearchTextField.stringValue.isEmpty + + default: + + accountLabel.isHidden = true + accountPopupButton.isHidden = true + + if !screenSearchTextField.stringValue.isEmpty { + typeDescriptionLabel.stringValue = NSLocalizedString("Tweets that contain \(screenSearchTextField.stringValue)", comment: "Home Timeline") + } else { + typeDescriptionLabel.stringValue = "" + } + + screenSearchTextField.placeholderString = nil + screenSearchTextField.isHidden = false + addButton.isEnabled = !screenSearchTextField.stringValue.isEmpty + + } + + addButton.isEnabled = !screenSearchTextField.stringValue.isEmpty } func cancelSheet() { From c37bbe2fbbf61fe340b14d248da88e789a1bbc93 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 22 Apr 2020 07:36:22 -0500 Subject: [PATCH 087/108] Fix add button UI rules bug --- Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift index 9ab8f8cc0..76e910c00 100644 --- a/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift +++ b/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift @@ -158,7 +158,6 @@ private extension AddTwitterFeedWindowController { } - addButton.isEnabled = !screenSearchTextField.stringValue.isEmpty } func cancelSheet() { From 0369d976fa3d9267733a91ec1ba4fa866b8e613e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 22 Apr 2020 11:25:49 -0500 Subject: [PATCH 088/108] Add twitter URL creation logic --- .../Twitter/TwitterFeedProvider.swift | 56 +++++++++++++++++-- .../AddTwitterFeedWindowController.swift | 29 ++++++++-- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 7dde373fa..261ff90fe 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -11,10 +11,25 @@ import Secrets import OAuthSwift import RSParser -// TODO: Beef up error handling... -public enum TwitterFeedProviderError: Error { +public enum TwitterFeedProviderError: LocalizedError { case screenNameNotFound case unknown + + public var localizedDescription: String { + switch self { + case .screenNameNotFound: + return NSLocalizedString("Unable to determine screen name.", comment: "Screen name") + case .unknown: + return NSLocalizedString("An unknown Twitter Feed Provider error has occurred.", comment: "Screen name") + } + } +} + +public enum TwitterFeedType: Int { + case homeTimeline = 0 + case mentions = 1 + case screenName = 2 + case search = 3 } public struct TwitterFeedProvider: FeedProvider { @@ -108,7 +123,7 @@ public struct TwitterFeedProvider: FeedProvider { public func assignName(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { switch urlComponents.path { - case "/", "/home": + case "", "/", "/home": let name = NSLocalizedString("Twitter Timeline", comment: "Twitter Timeline") completion(.success(name)) @@ -160,7 +175,7 @@ public struct TwitterFeedProvider: FeedProvider { var isSearch = false switch urlComponents.path { - case "/", "/home": + case "", "/", "/home": parameters["count"] = 100 api = "statuses/home_timeline.json" case "/notifications/mentions": @@ -199,6 +214,39 @@ public struct TwitterFeedProvider: FeedProvider { } } + public static func buildURL(_ type: TwitterFeedType, username: String?, screenName: String?, searchField: String?) -> URL? { + var components = URLComponents() + components.scheme = "https" + components.host = "twitter.com" + + switch type { + case .homeTimeline: + guard let username = username else { + return nil + } + components.user = username + case .mentions: + guard let username = username else { + return nil + } + components.user = username + components.path = "/notifications/mentions" + case .screenName: + guard let screenName = screenName else { + return nil + } + components.path = "/\(screenName)" + case .search: + guard let searchField = searchField else { + return nil + } + components.path = "/search" + components.queryItems = [URLQueryItem(name: "q", value: searchField)] + } + + return components.url + } + } // MARK: OAuth1SwiftProvider diff --git a/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift index 76e910c00..72189485f 100644 --- a/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift +++ b/Mac/MainWindow/AddFeed/AddTwitterFeedWindowController.swift @@ -13,7 +13,6 @@ import Articles import Account class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowController { - @IBOutlet weak var typePopupButton: NSPopUpButton! @IBOutlet weak var typeDescriptionLabel: NSTextField! @@ -33,6 +32,15 @@ class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowControll private weak var delegate: AddFeedWindowControllerDelegate? private var folderTreeController: TreeController! + private var userEnteredScreenSearch: String? { + var s = screenSearchTextField.stringValue + s = s.collapsingWhitespace + if s.isEmpty { + return nil + } + return s + } + private var userEnteredTitle: String? { var s = nameTextField.stringValue s = s.collapsingWhitespace @@ -57,6 +65,16 @@ class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowControll override func windowDidLoad() { + let accountMenu = NSMenu() + for feedProvider in ExtensionPointManager.shared.activeFeedProviders { + if let twitterFeedProvider = feedProvider as? TwitterFeedProvider { + let accountMenuItem = NSMenuItem() + accountMenuItem.title = "@\(twitterFeedProvider.screenName)" + accountMenu.addItem(accountMenuItem) + } + } + accountPopupButton.menu = accountMenu + folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode) if let container = AddWebFeedDefaultContainer.defaultContainer { @@ -84,9 +102,12 @@ class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowControll } @IBAction func addFeed(_ sender: Any?) { - - // TODO: Build the URL... - let url = URL(string: "https://twitter.com")! + guard let type = TwitterFeedType(rawValue: typePopupButton.selectedItem?.tag ?? 0), + let atUsername = accountPopupButton.selectedItem?.title else { return } + + let username = String(atUsername[atUsername.index(atUsername.startIndex, offsetBy: 1).. Date: Wed, 22 Apr 2020 11:36:07 -0500 Subject: [PATCH 089/108] Disable Add Twitter Feed menu item if no Twitter extension points are available --- Mac/AppDelegate.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index e8bc956b3..80f68a1b9 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -403,6 +403,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, if item.action == #selector(showAddWebFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) { return !isDisplayingSheet && !AccountManager.shared.activeAccounts.isEmpty } + if item.action == #selector(showAddTwitterFeedWindow(_:)) { + return ExtensionPointManager.shared.activeExtensionPoints.values.contains(where: { $0 is TwitterFeedProvider }) + } #if !MAC_APP_STORE if item.action == #selector(toggleWebInspectorEnabled(_:)) { (item as! NSMenuItem).state = AppDefaults.webInspectorEnabled ? .on : .off From 7b37f51f5934f3b382d59b2d2ed6454e812c3aab Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 22 Apr 2020 11:41:40 -0500 Subject: [PATCH 090/108] Disallow Twitter Feed Provider ability if the feed names explicitly don't match --- .../FeedProvider/Twitter/TwitterFeedProvider.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 261ff90fe..183819aaa 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -93,8 +93,15 @@ public struct TwitterFeedProvider: FeedProvider { return .none } - let bestUserName = username != nil ? username : urlComponents.user - if bestUserName == screenName { + if let username = username { + if username == screenName { + return .owner + } else { + return .none + } + } + + if let user = urlComponents.user, user == screenName { return .owner } From 0ac5a6dbcab8f0b0b0179454f531a1fc960692cf Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 22 Apr 2020 14:16:50 -0500 Subject: [PATCH 091/108] Disable Twitter Extension Point for Developer Build. --- Shared/ExtensionPoints/ExtensionPoint.swift | 1 + Shared/ExtensionPoints/ExtensionPointManager.swift | 10 ++++++---- Shared/ExtensionPoints/SendToMarsEditCommand.swift | 1 + Shared/ExtensionPoints/SendToMicroBlogCommand.swift | 3 ++- .../TwitterFeedProvider-Extensions.swift | 1 + 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Shared/ExtensionPoints/ExtensionPoint.swift b/Shared/ExtensionPoints/ExtensionPoint.swift index 86a84557d..148b48485 100644 --- a/Shared/ExtensionPoints/ExtensionPoint.swift +++ b/Shared/ExtensionPoints/ExtensionPoint.swift @@ -16,6 +16,7 @@ import RSCore protocol ExtensionPoint { static var isSinglton: Bool { get } + static var isDeveloperBuildRestricted: Bool { get } static var title: String { get } static var templateImage: RSImage { get } static var description: NSAttributedString { get } diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift index 723d3180c..aecb20ba3 100644 --- a/Shared/ExtensionPoints/ExtensionPointManager.swift +++ b/Shared/ExtensionPoints/ExtensionPointManager.swift @@ -26,12 +26,14 @@ final class ExtensionPointManager: FeedProviderManagerDelegate { let activeExtensionPointTypes = activeExtensionPoints.keys.compactMap({ ObjectIdentifier($0.extensionPointType) }) var available = [ExtensionPoint.Type]() for possibleExtensionPointType in possibleExtensionPointTypes { - if possibleExtensionPointType.isSinglton { - if !activeExtensionPointTypes.contains(ObjectIdentifier(possibleExtensionPointType)) { + if !(AppDefaults.isDeveloperBuild && possibleExtensionPointType.isDeveloperBuildRestricted) { + if possibleExtensionPointType.isSinglton { + if !activeExtensionPointTypes.contains(ObjectIdentifier(possibleExtensionPointType)) { + available.append(possibleExtensionPointType) + } + } else { available.append(possibleExtensionPointType) } - } else { - available.append(possibleExtensionPointType) } } diff --git a/Shared/ExtensionPoints/SendToMarsEditCommand.swift b/Shared/ExtensionPoints/SendToMarsEditCommand.swift index 3208ed8aa..7394bebea 100644 --- a/Shared/ExtensionPoints/SendToMarsEditCommand.swift +++ b/Shared/ExtensionPoints/SendToMarsEditCommand.swift @@ -13,6 +13,7 @@ import Articles final class SendToMarsEditCommand: ExtensionPoint, SendToCommand { static var isSinglton = true + static var isDeveloperBuildRestricted = false static var title = NSLocalizedString("MarsEdit", comment: "MarsEdit") static var templateImage = AppAssets.extensionPointMarsEdit static var description: NSAttributedString = { diff --git a/Shared/ExtensionPoints/SendToMicroBlogCommand.swift b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift index b6149c58d..36f8326c7 100644 --- a/Shared/ExtensionPoints/SendToMicroBlogCommand.swift +++ b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift @@ -14,7 +14,8 @@ import RSCore final class SendToMicroBlogCommand: ExtensionPoint, SendToCommand { - static var isSinglton: Bool = true + static var isSinglton = true + static var isDeveloperBuildRestricted = false static var title: String = NSLocalizedString("Micro.blog", comment: "Micro.blog") static var templateImage = AppAssets.extensionPointMicroblog static var description: NSAttributedString = { diff --git a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift index 92bd77e20..50f3383f0 100644 --- a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift +++ b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift @@ -12,6 +12,7 @@ import Account extension TwitterFeedProvider: ExtensionPoint { static var isSinglton = false + static var isDeveloperBuildRestricted = true static var title = NSLocalizedString("Twitter", comment: "Twitter") static var templateImage = AppAssets.extensionPointTwitter static var description: NSAttributedString = { From 3eecae95e15488b398f3d2a586cdbe75b5c125a2 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 22 Apr 2020 14:21:38 -0500 Subject: [PATCH 092/108] Remove commented/debug code. --- .../Account/FeedProvider/Twitter/TwitterFeedProvider.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 183819aaa..7c3cf4cd0 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -323,16 +323,12 @@ private extension TwitterFeedProvider { client.get(url, parameters: expandedParameters) { result in switch result { case .success(let response): + let decoder = JSONDecoder() let dateFormatter = DateFormatter() dateFormatter.dateFormat = Self.dateFormat decoder.dateDecodingStrategy = .formatted(dateFormatter) -// let jsonString = String(data: response.data, encoding: .utf8) -// let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("twitter.json") -// print("******** writing to: \(url.path)") -// try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) - do { let tweets: [TwitterStatus] if isSearch { @@ -349,6 +345,7 @@ private extension TwitterFeedProvider { } catch { completion(.failure(error)) } + case .failure(let error): completion(.failure(error)) } From 14156e377479808c952bd2e776b183649400db5c Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 22 Apr 2020 17:42:20 -0500 Subject: [PATCH 093/108] Sync the WebFeed username so that Feed Provider created feeds stay locked to their accounts. --- .../CloudKit/CloudKitAccountDelegate.swift | 6 ++--- .../CloudKit/CloudKitAccountZone.swift | 6 ++++- .../CloudKitAccountZoneDelegate.swift | 27 ++++++++++--------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift index a7c2be710..f036c1efb 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift @@ -292,7 +292,7 @@ final class CloudKitAccountDelegate: AccountDelegate { func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) - accountZone.createWebFeed(url: feed.url, editedName: feed.editedName, container: container) { result in + accountZone.createWebFeed(url: feed.url, editedName: feed.editedName, username: feed.username, container: container) { result in self.refreshProgress.completeTask() switch result { case .success(let externalID): @@ -615,7 +615,7 @@ private extension CloudKitAccountDelegate { return } - self.accountZone.createWebFeed(url: newURLString, editedName: editedName, container: container) { result in + self.accountZone.createWebFeed(url: newURLString, editedName: editedName, username: urlComponents.user, container: container) { result in self.refreshProgress.completeTask() switch result { @@ -674,7 +674,7 @@ private extension CloudKitAccountDelegate { return } - self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: editedName, container: container) { result in + self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: editedName, username: nil, container: container) { result in self.refreshProgress.completeTask() switch result { diff --git a/Frameworks/Account/CloudKit/CloudKitAccountZone.swift b/Frameworks/Account/CloudKit/CloudKitAccountZone.swift index 69ff6bef2..b812a2eec 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountZone.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountZone.swift @@ -29,6 +29,7 @@ final class CloudKitAccountZone: CloudKitZone { struct Fields { static let url = "url" static let editedName = "editedName" + static let username = "username" static let containerExternalIDs = "containerExternalIDs" } } @@ -81,13 +82,16 @@ final class CloudKitAccountZone: CloudKitZone { } /// Persist a web feed record to iCloud and return the external key - func createWebFeed(url: String, editedName: String?, container: Container, completion: @escaping (Result) -> Void) { + func createWebFeed(url: String, editedName: String?, username: String?, container: Container, completion: @escaping (Result) -> Void) { let recordID = CKRecord.ID(recordName: url.md5String, zoneID: Self.zoneID) let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID) record[CloudKitWebFeed.Fields.url] = url if let editedName = editedName { record[CloudKitWebFeed.Fields.editedName] = editedName } + if let username = username { + record[CloudKitWebFeed.Fields.username] = username + } guard let containerExternalID = container.externalID else { completion(.failure(CloudKitZoneError.invalidParameter)) diff --git a/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift index a4361e44c..8bd8d647a 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift @@ -13,7 +13,7 @@ import CloudKit class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { - private typealias UnclaimedWebFeed = (url: URL, editedName: String?, webFeedExternalID: String) + private typealias UnclaimedWebFeed = (url: URL, editedName: String?, username: String?, webFeedExternalID: String) private var unclaimedWebFeeds = [String: [UnclaimedWebFeed]]() private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") @@ -72,10 +72,11 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { } let editedName = record[CloudKitAccountZone.CloudKitWebFeed.Fields.editedName] as? String - + let username = record[CloudKitAccountZone.CloudKitWebFeed.Fields.username] as? String + if let webFeed = account.existingWebFeed(withExternalID: record.externalID) { - updateWebFeed(webFeed, editedName: editedName, containerExternalIDs: containerExternalIDs) + updateWebFeed(webFeed, editedName: editedName, username: username, containerExternalIDs: containerExternalIDs) completion() } else { @@ -84,11 +85,11 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { for containerExternalID in containerExternalIDs { group.enter() if let container = account.existingContainer(withExternalID: containerExternalID) { - createWebFeedIfNecessary(url: url, editedName: editedName, webFeedExternalID: record.externalID, container: container) { webFeed in + createWebFeedIfNecessary(url: url, editedName: editedName, username: username, webFeedExternalID: record.externalID, container: container) { webFeed in group.leave() } } else { - addUnclaimedWebFeed(url: url, editedName: editedName, webFeedExternalID: record.externalID, containerExternalID: containerExternalID) + addUnclaimedWebFeed(url: url, editedName: editedName, username: username, webFeedExternalID: record.externalID, containerExternalID: containerExternalID) group.leave() } } @@ -129,7 +130,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { for unclaimedWebFeed in unclaimedWebFeeds { group.enter() - createWebFeedIfNecessary(url: unclaimedWebFeed.url, editedName: unclaimedWebFeed.editedName, webFeedExternalID: unclaimedWebFeed.webFeedExternalID, container: folder) { webFeed in + createWebFeedIfNecessary(url: unclaimedWebFeed.url, editedName: unclaimedWebFeed.editedName, username: unclaimedWebFeed.username, webFeedExternalID: unclaimedWebFeed.webFeedExternalID, container: folder) { webFeed in group.leave() } } @@ -156,9 +157,10 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { private extension CloudKitAcountZoneDelegate { - func updateWebFeed(_ webFeed: WebFeed, editedName: String?, containerExternalIDs: [String]) { + func updateWebFeed(_ webFeed: WebFeed, editedName: String?, username: String?, containerExternalIDs: [String]) { guard let account = account else { return } webFeed.editedName = editedName + webFeed.username = username let existingContainers = account.existingContainers(withWebFeed: webFeed) let existingContainerExternalIds = existingContainers.compactMap { $0.externalID } @@ -179,7 +181,7 @@ private extension CloudKitAcountZoneDelegate { } } - func createWebFeedIfNecessary(url: URL, editedName: String?, webFeedExternalID: String, container: Container, completion: @escaping (WebFeed) -> Void) { + func createWebFeedIfNecessary(url: URL, editedName: String?, username: String?, webFeedExternalID: String, container: Container, completion: @escaping (WebFeed) -> Void) { guard let account = account, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return } if let webFeed = account.existingWebFeed(withExternalID: webFeedExternalID) { @@ -189,9 +191,10 @@ private extension CloudKitAcountZoneDelegate { let webFeed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) webFeed.editedName = editedName + webFeed.username = username webFeed.externalID = webFeedExternalID - if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { + if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: username) { refreshProgress?.addToNumberOfTasksAndRemaining(2) feedProvider.assignName(urlComponents) { result in @@ -239,13 +242,13 @@ private extension CloudKitAcountZoneDelegate { } - func addUnclaimedWebFeed(url: URL, editedName: String?, webFeedExternalID: String, containerExternalID: String) { + func addUnclaimedWebFeed(url: URL, editedName: String?, username: String?, webFeedExternalID: String, containerExternalID: String) { if var unclaimedWebFeeds = self.unclaimedWebFeeds[containerExternalID] { - unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, editedName: editedName, webFeedExternalID: webFeedExternalID)) + unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, editedName: editedName, username: username, webFeedExternalID: webFeedExternalID)) self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds } else { var unclaimedWebFeeds = [UnclaimedWebFeed]() - unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, editedName: editedName, webFeedExternalID: webFeedExternalID)) + unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, editedName: editedName, username: username, webFeedExternalID: webFeedExternalID)) self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds } } From 62e91cfd2632fd0969bb3bb8863e500c5d9d2de5 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 22 Apr 2020 22:13:55 -0500 Subject: [PATCH 094/108] Change to never link out to Twitter for photos that are inlined. --- .../FeedProvider/Twitter/TwitterExtendedMedia.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift index 442f4043c..91a3bdf9e 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterExtendedMedia.swift @@ -35,13 +35,7 @@ struct TwitterExtendedMedia: Codable { switch type { case "photo": - if let url = url { - html += "" - html += renderPhotoAsHTML() - html += "" - } else { - html += renderPhotoAsHTML() - } + html += renderPhotoAsHTML() case "video", "animated_gif": html += renderVideoAsHTML() default: From e11cf2ccf58c8525e5a47fbc2ed264c9b4e9c726 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 23 Apr 2020 04:00:51 -0500 Subject: [PATCH 095/108] Rename to use common name for table view cell --- NetNewsWire.xcodeproj/project.pbxproj | 10 +++++----- iOS/Add/Add.storyboard | 8 ++++---- ...TableViewCell.swift => AddComboTableViewCell.swift} | 4 ++-- iOS/Add/AddWebFeedFolderViewController.swift | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) rename iOS/Add/{AddWebFeedFolderTableViewCell.swift => AddComboTableViewCell.swift} (85%) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index aae3816c8..0f667eca7 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -314,7 +314,7 @@ 51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; }; 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB3C229AB08300645299 /* ErrorHandler.swift */; }; 51E43962238037C400015C31 /* AddWebFeedFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */; }; - 51E4398023805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */; }; + 51E4398023805EBC00015C31 /* AddComboTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397F23805EBC00015C31 /* AddComboTableViewCell.swift */; }; 51E4DAED2425F6940091EB5B /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4DAEC2425F6940091EB5B /* CloudKit.framework */; }; 51E4DB082425F9EB0091EB5B /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4DB072425F9EB0091EB5B /* CloudKit.framework */; }; 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; @@ -1548,7 +1548,7 @@ 51E3EB32229AB02C00645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E3EB3C229AB08300645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedFolderViewController.swift; sourceTree = ""; }; - 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedFolderTableViewCell.swift; sourceTree = ""; }; + 51E4397F23805EBC00015C31 /* AddComboTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddComboTableViewCell.swift; sourceTree = ""; }; 51E4DAEC2425F6940091EB5B /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 51E4DB072425F9EB0091EB5B /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; }; 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleStatusSyncTimer.swift; sourceTree = ""; }; @@ -2261,14 +2261,14 @@ isa = PBXGroup; children = ( 51C452822265093600C03939 /* Add.storyboard */, + 51E4397F23805EBC00015C31 /* AddComboTableViewCell.swift */, 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */, 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */, 51C4528B2265095F00C03939 /* AddFolderViewController.swift */, - 51C452842265093600C03939 /* AddWebFeedViewController.swift */, 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */, 51E36E70239D6610006F47A5 /* AddWebFeedSelectFolderTableViewCell.swift */, 51E36E8B239D6765006F47A5 /* AddWebFeedSelectFolderTableViewCell.xib */, - 51E4397F23805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift */, + 51C452842265093600C03939 /* AddWebFeedViewController.swift */, ); path = Add; sourceTree = ""; @@ -4301,7 +4301,7 @@ 51C45258226508CF00C03939 /* AppAssets.swift in Sources */, 51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */, 51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */, - 51E4398023805EBC00015C31 /* AddWebFeedFolderTableViewCell.swift in Sources */, + 51E4398023805EBC00015C31 /* AddComboTableViewCell.swift in Sources */, 51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */, 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */, 51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */, diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 54d418e17..a8fd20ddf 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -1,8 +1,8 @@ - + - + @@ -140,7 +140,7 @@ - + @@ -175,7 +175,7 @@ - + diff --git a/iOS/Add/AddWebFeedFolderTableViewCell.swift b/iOS/Add/AddComboTableViewCell.swift similarity index 85% rename from iOS/Add/AddWebFeedFolderTableViewCell.swift rename to iOS/Add/AddComboTableViewCell.swift index 4023057f1..83ae83e88 100644 --- a/iOS/Add/AddWebFeedFolderTableViewCell.swift +++ b/iOS/Add/AddComboTableViewCell.swift @@ -1,5 +1,5 @@ // -// AddWebFeedFolderTableViewCell.swift +// AddComboTableViewCell.swift // NetNewsWire // // Created by Maurice Parker on 11/16/19. @@ -8,7 +8,7 @@ import UIKit -class AddWebFeedFolderTableViewCell: VibrantTableViewCell { +class AddComboTableViewCell: VibrantTableViewCell { @IBOutlet weak var icon: UIImageView! @IBOutlet weak var label: UILabel! diff --git a/iOS/Add/AddWebFeedFolderViewController.swift b/iOS/Add/AddWebFeedFolderViewController.swift index a97aee90f..50c7631f3 100644 --- a/iOS/Add/AddWebFeedFolderViewController.swift +++ b/iOS/Add/AddWebFeedFolderViewController.swift @@ -44,11 +44,11 @@ class AddWebFeedFolderViewController: UITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let container = containers[indexPath.row] - let cell: AddWebFeedFolderTableViewCell = { + let cell: AddComboTableViewCell = { if container is Account { - return tableView.dequeueReusableCell(withIdentifier: "AccountCell", for: indexPath) as! AddWebFeedFolderTableViewCell + return tableView.dequeueReusableCell(withIdentifier: "AccountCell", for: indexPath) as! AddComboTableViewCell } else { - return tableView.dequeueReusableCell(withIdentifier: "FolderCell", for: indexPath) as! AddWebFeedFolderTableViewCell + return tableView.dequeueReusableCell(withIdentifier: "FolderCell", for: indexPath) as! AddComboTableViewCell } }() From 4461cf83cbeaf02acc75e20a07ed58e4cfab69b1 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 23 Apr 2020 04:44:26 -0500 Subject: [PATCH 096/108] Add select URL Builder dialog --- NetNewsWire.xcodeproj/project.pbxproj | 8 ++ iOS/Add/Add.storyboard | 95 ++++++++++++++++++- iOS/Add/AddWebFeedFolderViewController.swift | 2 +- iOS/Add/AddWebFeedViewController.swift | 6 ++ iOS/Add/SelectComboTableViewCell.swift | 26 +++++ .../SelectURLBuilderTableViewController.swift | 44 +++++++++ 6 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 iOS/Add/SelectComboTableViewCell.swift create mode 100644 iOS/Add/SelectURLBuilderTableViewController.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 0f667eca7..7cbe827ca 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 3B826DCE2385C89600FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; }; 49F40DF82335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; 49F40DF92335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; + 510289AA2451967500426DDF /* SelectURLBuilderTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510289A92451967500426DDF /* SelectURLBuilderTableViewController.swift */; }; + 510289CD24519A1D00426DDF /* SelectComboTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510289CC24519A1D00426DDF /* SelectComboTableViewCell.swift */; }; 5102FD83244009F000534F17 /* Secrets.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5102FD7B244008A700534F17 /* Secrets.framework */; }; 5102FD84244009F000534F17 /* Secrets.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5102FD7B244008A700534F17 /* Secrets.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5102FD9B244009FA00534F17 /* Secrets.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5102FD7B244008A700534F17 /* Secrets.framework */; }; @@ -1382,6 +1384,8 @@ 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = ""; }; 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsFeedWranglerWindowController.swift; sourceTree = ""; }; 49F40DEF2335B71000552BF4 /* newsfoot.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newsfoot.js; sourceTree = ""; }; + 510289A92451967500426DDF /* SelectURLBuilderTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectURLBuilderTableViewController.swift; sourceTree = ""; }; + 510289CC24519A1D00426DDF /* SelectComboTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectComboTableViewCell.swift; sourceTree = ""; }; 5102FD72244008A700534F17 /* Secrets.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Secrets.xcodeproj; path = Frameworks/Secrets/Secrets.xcodeproj; sourceTree = SOURCE_ROOT; }; 5103A9972421643300410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; }; 5103A9B324216A4200410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; }; @@ -2269,6 +2273,8 @@ 51E36E70239D6610006F47A5 /* AddWebFeedSelectFolderTableViewCell.swift */, 51E36E8B239D6765006F47A5 /* AddWebFeedSelectFolderTableViewCell.xib */, 51C452842265093600C03939 /* AddWebFeedViewController.swift */, + 510289A92451967500426DDF /* SelectURLBuilderTableViewController.swift */, + 510289CC24519A1D00426DDF /* SelectComboTableViewCell.swift */, ); path = Add; sourceTree = ""; @@ -4262,6 +4268,7 @@ 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */, 51A1699C235E10D700EB091F /* AddAccountViewController.swift in Sources */, 51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */, + 510289CD24519A1D00426DDF /* SelectComboTableViewCell.swift in Sources */, 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */, 51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */, 51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */, @@ -4293,6 +4300,7 @@ 51B5C87B23F2317700032075 /* ExtensionFeedAddRequest.swift in Sources */, 51627A93238A3836007B3B4B /* CroppingPreviewParameters.swift in Sources */, 512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */, + 510289AA2451967500426DDF /* SelectURLBuilderTableViewController.swift in Sources */, 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */, 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */, 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */, diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index a8fd20ddf..3065861c5 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -26,16 +26,25 @@ - + + + + + - @@ -436,8 +445,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Add/AddWebFeedFolderViewController.swift b/iOS/Add/AddWebFeedFolderViewController.swift index 50c7631f3..9039bc782 100644 --- a/iOS/Add/AddWebFeedFolderViewController.swift +++ b/iOS/Add/AddWebFeedFolderViewController.swift @@ -1,5 +1,5 @@ // -// AddWebFeedLocationViewController.swift +// AddWebFeedFolderViewController.swift // NetNewsWire-iOS // // Created by Maurice Parker on 11/16/19. diff --git a/iOS/Add/AddWebFeedViewController.swift b/iOS/Add/AddWebFeedViewController.swift index 4fb5484bf..3c0f62e61 100644 --- a/iOS/Add/AddWebFeedViewController.swift +++ b/iOS/Add/AddWebFeedViewController.swift @@ -65,6 +65,12 @@ class AddWebFeedViewController: UITableViewController, AddContainerViewControlle } + @IBAction func showURLBuilder(_ sender: Any) { + let navController = UIStoryboard.add.instantiateViewController(withIdentifier: "SelectURLBuilderNavViewController") as! UINavigationController + navController.modalPresentationStyle = .currentContext + present(navController, animated: true) + } + func cancel() { userCancelled = true delegate?.processingDidCancel() diff --git a/iOS/Add/SelectComboTableViewCell.swift b/iOS/Add/SelectComboTableViewCell.swift new file mode 100644 index 000000000..890869bdd --- /dev/null +++ b/iOS/Add/SelectComboTableViewCell.swift @@ -0,0 +1,26 @@ +// +// SelectComboTableViewCell.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/23/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +class SelectComboTableViewCell: VibrantTableViewCell { + + @IBOutlet weak var icon: UIImageView! + @IBOutlet weak var label: UILabel! + + override func updateVibrancy(animated: Bool) { + super.updateVibrancy(animated: animated) + + let iconTintColor = isHighlighted || isSelected ? AppAssets.vibrantTextColor : UIColor.label + UIView.animate(withDuration: duration(animated: animated)) { + self.icon.tintColor = iconTintColor + } + updateLabelVibrancy(label, color: labelColor, animated: animated) + } + +} diff --git a/iOS/Add/SelectURLBuilderTableViewController.swift b/iOS/Add/SelectURLBuilderTableViewController.swift new file mode 100644 index 000000000..dd677f9dd --- /dev/null +++ b/iOS/Add/SelectURLBuilderTableViewController.swift @@ -0,0 +1,44 @@ +// +// SelectURLBuilderTableViewController.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/23/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +class SelectURLBuilderTableViewController: UITableViewController { + + override func viewDidLoad() { + super.viewDidLoad() + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "URLBuilderCell", for: indexPath) as! SelectComboTableViewCell + cell.icon?.image = AppAssets.extensionPointTwitter + cell.label?.text = NSLocalizedString("Twitter", comment: "Twitter") + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + dismiss(animated: true) + } + + // MARK: Actions + + @IBAction func cancel(_ sender: Any) { + dismiss(animated: true) + } + +} From 1b48b093694ef895031763be0a5b638c024c44f2 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 23 Apr 2020 06:55:57 -0500 Subject: [PATCH 097/108] Fix hammer button compression bug --- iOS/Add/Add.storyboard | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Add/Add.storyboard b/iOS/Add/Add.storyboard index 3065861c5..ddccaedb8 100644 --- a/iOS/Add/Add.storyboard +++ b/iOS/Add/Add.storyboard @@ -30,7 +30,7 @@ -