diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 616bb4aaa..67ca973f3 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -143,8 +143,6 @@ 5137C2EA26F63AE6009EFEDB /* ArticleThemeImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5137C2E926F63AE6009EFEDB /* ArticleThemeImporter.swift */; }; 51386A8E25673277005F3762 /* AccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51386A8D25673276005F3762 /* AccountCell.swift */; }; 51386A8F25673277005F3762 /* AccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51386A8D25673276005F3762 /* AccountCell.swift */; }; - 5138E93A24D33E5600AFF0FE /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 5138E93924D33E5600AFF0FE /* RSTree */; }; - 5138E93B24D33E5600AFF0FE /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 5138E93924D33E5600AFF0FE /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 5138E95224D3418100AFF0FE /* RSParser in Frameworks */ = {isa = PBXBuildFile; productRef = 5138E95124D3418100AFF0FE /* RSParser */; }; 5138E95324D3418100AFF0FE /* RSParser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 5138E95124D3418100AFF0FE /* RSParser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 5138E95824D3419000AFF0FE /* RSWeb in Frameworks */ = {isa = PBXBuildFile; productRef = 5138E95724D3419000AFF0FE /* RSWeb */; }; @@ -177,8 +175,6 @@ 5148F4552336DB7000F8CD8B /* TimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* TimelineTitleView.swift */; }; 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; }; 514C16CE24D2E63F009A3AFA /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16CD24D2E63F009A3AFA /* Account */; }; - 514C16DE24D2EF15009A3AFA /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16DD24D2EF15009A3AFA /* RSTree */; }; - 514C16DF24D2EF15009A3AFA /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 514C16DD24D2EF15009A3AFA /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.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 */; }; @@ -263,10 +259,6 @@ 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; 51BC2F3824D3439A00E90810 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F3724D3439A00E90810 /* Account */; }; - 51BC2F4824D3439E00E90810 /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4724D3439E00E90810 /* RSTree */; }; - 51BC2F4924D3439E00E90810 /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4724D3439E00E90810 /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 51BC2F4D24D343AB00E90810 /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4C24D343AB00E90810 /* RSTree */; }; - 51BC2F4E24D343AB00E90810 /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4C24D343AB00E90810 /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 51BC4AFF247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; 51BC4B00247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; 51BC4B01247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; @@ -401,8 +393,6 @@ 653813282680E1EC007A082C /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 653813272680E1EC007A082C /* CrashReporter */; }; 653813302680E20C007A082C /* RSParser in Frameworks */ = {isa = PBXBuildFile; productRef = 6538132F2680E20C007A082C /* RSParser */; }; 653813312680E20C007A082C /* RSParser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 6538132F2680E20C007A082C /* RSParser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 653813332680E220007A082C /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 653813322680E220007A082C /* RSTree */; }; - 653813342680E220007A082C /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 653813322680E220007A082C /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 653813362680E224007A082C /* RSWeb in Frameworks */ = {isa = PBXBuildFile; productRef = 653813352680E224007A082C /* RSWeb */; }; 653813372680E224007A082C /* RSWeb in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 653813352680E224007A082C /* RSWeb */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 653813392680E22B007A082C /* Secrets in Frameworks */ = {isa = PBXBuildFile; productRef = 653813382680E22B007A082C /* Secrets */; }; @@ -607,6 +597,9 @@ 841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */; }; 841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; }; 841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */; }; + 841CECD82BAD04B20001EE72 /* Tree in Frameworks */ = {isa = PBXBuildFile; productRef = 841CECD72BAD04B20001EE72 /* Tree */; }; + 841CECDA2BAD04B80001EE72 /* Tree in Frameworks */ = {isa = PBXBuildFile; productRef = 841CECD92BAD04B80001EE72 /* Tree */; }; + 841CECDC2BAD04BF0001EE72 /* Tree in Frameworks */ = {isa = PBXBuildFile; productRef = 841CECDB2BAD04BF0001EE72 /* Tree */; }; 84216D0322128B9D0049B9B9 /* DetailWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */; }; 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; 8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; @@ -903,7 +896,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 51BC2F4924D3439E00E90810 /* RSTree in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -914,7 +906,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 51BC2F4E24D343AB00E90810 /* RSTree in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -954,7 +945,6 @@ 513F327B2593EE6F0003048F /* SyncDatabase in Embed Frameworks */, 513F32722593EE6F0003048F /* Articles in Embed Frameworks */, 513F32812593EF180003048F /* Account in Embed Frameworks */, - 5138E93B24D33E5600AFF0FE /* RSTree in Embed Frameworks */, 513F32752593EE6F0003048F /* ArticlesDatabase in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -992,7 +982,6 @@ 653813312680E20C007A082C /* RSParser in Embed Frameworks */, 6538133A2680E22B007A082C /* Secrets in Embed Frameworks */, 653813252680E1D6007A082C /* ArticlesDatabase in Embed Frameworks */, - 653813342680E220007A082C /* RSTree in Embed Frameworks */, 653813222680E1D0007A082C /* Articles in Embed Frameworks */, 6538131F2680E1CA007A082C /* Account in Embed Frameworks */, ); @@ -1038,7 +1027,6 @@ 513277652590FC640064F1E7 /* SyncDatabase in Embed Frameworks */, 513277622590FC640064F1E7 /* ArticlesDatabase in Embed Frameworks */, 51A737C924DB19CC0015FA66 /* RSParser in Embed Frameworks */, - 514C16DF24D2EF15009A3AFA /* RSTree in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -1325,6 +1313,7 @@ 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NothingInspectorViewController.swift; sourceTree = "<group>"; }; 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderInspectorViewController.swift; sourceTree = "<group>"; }; 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuiltinSmartFeedInspectorViewController.swift; sourceTree = "<group>"; }; + 841CECD62BAD03C60001EE72 /* Tree */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Tree; sourceTree = "<group>"; }; 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebViewController.swift; sourceTree = "<group>"; }; 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconDownloader.swift; sourceTree = "<group>"; }; 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadataDownloader.swift; sourceTree = "<group>"; }; @@ -1535,7 +1524,6 @@ buildActionMask = 2147483647; files = ( 27B86EEB25A53AAB00264340 /* Account in Frameworks */, - 51BC2F4D24D343AB00E90810 /* RSTree in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1544,7 +1532,6 @@ buildActionMask = 2147483647; files = ( 51BC2F3824D3439A00E90810 /* Account in Frameworks */, - 51BC2F4824D3439E00E90810 /* RSTree in Frameworks */, 84D9582C2BABE53B0053E7B2 /* FoundationExtras in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1579,11 +1566,11 @@ 653813362680E224007A082C /* RSWeb in Frameworks */, 84DCA51C2BABB78E00792720 /* CloudKitExtras in Frameworks */, 84DCA5272BABBB6200792720 /* Core in Frameworks */, + 841CECDA2BAD04B80001EE72 /* Tree in Frameworks */, 84DCA5182BABB77E00792720 /* FoundationExtras in Frameworks */, 653813302680E20C007A082C /* RSParser in Frameworks */, 6538131E2680E1CA007A082C /* Account in Frameworks */, 653813282680E1EC007A082C /* CrashReporter in Frameworks */, - 653813332680E220007A082C /* RSTree in Frameworks */, 84DCA51A2BABB78700792720 /* AppKitExtras in Frameworks */, 653813262680E1E4007A082C /* CloudKit.framework in Frameworks */, 653813242680E1D6007A082C /* ArticlesDatabase in Frameworks */, @@ -1614,9 +1601,9 @@ 84DCA5222BABB7A800792720 /* CloudKitExtras in Frameworks */, 513F32742593EE6F0003048F /* ArticlesDatabase in Frameworks */, 513F327A2593EE6F0003048F /* SyncDatabase in Frameworks */, + 841CECDC2BAD04BF0001EE72 /* Tree in Frameworks */, 848565512B9E910200F4BAE0 /* FMDB in Frameworks */, 8485654F2B9E90FD00F4BAE0 /* Database in Frameworks */, - 5138E93A24D33E5600AFF0FE /* RSTree in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1628,7 +1615,6 @@ 17192ADA2567B3D500AAEACA /* RSSparkle in Frameworks */, 51A737C524DB19B50015FA66 /* RSWeb in Frameworks */, 8438C2DB2BABE0B00040C9EE /* CoreResources in Frameworks */, - 514C16DE24D2EF15009A3AFA /* RSTree in Frameworks */, 5132775E2590FC640064F1E7 /* Articles in Frameworks */, 84DCA5252BABBB5A00792720 /* Core in Frameworks */, 8479ABE32B9E906E00F84C4D /* Database in Frameworks */, @@ -1643,6 +1629,7 @@ 84DCA5162BABB76B00792720 /* CloudKitExtras in Frameworks */, 514C16CE24D2E63F009A3AFA /* Account in Frameworks */, 519CA8E525841DB700EB079A /* CrashReporter in Frameworks */, + 841CECD82BAD04B20001EE72 /* Tree in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2375,6 +2362,7 @@ 841550F52B9E4D6800D4B345 /* FMDB */, 84DCA50F2BABB65600792720 /* CloudKitExtras */, 84DCA5232BABBA8100792720 /* Core */, + 841CECD62BAD03C60001EE72 /* Tree */, 84DCA5102BABB6A100792720 /* UIKitExtras */, 84DCA50E2BABB5D800792720 /* AppKitExtras */, 84DCA50D2BAB643700792720 /* FoundationExtras */, @@ -2807,7 +2795,6 @@ name = "NetNewsWire iOS Intents Extension"; packageProductDependencies = ( 51BC2F4A24D343A500E90810 /* Account */, - 51BC2F4C24D343AB00E90810 /* RSTree */, ); productName = "NetNewsWire iOS Intents Extension"; productReference = 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */; @@ -2830,7 +2817,6 @@ name = "NetNewsWire iOS Share Extension"; packageProductDependencies = ( 51BC2F3724D3439A00E90810 /* Account */, - 51BC2F4724D3439E00E90810 /* RSTree */, 84D9582B2BABE53B0053E7B2 /* FoundationExtras */, ); productName = "NetNewsWire iOS Share Extension"; @@ -2920,13 +2906,13 @@ 653813232680E1D6007A082C /* ArticlesDatabase */, 653813272680E1EC007A082C /* CrashReporter */, 6538132F2680E20C007A082C /* RSParser */, - 653813322680E220007A082C /* RSTree */, 653813352680E224007A082C /* RSWeb */, 653813382680E22B007A082C /* Secrets */, 84DCA5172BABB77E00792720 /* FoundationExtras */, 84DCA5192BABB78700792720 /* AppKitExtras */, 84DCA51B2BABB78E00792720 /* CloudKitExtras */, 84DCA5262BABBB6200792720 /* Core */, + 841CECD92BAD04B80001EE72 /* Tree */, ); productName = NetNewsWire; productReference = 65ED4083235DEF6C0081F399 /* NetNewsWire.app */; @@ -2970,7 +2956,6 @@ name = "NetNewsWire-iOS"; packageProductDependencies = ( 516B695E24D2F33B00B5702F /* Account */, - 5138E93924D33E5600AFF0FE /* RSTree */, 5138E95124D3418100AFF0FE /* RSParser */, 5138E95724D3419000AFF0FE /* RSWeb */, 513F32702593EE6F0003048F /* Articles */, @@ -2984,6 +2969,7 @@ 84DCA51F2BABB7A200792720 /* UIKitExtras */, 84DCA5212BABB7A800792720 /* CloudKitExtras */, 84DCA5282BABBB6A00792720 /* Core */, + 841CECDB2BAD04BF0001EE72 /* Tree */, ); productName = "NetNewsWire-iOS"; productReference = 840D617C2029031C009BC708 /* NetNewsWire.app */; @@ -3013,7 +2999,6 @@ name = NetNewsWire; packageProductDependencies = ( 514C16CD24D2E63F009A3AFA /* Account */, - 514C16DD24D2EF15009A3AFA /* RSTree */, 51C4CFF524D37DD500AF9874 /* Secrets */, 51A737C424DB19B50015FA66 /* RSWeb */, 51A737C724DB19CC0015FA66 /* RSParser */, @@ -3030,6 +3015,7 @@ 84DCA5152BABB76B00792720 /* CloudKitExtras */, 84DCA5242BABBB5A00792720 /* Core */, 8438C2DA2BABE0B00040C9EE /* CoreResources */, + 841CECD72BAD04B20001EE72 /* Tree */, ); productName = NetNewsWire; productReference = 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */; @@ -3143,7 +3129,6 @@ ); mainGroup = 849C64571ED37A5D003D8FC0; packageReferences = ( - 510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */, 51383A3024D1F90E0027E272 /* XCRemoteSwiftPackageReference "RSWeb" */, 51B0DF2324D2C7FA000AD99E /* XCRemoteSwiftPackageReference "RSParser" */, 17192AD82567B3D500AAEACA /* XCRemoteSwiftPackageReference "Sparkle-Binary" */, @@ -4770,14 +4755,6 @@ revision = 059e7346082d02de16220cd79df7db18ddeba8c3; }; }; - 510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/Ranchero-Software/RSTree.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; 51383A3024D1F90E0027E272 /* XCRemoteSwiftPackageReference "RSWeb" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Ranchero-Software/RSWeb.git"; @@ -4845,11 +4822,6 @@ isa = XCSwiftPackageProductDependency; productName = SyncDatabase; }; - 5138E93924D33E5600AFF0FE /* RSTree */ = { - isa = XCSwiftPackageProductDependency; - package = 510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */; - productName = RSTree; - }; 5138E95124D3418100AFF0FE /* RSParser */ = { isa = XCSwiftPackageProductDependency; package = 51B0DF2324D2C7FA000AD99E /* XCRemoteSwiftPackageReference "RSParser" */; @@ -4880,11 +4852,6 @@ isa = XCSwiftPackageProductDependency; productName = Account; }; - 514C16DD24D2EF15009A3AFA /* RSTree */ = { - isa = XCSwiftPackageProductDependency; - package = 510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */; - productName = RSTree; - }; 516B695E24D2F33B00B5702F /* Account */ = { isa = XCSwiftPackageProductDependency; productName = Account; @@ -4908,20 +4875,10 @@ isa = XCSwiftPackageProductDependency; productName = Account; }; - 51BC2F4724D3439E00E90810 /* RSTree */ = { - isa = XCSwiftPackageProductDependency; - package = 510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */; - productName = RSTree; - }; 51BC2F4A24D343A500E90810 /* Account */ = { isa = XCSwiftPackageProductDependency; productName = Account; }; - 51BC2F4C24D343AB00E90810 /* RSTree */ = { - isa = XCSwiftPackageProductDependency; - package = 510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */; - productName = RSTree; - }; 51C4CFF524D37DD500AF9874 /* Secrets */ = { isa = XCSwiftPackageProductDependency; productName = Secrets; @@ -4948,11 +4905,6 @@ package = 51B0DF2324D2C7FA000AD99E /* XCRemoteSwiftPackageReference "RSParser" */; productName = RSParser; }; - 653813322680E220007A082C /* RSTree */ = { - isa = XCSwiftPackageProductDependency; - package = 510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */; - productName = RSTree; - }; 653813352680E224007A082C /* RSWeb */ = { isa = XCSwiftPackageProductDependency; package = 51383A3024D1F90E0027E272 /* XCRemoteSwiftPackageReference "RSWeb" */; @@ -4966,6 +4918,18 @@ isa = XCSwiftPackageProductDependency; productName = Account; }; + 841CECD72BAD04B20001EE72 /* Tree */ = { + isa = XCSwiftPackageProductDependency; + productName = Tree; + }; + 841CECD92BAD04B80001EE72 /* Tree */ = { + isa = XCSwiftPackageProductDependency; + productName = Tree; + }; + 841CECDB2BAD04BF0001EE72 /* Tree */ = { + isa = XCSwiftPackageProductDependency; + productName = Tree; + }; 8438C2DA2BABE0B00040C9EE /* CoreResources */ = { isa = XCSwiftPackageProductDependency; productName = CoreResources; diff --git a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 800ed313e..b1ab83243 100644 --- a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "4d91e68a1cd512b0fa806978f5c3b759c8c6defc0a648d1e0fb0db9e944c07c1", + "originHash" : "143979811fdacc63ea101f03b6eeae2ff838828510390a5acdd415b90ffc549f", "pins" : [ { "identity" : "plcrashreporter", @@ -19,15 +19,6 @@ "version" : "2.0.3" } }, - { - "identity" : "rstree", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Ranchero-Software/RSTree.git", - "state" : { - "revision" : "9d051f42cfc4faa991fd79cdb32e4cc8c545e334", - "version" : "1.0.0" - } - }, { "identity" : "rsweb", "kind" : "remoteSourceControl", diff --git a/Tree/.gitignore b/Tree/.gitignore new file mode 100644 index 000000000..0023a5340 --- /dev/null +++ b/Tree/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Tree/Package.swift b/Tree/Package.swift new file mode 100644 index 000000000..f50a4d043 --- /dev/null +++ b/Tree/Package.swift @@ -0,0 +1,21 @@ +// swift-tools-version: 5.10 + +import PackageDescription + +let package = Package( + name: "Tree", + products: [ + .library( + name: "Tree", + targets: ["Tree"]), + ], + targets: [ + .target( + name: "Tree", + dependencies: [], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency") + ] + ) + ] +) diff --git a/Tree/Sources/Tree/NSOutlineView+RSTree.swift b/Tree/Sources/Tree/NSOutlineView+RSTree.swift new file mode 100644 index 000000000..bd26475c9 --- /dev/null +++ b/Tree/Sources/Tree/NSOutlineView+RSTree.swift @@ -0,0 +1,58 @@ +// +// NSOutlineView+RSTree.swift +// RSTree +// +// Created by Brent Simmons on 9/5/16. +// Copyright © 2016 Ranchero Software, LLC. All rights reserved. +// + +#if os(OSX) + +import AppKit + +public extension NSOutlineView { + + @discardableResult + func revealAndSelectNodeAtPath(_ nodePath: NodePath) -> Bool { + + // Returns true on success. Expands folders on the way. May succeed partially (returns false, in that case). + + let numberOfNodes = nodePath.components.count + if numberOfNodes < 2 { + return false + } + + let indexOfNodeToSelect = numberOfNodes - 1 + + for i in 1...indexOfNodeToSelect { // Start at 1 to skip root node. + + let oneNode = nodePath.components[i] + let oneRow = row(forItem: oneNode) + if oneRow < 0 { + return false + } + + if i == indexOfNodeToSelect { + selectRowIndexes(NSIndexSet(index: oneRow) as IndexSet, byExtendingSelection: false) + scrollRowToVisible(oneRow) + return true + } + else { + expandItem(oneNode) + } + } + + return false + } + + @discardableResult + func revealAndSelectRepresentedObject(_ representedObject: AnyObject, _ treeController: TreeController) -> Bool { + + guard let nodePath = NodePath(representedObject: representedObject, treeController: treeController) else { + return false + } + return revealAndSelectNodeAtPath(nodePath) + } +} + +#endif diff --git a/Tree/Sources/Tree/Node.swift b/Tree/Sources/Tree/Node.swift new file mode 100644 index 000000000..3ff9408b0 --- /dev/null +++ b/Tree/Sources/Tree/Node.swift @@ -0,0 +1,224 @@ +// +// Node.swift +// NetNewsWire +// +// Created by Brent Simmons on 7/21/15. +// Copyright © 2015 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +// Main thread only. + +@MainActor public final class Node: Hashable { + + public weak var parent: Node? + public let representedObject: AnyObject + public var canHaveChildNodes = false + public var isGroupItem = false + public var childNodes = [Node]() + public let uniqueID: Int + private static var incrementingID = 0 + + public var isRoot: Bool { + if let _ = parent { + return false + } + return true + } + + public var numberOfChildNodes: Int { + return childNodes.count + } + + public var indexPath: IndexPath { + if let parent = parent { + let parentPath = parent.indexPath + if let childIndex = parent.indexOfChild(self) { + return parentPath.appending(childIndex) + } + preconditionFailure("A Node’s parent must contain it as a child.") + } + return IndexPath(index: 0) //root node + } + + public var level: Int { + if let parent = parent { + return parent.level + 1 + } + return 0 + } + + public var isLeaf: Bool { + return numberOfChildNodes < 1 + } + + public init(representedObject: AnyObject, parent: Node?) { + + precondition(Thread.isMainThread) + + self.representedObject = representedObject + self.parent = parent + + self.uniqueID = Node.incrementingID + Node.incrementingID += 1 + } + + public class func genericRootNode() -> Node { + + let node = Node(representedObject: TopLevelRepresentedObject(), parent: nil) + node.canHaveChildNodes = true + return node + } + + public func existingOrNewChildNode(with representedObject: AnyObject) -> Node { + + if let node = childNodeRepresentingObject(representedObject) { + return node + } + return createChildNode(representedObject) + } + + public func createChildNode(_ representedObject: AnyObject) -> Node { + + // Just creates — doesn’t add it. + return Node(representedObject: representedObject, parent: self) + } + + public func childAtIndex(_ index: Int) -> Node? { + + if index >= childNodes.count || index < 0 { + return nil + } + return childNodes[index] + } + + public func indexOfChild(_ node: Node) -> Int? { + + return childNodes.firstIndex{ (oneChildNode) -> Bool in + oneChildNode === node + } + } + + public func childNodeRepresentingObject(_ obj: AnyObject) -> Node? { + return findNodeRepresentingObject(obj, recursively: false) + } + + public func descendantNodeRepresentingObject(_ obj: AnyObject) -> Node? { + return findNodeRepresentingObject(obj, recursively: true) + } + + public func descendantNode(where test: (Node) -> Bool) -> Node? { + return findNode(where: test, recursively: true) + } + + public func hasAncestor(in nodes: [Node]) -> Bool { + + for node in nodes { + if node.isAncestor(of: self) { + return true + } + } + return false + } + + public func isAncestor(of node: Node) -> Bool { + + if node == self { + return false + } + + var nomad = node + while true { + guard let parent = nomad.parent else { + return false + } + if parent == self { + return true + } + nomad = parent + } + } + + public class func nodesOrganizedByParent(_ nodes: [Node]) -> [Node: [Node]] { + + let nodesWithParents = nodes.filter { $0.parent != nil } + return Dictionary(grouping: nodesWithParents, by: { $0.parent! }) + } + + public class func indexSetsGroupedByParent(_ nodes: [Node]) -> [Node: IndexSet] { + + let d = nodesOrganizedByParent(nodes) + let indexSetDictionary = d.mapValues { (nodes) -> IndexSet in + + var indexSet = IndexSet() + if nodes.isEmpty { + return indexSet + } + + let parent = nodes.first!.parent! + for node in nodes { + if let index = parent.indexOfChild(node) { + indexSet.insert(index) + } + } + + return indexSet + } + + return indexSetDictionary + } + + // MARK: - Hashable + + nonisolated public func hash(into hasher: inout Hasher) { + hasher.combine(uniqueID) + } + + // MARK: - Equatable + + nonisolated public class func ==(lhs: Node, rhs: Node) -> Bool { + return lhs === rhs + } +} + + +public extension Array where Element == Node { + + @MainActor func representedObjects() -> [AnyObject] { + + return self.map{ $0.representedObject } + } +} + +private extension Node { + + func findNodeRepresentingObject(_ obj: AnyObject, recursively: Bool = false) -> Node? { + + for childNode in childNodes { + if childNode.representedObject === obj { + return childNode + } + if recursively, let foundNode = childNode.descendantNodeRepresentingObject(obj) { + return foundNode + } + } + + return nil + } + + func findNode(where test: (Node) -> Bool, recursively: Bool = false) -> Node? { + + for childNode in childNodes { + if test(childNode) { + return childNode + } + if recursively, let foundNode = childNode.findNode(where: test, recursively: recursively) { + return foundNode + } + } + + return nil + } + +} diff --git a/Tree/Sources/Tree/NodePath.swift b/Tree/Sources/Tree/NodePath.swift new file mode 100644 index 000000000..bb280ad03 --- /dev/null +++ b/Tree/Sources/Tree/NodePath.swift @@ -0,0 +1,42 @@ +// +// NodePath.swift +// RSTree +// +// Created by Brent Simmons on 9/5/16. +// Copyright © 2016 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +@MainActor public struct NodePath { + + let components: [Node] + + public init(node: Node) { + + var tempArray = [node] + + var nomad: Node = node + while true { + if let parent = nomad.parent { + tempArray.append(parent) + nomad = parent + } + else { + break + } + } + + self.components = tempArray.reversed() + } + + public init?(representedObject: AnyObject, treeController: TreeController) { + + if let node = treeController.nodeInTreeRepresentingObject(representedObject) { + self.init(node: node) + } + else { + return nil + } + } +} diff --git a/Tree/Sources/Tree/RSTree.swift b/Tree/Sources/Tree/RSTree.swift new file mode 100644 index 000000000..ac0aa000a --- /dev/null +++ b/Tree/Sources/Tree/RSTree.swift @@ -0,0 +1,3 @@ +struct RSTree { + var text = "Hello, World!" +} diff --git a/Tree/Sources/Tree/TopLevelRepresentedObject.swift b/Tree/Sources/Tree/TopLevelRepresentedObject.swift new file mode 100644 index 000000000..ec4619a7b --- /dev/null +++ b/Tree/Sources/Tree/TopLevelRepresentedObject.swift @@ -0,0 +1,15 @@ +// +// TopLevelRepresentedObject.swift +// RSTree +// +// Created by Brent Simmons on 8/10/16. +// Copyright © 2016 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +// Handy to use as the represented object for a root node. Not required to use it, though. + +final class TopLevelRepresentedObject { + +} diff --git a/Tree/Sources/Tree/TreeController.swift b/Tree/Sources/Tree/TreeController.swift new file mode 100644 index 000000000..1d976cebb --- /dev/null +++ b/Tree/Sources/Tree/TreeController.swift @@ -0,0 +1,135 @@ +// +// TreeController.swift +// NetNewsWire +// +// Created by Brent Simmons on 5/29/16. +// Copyright © 2016 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +public protocol TreeControllerDelegate: AnyObject { + + func treeController(treeController: TreeController, childNodesFor: Node) -> [Node]? +} + +public typealias NodeVisitBlock = (_ : Node) -> Void + +@MainActor public final class TreeController { + + private weak var delegate: TreeControllerDelegate? + public let rootNode: Node + + public init(delegate: TreeControllerDelegate, rootNode: Node) { + + self.delegate = delegate + self.rootNode = rootNode + rebuild() + } + + public convenience init(delegate: TreeControllerDelegate) { + + self.init(delegate: delegate, rootNode: Node.genericRootNode()) + } + + @discardableResult + public func rebuild() -> Bool { + + // Rebuild and re-sort. Return true if any changes in the entire tree. + + return rebuildChildNodes(node: rootNode) + } + + public func visitNodes(_ visitBlock: NodeVisitBlock) { + + visitNode(rootNode, visitBlock) + } + + public func nodeInArrayRepresentingObject(nodes: [Node], representedObject: AnyObject, recurse: Bool = false) -> Node? { + + for oneNode in nodes { + + if oneNode.representedObject === representedObject { + return oneNode + } + + if recurse, oneNode.canHaveChildNodes { + if let foundNode = nodeInArrayRepresentingObject(nodes: oneNode.childNodes, representedObject: representedObject, recurse: recurse) { + return foundNode + } + + } + } + return nil + } + + public func nodeInTreeRepresentingObject(_ representedObject: AnyObject) -> Node? { + + return nodeInArrayRepresentingObject(nodes: [rootNode], representedObject: representedObject, recurse: true) + } + + public func normalizedSelectedNodes(_ nodes: [Node]) -> [Node] { + + // An array of nodes might include a leaf node and its parent. Remove the leaf node. + + var normalizedNodes = [Node]() + + for node in nodes { + if !node.hasAncestor(in: nodes) { + normalizedNodes += [node] + } + } + + return normalizedNodes + } +} + +private extension TreeController { + + func visitNode(_ node: Node, _ visitBlock: NodeVisitBlock) { + + visitBlock(node) + node.childNodes.forEach{ (oneChildNode) in + visitNode(oneChildNode, visitBlock) + } + } + + func nodeArraysAreEqual(_ nodeArray1: [Node]?, _ nodeArray2: [Node]?) -> Bool { + + if nodeArray1 == nil && nodeArray2 == nil { + return true + } + if nodeArray1 != nil && nodeArray2 == nil { + return false + } + if nodeArray1 == nil && nodeArray2 != nil { + return false + } + + return nodeArray1! == nodeArray2! + } + + func rebuildChildNodes(node: Node) -> Bool { + + if !node.canHaveChildNodes { + return false + } + + var childNodesDidChange = false + + let childNodes = delegate?.treeController(treeController: self, childNodesFor: node) ?? [Node]() + + childNodesDidChange = !nodeArraysAreEqual(childNodes, node.childNodes) + if (childNodesDidChange) { + node.childNodes = childNodes + } + + childNodes.forEach{ (oneChildNode) in + if rebuildChildNodes(node: oneChildNode) { + childNodesDidChange = true + } + } + + return childNodesDidChange + } +}