feat: add content for StatusNode. Migrate HTML parser from Kanna to Fuzi
This commit is contained in:
parent
1a3135b998
commit
69a7517fde
|
@ -183,6 +183,7 @@
|
||||||
DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; };
|
DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; };
|
||||||
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; };
|
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; };
|
||||||
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
|
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
|
||||||
|
DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */; };
|
||||||
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; };
|
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; };
|
||||||
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; };
|
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; };
|
||||||
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; };
|
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; };
|
||||||
|
@ -202,6 +203,9 @@
|
||||||
DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */; };
|
DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */; };
|
||||||
DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */; };
|
DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */; };
|
||||||
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E347725F519300079D7DF /* PickServerItem.swift */; };
|
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E347725F519300079D7DF /* PickServerItem.swift */; };
|
||||||
|
DB1EE7AE267F3071000CC337 /* MastodonStatusContent+ParseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7AD267F3071000CC337 /* MastodonStatusContent+ParseResult.swift */; };
|
||||||
|
DB1EE7B0267F3088000CC337 /* MastodonStatusContent+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7AF267F3088000CC337 /* MastodonStatusContent+Appearance.swift */; };
|
||||||
|
DB1EE7B2267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */; };
|
||||||
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */; };
|
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */; };
|
||||||
DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */; };
|
DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */; };
|
||||||
DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */; };
|
DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */; };
|
||||||
|
@ -416,6 +420,7 @@
|
||||||
DBAC6499267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC6498267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift */; };
|
DBAC6499267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC6498267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift */; };
|
||||||
DBAC649B267DF8C8007FE9FD /* ActivityIndicatorNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC649A267DF8C8007FE9FD /* ActivityIndicatorNode.swift */; };
|
DBAC649B267DF8C8007FE9FD /* ActivityIndicatorNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAC649A267DF8C8007FE9FD /* ActivityIndicatorNode.swift */; };
|
||||||
DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */ = {isa = PBXBuildFile; productRef = DBAC649D267DFE43007FE9FD /* DiffableDataSources */; };
|
DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */ = {isa = PBXBuildFile; productRef = DBAC649D267DFE43007FE9FD /* DiffableDataSources */; };
|
||||||
|
DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */ = {isa = PBXBuildFile; productRef = DBAC64A0267E6D02007FE9FD /* Fuzi */; };
|
||||||
DBAE3F682615DD60004B8251 /* UserProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F672615DD60004B8251 /* UserProvider.swift */; };
|
DBAE3F682615DD60004B8251 /* UserProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F672615DD60004B8251 /* UserProvider.swift */; };
|
||||||
DBAE3F822615DDA3004B8251 /* ProfileViewController+UserProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F812615DDA3004B8251 /* ProfileViewController+UserProvider.swift */; };
|
DBAE3F822615DDA3004B8251 /* ProfileViewController+UserProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F812615DDA3004B8251 /* ProfileViewController+UserProvider.swift */; };
|
||||||
DBAE3F882615DDF4004B8251 /* UserProviderFacade.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F872615DDF4004B8251 /* UserProviderFacade.swift */; };
|
DBAE3F882615DDF4004B8251 /* UserProviderFacade.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F872615DDF4004B8251 /* UserProviderFacade.swift */; };
|
||||||
|
@ -765,6 +770,7 @@
|
||||||
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.debug.xcconfig"; sourceTree = "<group>"; };
|
D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
|
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
|
||||||
|
DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASMetaEditableTextNode.swift; sourceTree = "<group>"; };
|
||||||
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationController.swift; sourceTree = "<group>"; };
|
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationController.swift; sourceTree = "<group>"; };
|
||||||
DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReplyLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReplyLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = "<group>"; };
|
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = "<group>"; };
|
||||||
|
@ -786,6 +792,9 @@
|
||||||
DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlNavigateable.swift; sourceTree = "<group>"; };
|
DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlNavigateable.swift; sourceTree = "<group>"; };
|
||||||
DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = "<group>"; };
|
DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = "<group>"; };
|
||||||
DB1E347725F519300079D7DF /* PickServerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickServerItem.swift; sourceTree = "<group>"; };
|
DB1E347725F519300079D7DF /* PickServerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickServerItem.swift; sourceTree = "<group>"; };
|
||||||
|
DB1EE7AD267F3071000CC337 /* MastodonStatusContent+ParseResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+ParseResult.swift"; sourceTree = "<group>"; };
|
||||||
|
DB1EE7AF267F3088000CC337 /* MastodonStatusContent+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+Appearance.swift"; sourceTree = "<group>"; };
|
||||||
|
DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+StatusNodeDelegate.swift"; sourceTree = "<group>"; };
|
||||||
DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = "<group>"; };
|
DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = "<group>"; };
|
||||||
DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSection.swift; sourceTree = "<group>"; };
|
DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSection.swift; sourceTree = "<group>"; };
|
||||||
DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
DB1FD44F25F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1083,6 +1092,7 @@
|
||||||
DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */,
|
DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */,
|
||||||
DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */,
|
DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */,
|
||||||
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
|
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
|
||||||
|
DBAC64A1267E6D02007FE9FD /* Fuzi in Frameworks */,
|
||||||
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
|
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
|
||||||
DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */,
|
DBAC649E267DFE43007FE9FD /* DiffableDataSources in Frameworks */,
|
||||||
2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */,
|
2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */,
|
||||||
|
@ -1299,6 +1309,7 @@
|
||||||
2D38F1FD25CD481700561493 /* StatusProvider.swift */,
|
2D38F1FD25CD481700561493 /* StatusProvider.swift */,
|
||||||
2DF75B9A25D0E27500694EC8 /* StatusProviderFacade.swift */,
|
2DF75B9A25D0E27500694EC8 /* StatusProviderFacade.swift */,
|
||||||
2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */,
|
2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */,
|
||||||
|
DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */,
|
||||||
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */,
|
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */,
|
||||||
DB71FD4525F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift */,
|
DB71FD4525F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift */,
|
||||||
DB1D843526579DB5000346B3 /* StatusProvider+TableViewControllerNavigateable.swift */,
|
DB1D843526579DB5000346B3 /* StatusProvider+TableViewControllerNavigateable.swift */,
|
||||||
|
@ -1654,6 +1665,16 @@
|
||||||
path = Onboarding;
|
path = Onboarding;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
DB023296267F0ABE00031745 /* Status */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DBAC6484267D0F9E007FE9FD /* StatusNode.swift */,
|
||||||
|
DBAC6496267DECCB007FE9FD /* TimelineMiddleLoaderNode.swift */,
|
||||||
|
DBAC6498267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift */,
|
||||||
|
);
|
||||||
|
path = Status;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
DB084B5125CBC56300F898ED /* CoreDataStack */ = {
|
DB084B5125CBC56300F898ED /* CoreDataStack */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2307,6 +2328,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
2D42FF6A25C817D2004A627A /* MastodonStatusContent.swift */,
|
2D42FF6A25C817D2004A627A /* MastodonStatusContent.swift */,
|
||||||
|
DB1EE7AD267F3071000CC337 /* MastodonStatusContent+ParseResult.swift */,
|
||||||
|
DB1EE7AF267F3088000CC337 /* MastodonStatusContent+Appearance.swift */,
|
||||||
DB6F5E2E264E5518009108F4 /* MastodonRegex.swift */,
|
DB6F5E2E264E5518009108F4 /* MastodonRegex.swift */,
|
||||||
DB35FC2E26130172006193C9 /* MastodonField.swift */,
|
DB35FC2E26130172006193C9 /* MastodonField.swift */,
|
||||||
DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */,
|
DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */,
|
||||||
|
@ -2353,9 +2376,8 @@
|
||||||
DBAC6486267D0FAC007FE9FD /* Node */ = {
|
DBAC6486267D0FAC007FE9FD /* Node */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
DBAC6484267D0F9E007FE9FD /* StatusNode.swift */,
|
DB023296267F0ABE00031745 /* Status */,
|
||||||
DBAC6496267DECCB007FE9FD /* TimelineMiddleLoaderNode.swift */,
|
DB023294267F0AB800031745 /* ASMetaEditableTextNode.swift */,
|
||||||
DBAC6498267DF2C4007FE9FD /* TimelineBottomLoaderNode.swift */,
|
|
||||||
);
|
);
|
||||||
path = Node;
|
path = Node;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2553,6 +2575,7 @@
|
||||||
DBAEDE5E267A0B1500D25FF5 /* Nuke */,
|
DBAEDE5E267A0B1500D25FF5 /* Nuke */,
|
||||||
DBAC6482267D0B21007FE9FD /* DifferenceKit */,
|
DBAC6482267D0B21007FE9FD /* DifferenceKit */,
|
||||||
DBAC649D267DFE43007FE9FD /* DiffableDataSources */,
|
DBAC649D267DFE43007FE9FD /* DiffableDataSources */,
|
||||||
|
DBAC64A0267E6D02007FE9FD /* Fuzi */,
|
||||||
);
|
);
|
||||||
productName = Mastodon;
|
productName = Mastodon;
|
||||||
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
|
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
|
||||||
|
@ -2743,6 +2766,7 @@
|
||||||
DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */,
|
DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */,
|
||||||
DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */,
|
DBAC6481267D0B21007FE9FD /* XCRemoteSwiftPackageReference "DifferenceKit" */,
|
||||||
DBAC649C267DFE43007FE9FD /* XCRemoteSwiftPackageReference "DiffableDataSources" */,
|
DBAC649C267DFE43007FE9FD /* XCRemoteSwiftPackageReference "DiffableDataSources" */,
|
||||||
|
DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */,
|
||||||
);
|
);
|
||||||
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
|
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -3210,6 +3234,7 @@
|
||||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
|
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
|
||||||
DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */,
|
DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */,
|
||||||
DB6F5E2F264E5518009108F4 /* MastodonRegex.swift in Sources */,
|
DB6F5E2F264E5518009108F4 /* MastodonRegex.swift in Sources */,
|
||||||
|
DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */,
|
||||||
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
|
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
|
||||||
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */,
|
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */,
|
||||||
DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */,
|
DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */,
|
||||||
|
@ -3283,6 +3308,7 @@
|
||||||
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */,
|
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */,
|
||||||
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
|
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
|
||||||
2D7867192625B77500211898 /* NotificationItem.swift in Sources */,
|
2D7867192625B77500211898 /* NotificationItem.swift in Sources */,
|
||||||
|
DB1EE7AE267F3071000CC337 /* MastodonStatusContent+ParseResult.swift in Sources */,
|
||||||
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
|
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
|
||||||
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
||||||
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
||||||
|
@ -3299,6 +3325,7 @@
|
||||||
DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */,
|
DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */,
|
||||||
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */,
|
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */,
|
||||||
2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */,
|
2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */,
|
||||||
|
DB1EE7B0267F3088000CC337 /* MastodonStatusContent+Appearance.swift in Sources */,
|
||||||
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+Provider.swift in Sources */,
|
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+Provider.swift in Sources */,
|
||||||
0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */,
|
0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */,
|
||||||
2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */,
|
2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */,
|
||||||
|
@ -3353,6 +3380,7 @@
|
||||||
DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */,
|
DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */,
|
||||||
DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */,
|
DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */,
|
||||||
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */,
|
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */,
|
||||||
|
DB1EE7B2267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift in Sources */,
|
||||||
5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */,
|
5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */,
|
||||||
5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */,
|
5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */,
|
||||||
DBA94436265CBB7400C537E1 /* ProfileFieldItem.swift in Sources */,
|
DBA94436265CBB7400C537E1 /* ProfileFieldItem.swift in Sources */,
|
||||||
|
@ -4186,6 +4214,14 @@
|
||||||
kind = branch;
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/cezheng/Fuzi.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 3.1.3;
|
||||||
|
};
|
||||||
|
};
|
||||||
DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */ = {
|
DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/kean/Nuke.git";
|
repositoryURL = "https://github.com/kean/Nuke.git";
|
||||||
|
@ -4279,6 +4315,11 @@
|
||||||
package = DBAC649C267DFE43007FE9FD /* XCRemoteSwiftPackageReference "DiffableDataSources" */;
|
package = DBAC649C267DFE43007FE9FD /* XCRemoteSwiftPackageReference "DiffableDataSources" */;
|
||||||
productName = DiffableDataSources;
|
productName = DiffableDataSources;
|
||||||
};
|
};
|
||||||
|
DBAC64A0267E6D02007FE9FD /* Fuzi */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */;
|
||||||
|
productName = Fuzi;
|
||||||
|
};
|
||||||
DBAEDE5E267A0B1500D25FF5 /* Nuke */ = {
|
DBAEDE5E267A0B1500D25FF5 /* Nuke */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */;
|
package = DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<key>AppShared.xcscheme_^#shared#^_</key>
|
<key>AppShared.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>19</integer>
|
<integer>20</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>20</integer>
|
<integer>19</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -64,6 +64,15 @@
|
||||||
"version": "1.2.0"
|
"version": "1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"package": "Fuzi",
|
||||||
|
"repositoryURL": "https://github.com/cezheng/Fuzi.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "f08c8323da21e985f3772610753bcfc652c2103f",
|
||||||
|
"version": "3.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"package": "KeychainAccess",
|
"package": "KeychainAccess",
|
||||||
"repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
"repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// MastodonStatusContent+Appearance.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-6-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension MastodonStatusContent {
|
||||||
|
struct Appearance {
|
||||||
|
let attributes: [NSAttributedString.Key: Any]
|
||||||
|
let urlAttributes: [NSAttributedString.Key: Any]
|
||||||
|
let hashtagAttributes: [NSAttributedString.Key: Any]
|
||||||
|
let mentionAttributes: [NSAttributedString.Key: Any]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
//
|
||||||
|
// MastodonStatusContent+ParseResult.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-6-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import ActiveLabel
|
||||||
|
|
||||||
|
extension MastodonStatusContent {
|
||||||
|
struct ParseResult: Hashable {
|
||||||
|
let document: String
|
||||||
|
let original: String
|
||||||
|
let trimmed: String
|
||||||
|
let activeEntities: [ActiveEntity]
|
||||||
|
|
||||||
|
static func == (lhs: MastodonStatusContent.ParseResult, rhs: MastodonStatusContent.ParseResult) -> Bool {
|
||||||
|
return lhs.document == rhs.document
|
||||||
|
&& lhs.original == rhs.original
|
||||||
|
&& lhs.trimmed == rhs.trimmed
|
||||||
|
&& lhs.activeEntities.count == rhs.activeEntities.count // FIXME:
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(document)
|
||||||
|
hasher.combine(original)
|
||||||
|
hasher.combine(trimmed)
|
||||||
|
hasher.combine(activeEntities.count) // FIXME:
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimmedAttributedString(appearance: MastodonStatusContent.Appearance) -> NSAttributedString {
|
||||||
|
let attributedString = NSMutableAttributedString(string: trimmed, attributes: appearance.attributes)
|
||||||
|
for entity in activeEntities {
|
||||||
|
switch entity.type {
|
||||||
|
case .url:
|
||||||
|
attributedString.addAttributes(appearance.urlAttributes, range: entity.range)
|
||||||
|
case .hashtag:
|
||||||
|
attributedString.addAttributes(appearance.hashtagAttributes, range: entity.range)
|
||||||
|
case .mention:
|
||||||
|
attributedString.addAttributes(appearance.mentionAttributes, range: entity.range)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let uri = entity.type.uri {
|
||||||
|
attributedString.addAttributes([
|
||||||
|
.link: uri
|
||||||
|
], range: entity.range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attributedString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ActiveEntityType {
|
||||||
|
|
||||||
|
static let appScheme = "mastodon"
|
||||||
|
|
||||||
|
init?(url: URL) {
|
||||||
|
guard let scheme = url.scheme?.lowercased() else { return nil }
|
||||||
|
guard scheme == ActiveEntityType.appScheme else {
|
||||||
|
self = .url("", trimmed: "", url: url.absoluteString, userInfo: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||||
|
let parameters = components.queryItems else { return nil }
|
||||||
|
|
||||||
|
if let hashtag = parameters.first(where: { $0.name == "hashtag" }), let encoded = hashtag.value, let value = String(base64Encoded: encoded) {
|
||||||
|
self = .hashtag(value, userInfo: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let mention = parameters.first(where: { $0.name == "mention" }), let encoded = mention.value, let value = String(base64Encoded: encoded) {
|
||||||
|
self = .mention(value, userInfo: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri: URL? {
|
||||||
|
switch self {
|
||||||
|
case .url(_, _, let url, _):
|
||||||
|
return URL(string: url)
|
||||||
|
case .hashtag(let hashtag, _):
|
||||||
|
return URL(string: "\(ActiveEntityType.appScheme)://meta?hashtag=\(hashtag.base64Encoded)")
|
||||||
|
case .mention(let mention, _):
|
||||||
|
return URL(string: "\(ActiveEntityType.appScheme)://meta?mention=\(mention.base64Encoded)")
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
fileprivate var base64Encoded: String {
|
||||||
|
return Data(self.utf8).base64EncodedString()
|
||||||
|
}
|
||||||
|
|
||||||
|
init?(base64Encoded: String) {
|
||||||
|
guard let data = Data(base64Encoded: base64Encoded),
|
||||||
|
let string = String(data: data, encoding: .utf8) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self = string
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,10 @@
|
||||||
// Created by MainasuK Cirno on 2021/2/1.
|
// Created by MainasuK Cirno on 2021/2/1.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import Kanna
|
|
||||||
import ActiveLabel
|
import ActiveLabel
|
||||||
|
import Fuzi
|
||||||
|
|
||||||
enum MastodonStatusContent {
|
enum MastodonStatusContent {
|
||||||
|
|
||||||
|
@ -125,30 +125,6 @@ extension String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MastodonStatusContent {
|
|
||||||
struct ParseResult: Hashable {
|
|
||||||
let document: String
|
|
||||||
let original: String
|
|
||||||
let trimmed: String
|
|
||||||
let activeEntities: [ActiveEntity]
|
|
||||||
|
|
||||||
static func == (lhs: MastodonStatusContent.ParseResult, rhs: MastodonStatusContent.ParseResult) -> Bool {
|
|
||||||
return lhs.document == rhs.document
|
|
||||||
&& lhs.original == rhs.original
|
|
||||||
&& lhs.trimmed == rhs.trimmed
|
|
||||||
&& lhs.activeEntities.count == rhs.activeEntities.count // FIXME:
|
|
||||||
}
|
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
|
||||||
hasher.combine(document)
|
|
||||||
hasher.combine(original)
|
|
||||||
hasher.combine(trimmed)
|
|
||||||
hasher.combine(activeEntities.count) // FIXME:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extension MastodonStatusContent {
|
extension MastodonStatusContent {
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
|
@ -165,7 +141,7 @@ extension MastodonStatusContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
let tagName: String?
|
let tagName: String?
|
||||||
let classNames: Set<String>
|
let attributes: [String : String]
|
||||||
let href: String?
|
let href: String?
|
||||||
let hrefEllipsis: String?
|
let hrefEllipsis: String?
|
||||||
|
|
||||||
|
@ -175,56 +151,47 @@ extension MastodonStatusContent {
|
||||||
level: Int,
|
level: Int,
|
||||||
text: Substring,
|
text: Substring,
|
||||||
tagName: String?,
|
tagName: String?,
|
||||||
className: String?,
|
attributes: [String : String],
|
||||||
href: String?,
|
href: String?,
|
||||||
hrefEllipsis: String?,
|
hrefEllipsis: String?,
|
||||||
children: [Node]
|
children: [Node]
|
||||||
) {
|
) {
|
||||||
let _classNames: Set<String> = {
|
let _classNames: Set<String> = {
|
||||||
guard let className = className else { return Set() }
|
guard let className = attributes["class"] else { return Set() }
|
||||||
return Set(className.components(separatedBy: " "))
|
return Set(className.components(separatedBy: " "))
|
||||||
}()
|
}()
|
||||||
let _type: Type? = {
|
let _type: Type? = {
|
||||||
if tagName == "a" && !_classNames.contains("mention") {
|
if tagName == "a" {
|
||||||
return .url
|
|
||||||
}
|
|
||||||
|
|
||||||
if _classNames.contains("mention") {
|
|
||||||
if _classNames.contains("u-url") {
|
if _classNames.contains("u-url") {
|
||||||
return .mention
|
return .mention
|
||||||
} else if _classNames.contains("hashtag") {
|
}
|
||||||
|
if _classNames.contains("hashtag") {
|
||||||
return .hashtag
|
return .hashtag
|
||||||
}
|
}
|
||||||
|
return .url
|
||||||
|
} else {
|
||||||
|
if _classNames.contains("emoji") {
|
||||||
|
return .emoji
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _classNames.contains("emoji") {
|
|
||||||
return .emoji
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}()
|
}()
|
||||||
self.level = level
|
self.level = level
|
||||||
self.type = _type
|
self.type = _type
|
||||||
self.text = text
|
self.text = text
|
||||||
self.tagName = tagName
|
self.tagName = tagName
|
||||||
self.classNames = _classNames
|
self.attributes = attributes
|
||||||
self.href = href
|
self.href = href
|
||||||
self.hrefEllipsis = hrefEllipsis
|
self.hrefEllipsis = hrefEllipsis
|
||||||
self.children = children
|
self.children = children
|
||||||
}
|
}
|
||||||
|
|
||||||
static func parse(document: String) throws -> MastodonStatusContent.Node {
|
static func parse(document: String) throws -> MastodonStatusContent.Node {
|
||||||
let html = try HTML(html: document, encoding: .utf8)
|
let document = document.replacingOccurrences(of: "<br>|<br />", with: "\r\n", options: .regularExpression, range: nil)
|
||||||
|
let html = try HTMLDocument(string: document)
|
||||||
// add `\r\n` explicit due to Kanna text missing it after convert to text
|
|
||||||
// ref: https://github.com/tid-kijyun/Kanna/issues/150
|
|
||||||
let brNodes = html.css("br").makeIterator()
|
|
||||||
while let brNode = brNodes.next() {
|
|
||||||
brNode.addNextSibling(try! HTML(html: "<span>\r\n</span>", encoding: .utf8).body!)
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = html.body ?? nil
|
let body = html.body ?? nil
|
||||||
let text = body?.text ?? ""
|
let text = body?.stringValue ?? ""
|
||||||
let level = 0
|
let level = 0
|
||||||
let children: [MastodonStatusContent.Node] = body.flatMap { body in
|
let children: [MastodonStatusContent.Node] = body.flatMap { body in
|
||||||
return Node.parse(element: body, parentText: text[...], parentLevel: level + 1)
|
return Node.parse(element: body, parentText: text[...], parentLevel: level + 1)
|
||||||
|
@ -232,8 +199,8 @@ extension MastodonStatusContent {
|
||||||
let node = Node(
|
let node = Node(
|
||||||
level: level,
|
level: level,
|
||||||
text: text[...],
|
text: text[...],
|
||||||
tagName: body?.tagName,
|
tagName: body?.tag,
|
||||||
className: body?.className,
|
attributes: body?.attributes ?? [:],
|
||||||
href: nil,
|
href: nil,
|
||||||
hrefEllipsis: nil,
|
hrefEllipsis: nil,
|
||||||
children: children
|
children: children
|
||||||
|
@ -247,11 +214,9 @@ extension MastodonStatusContent {
|
||||||
let scanner = Scanner(string: String(parentText))
|
let scanner = Scanner(string: String(parentText))
|
||||||
scanner.charactersToBeSkipped = .none
|
scanner.charactersToBeSkipped = .none
|
||||||
|
|
||||||
var element = parent.at_css(":first-child")
|
|
||||||
var children: [Node] = []
|
var children: [Node] = []
|
||||||
|
for _element in parent.children {
|
||||||
while let _element = element {
|
let _text = _element.stringValue
|
||||||
let _text = _element.text ?? ""
|
|
||||||
|
|
||||||
// scan element text
|
// scan element text
|
||||||
_ = scanner.scanUpToString(_text)
|
_ = scanner.scanUpToString(_text)
|
||||||
|
@ -268,20 +233,19 @@ extension MastodonStatusContent {
|
||||||
let text = Substring(parentText.utf16[startIndex..<endIndex])
|
let text = Substring(parentText.utf16[startIndex..<endIndex])
|
||||||
|
|
||||||
let href = _element["href"]
|
let href = _element["href"]
|
||||||
let hrefEllipsis = href.flatMap { _ in _element.at_css(".ellipsis")?.text }
|
let hrefEllipsis = href.flatMap { _ in _element.firstChild(css: ".ellipsis")?.stringValue }
|
||||||
|
|
||||||
let level = parentLevel + 1
|
let level = parentLevel + 1
|
||||||
let node = Node(
|
let node = Node(
|
||||||
level: level,
|
level: level,
|
||||||
text: text,
|
text: text,
|
||||||
tagName: _element.tagName,
|
tagName: _element.tag,
|
||||||
className: _element.className,
|
attributes: _element.attributes,
|
||||||
href: href,
|
href: href,
|
||||||
hrefEllipsis: hrefEllipsis,
|
hrefEllipsis: hrefEllipsis,
|
||||||
children: Node.parse(element: _element, parentText: text, parentLevel: level + 1)
|
children: Node.parse(element: _element, parentText: text, parentLevel: level + 1)
|
||||||
)
|
)
|
||||||
children.append(node)
|
children.append(node)
|
||||||
element = _element.nextSibling
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return children
|
return children
|
||||||
|
@ -344,11 +308,8 @@ extension MastodonStatusContent.Node: CustomDebugStringConvertible {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
let classNamesInfo: String = {
|
let classNamesInfo: String = {
|
||||||
guard !classNames.isEmpty else { return "" }
|
guard let className = attributes["class"] else { return "" }
|
||||||
let names = Array(classNames)
|
return "@[\(className)]"
|
||||||
.sorted()
|
|
||||||
.joined(separator: ", ")
|
|
||||||
return "@[\(names)]"
|
|
||||||
}()
|
}()
|
||||||
let nodeDescription = String(
|
let nodeDescription = String(
|
||||||
format: "<%@>%@%@: %@",
|
format: "<%@>%@%@: %@",
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// StatusProvider+StatusNodeDelegate.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-6-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import ActiveLabel
|
||||||
|
|
||||||
|
// MARK: - StatusViewDelegate
|
||||||
|
extension StatusNodeDelegate where Self: StatusProvider {
|
||||||
|
func statusNode(_ node: StatusNode, statusContentTextNode: ASMetaEditableTextNode, didSelectActiveEntityType type: ActiveEntityType) {
|
||||||
|
StatusProviderFacade.responseToStatusActiveLabelAction(provider: self, node: node, didSelectActiveEntityType: type)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import AsyncDisplayKit
|
||||||
|
|
||||||
protocol StatusProvider: NeedsDependency & DisposeBagCollectable & UIViewController {
|
protocol StatusProvider: NeedsDependency & DisposeBagCollectable & UIViewController {
|
||||||
// async
|
// async
|
||||||
|
@ -21,4 +22,12 @@ protocol StatusProvider: NeedsDependency & DisposeBagCollectable & UIViewControl
|
||||||
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? { get }
|
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? { get }
|
||||||
func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item?
|
func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item?
|
||||||
func items(indexPaths: [IndexPath]) -> [Item]
|
func items(indexPaths: [IndexPath]) -> [Item]
|
||||||
|
|
||||||
|
func status(node: ASCellNode?, indexPath: IndexPath?) -> Status?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusProvider {
|
||||||
|
func status(node: ASCellNode?, indexPath: IndexPath?) -> Status? {
|
||||||
|
fatalError("Needs implement this")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import ActiveLabel
|
import ActiveLabel
|
||||||
|
import AsyncDisplayKit
|
||||||
|
|
||||||
enum StatusProviderFacade { }
|
enum StatusProviderFacade { }
|
||||||
|
|
||||||
|
@ -145,50 +146,84 @@ extension StatusProviderFacade {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func coordinateToStatusMentionProfileScene(for target: Target, provider: StatusProvider, cell: UITableViewCell, mention: String) {
|
static func responseToStatusActiveLabelAction(provider: StatusProvider, node: ASCellNode, didSelectActiveEntityType type: ActiveEntityType) {
|
||||||
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
switch type {
|
||||||
let domain = activeMastodonAuthenticationBox.domain
|
case .hashtag(let text, _):
|
||||||
|
let hashtagTimelienViewModel = HashtagTimelineViewModel(context: provider.context, hashtag: text)
|
||||||
|
provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelienViewModel), from: provider, transition: .show)
|
||||||
|
case .mention(let text, _):
|
||||||
|
coordinateToStatusMentionProfileScene(for: .primary, provider: provider, node: node, mention: text)
|
||||||
|
case .url(_, _, let url, _):
|
||||||
|
guard let url = URL(string: url) else { return }
|
||||||
|
if let domain = provider.context.authenticationService.activeMastodonAuthenticationBox.value?.domain, url.host == domain,
|
||||||
|
url.pathComponents.count >= 4,
|
||||||
|
url.pathComponents[0] == "/",
|
||||||
|
url.pathComponents[1] == "web",
|
||||||
|
url.pathComponents[2] == "statuses" {
|
||||||
|
let statusID = url.pathComponents[3]
|
||||||
|
let threadViewModel = RemoteThreadViewModel(context: provider.context, statusID: statusID)
|
||||||
|
provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
|
||||||
|
} else {
|
||||||
|
provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func coordinateToStatusMentionProfileScene(for target: Target, provider: StatusProvider, node: ASCellNode, mention: String) {
|
||||||
|
guard let status = provider.status(node: node, indexPath: nil) else { return }
|
||||||
|
coordinateToStatusMentionProfileScene(for: target, provider: provider, status: status, mention: mention)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func coordinateToStatusMentionProfileScene(for target: Target, provider: StatusProvider, cell: UITableViewCell, mention: String) {
|
||||||
provider.status(for: cell, indexPath: nil)
|
provider.status(for: cell, indexPath: nil)
|
||||||
.sink { [weak provider] status in
|
.sink { [weak provider] status in
|
||||||
guard let provider = provider else { return }
|
guard let provider = provider else { return }
|
||||||
let _status: Status? = {
|
guard let status = status else { return }
|
||||||
switch target {
|
coordinateToStatusMentionProfileScene(for: target, provider: provider, status: status, mention: mention)
|
||||||
case .primary: return status?.reblog ?? status
|
|
||||||
case .secondary: return status
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
guard let status = _status else { return }
|
|
||||||
|
|
||||||
// cannot continue without meta
|
|
||||||
guard let mentionMeta = (status.mentions ?? Set()).first(where: { $0.username == mention }) else { return }
|
|
||||||
|
|
||||||
let userID = mentionMeta.id
|
|
||||||
|
|
||||||
let profileViewModel: ProfileViewModel = {
|
|
||||||
// check if self
|
|
||||||
guard userID != activeMastodonAuthenticationBox.userID else {
|
|
||||||
return MeProfileViewModel(context: provider.context)
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = MastodonUser.sortedFetchRequest
|
|
||||||
request.fetchLimit = 1
|
|
||||||
request.predicate = MastodonUser.predicate(domain: domain, id: userID)
|
|
||||||
let mastodonUser = provider.context.managedObjectContext.safeFetch(request).first
|
|
||||||
|
|
||||||
if let mastodonUser = mastodonUser {
|
|
||||||
return CachedProfileViewModel(context: provider.context, mastodonUser: mastodonUser)
|
|
||||||
} else {
|
|
||||||
return RemoteProfileViewModel(context: provider.context, userID: userID)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
provider.coordinator.present(scene: .profile(viewModel: profileViewModel), from: provider, transition: .show)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.store(in: &provider.disposeBag)
|
.store(in: &provider.disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func coordinateToStatusMentionProfileScene(for target: Target, provider: StatusProvider, status: Status, mention: String) {
|
||||||
|
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||||
|
let domain = activeMastodonAuthenticationBox.domain
|
||||||
|
|
||||||
|
let status: Status = {
|
||||||
|
switch target {
|
||||||
|
case .primary: return status.reblog ?? status
|
||||||
|
case .secondary: return status
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// cannot continue without meta
|
||||||
|
guard let mentionMeta = (status.mentions ?? Set()).first(where: { $0.username == mention }) else { return }
|
||||||
|
|
||||||
|
let userID = mentionMeta.id
|
||||||
|
|
||||||
|
let profileViewModel: ProfileViewModel = {
|
||||||
|
// check if self
|
||||||
|
guard userID != activeMastodonAuthenticationBox.userID else {
|
||||||
|
return MeProfileViewModel(context: provider.context)
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = MastodonUser.sortedFetchRequest
|
||||||
|
request.fetchLimit = 1
|
||||||
|
request.predicate = MastodonUser.predicate(domain: domain, id: userID)
|
||||||
|
let mastodonUser = provider.context.managedObjectContext.safeFetch(request).first
|
||||||
|
|
||||||
|
if let mastodonUser = mastodonUser {
|
||||||
|
return CachedProfileViewModel(context: provider.context, mastodonUser: mastodonUser)
|
||||||
|
} else {
|
||||||
|
return RemoteProfileViewModel(context: provider.context, userID: userID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
provider.coordinator.present(scene: .profile(viewModel: profileViewModel), from: provider, transition: .show)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusProviderFacade {
|
extension StatusProviderFacade {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import AsyncDisplayKit
|
||||||
|
|
||||||
// MARK: - StatusProvider
|
// MARK: - StatusProvider
|
||||||
extension HomeTimelineViewController: StatusProvider {
|
extension HomeTimelineViewController: StatusProvider {
|
||||||
|
@ -84,6 +85,29 @@ extension HomeTimelineViewController: StatusProvider {
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func status(node: ASCellNode?, indexPath: IndexPath?) -> Status? {
|
||||||
|
guard let diffableDataSource = self.viewModel.diffableDataSource else {
|
||||||
|
assertionFailure()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let indexPath = indexPath ?? node.flatMap({ self.node.indexPath(for: $0) }),
|
||||||
|
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch item {
|
||||||
|
case .homeTimelineIndex(let objectID, _):
|
||||||
|
guard let homeTimelineIndex = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? HomeTimelineIndex else {
|
||||||
|
assertionFailure()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return homeTimelineIndex.status
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension HomeTimelineViewController: UserProvider {}
|
extension HomeTimelineViewController: UserProvider {}
|
||||||
|
|
|
@ -576,4 +576,13 @@ extension HomeTimelineViewController: ASTableDelegate {
|
||||||
viewModel.loadoldestStateMachine.enter(HomeTimelineViewModel.LoadOldestState.Loading.self)
|
viewModel.loadoldestStateMachine.enter(HomeTimelineViewModel.LoadOldestState.Loading.self)
|
||||||
context.completeBatchFetching(true)
|
context.completeBatchFetching(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tableNode(_ tableNode: ASTableNode, willDisplayRowWith node: ASCellNode) {
|
||||||
|
if let statusNode = node as? StatusNode {
|
||||||
|
statusNode.delegate = self
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - StatusNodeDelegate
|
||||||
|
extension HomeTimelineViewController: StatusNodeDelegate { }
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// ASMetaEditableTextNode.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-6-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
|
||||||
|
protocol ASMetaEditableTextNodeDelegate: AnyObject {
|
||||||
|
func metaEditableTextNode(_ textNode: ASMetaEditableTextNode, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ASMetaEditableTextNode: ASEditableTextNode, UITextViewDelegate {
|
||||||
|
weak var metaEditableTextNodeDelegate: ASMetaEditableTextNodeDelegate?
|
||||||
|
|
||||||
|
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
||||||
|
return metaEditableTextNodeDelegate?.metaEditableTextNode(self, shouldInteractWith: URL, in: characterRange, interaction: interaction) ?? false
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,20 +9,44 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import ActiveLabel
|
||||||
|
|
||||||
|
protocol StatusNodeDelegate: AnyObject {
|
||||||
|
func statusNode(_ node: StatusNode, statusContentTextNode: ASMetaEditableTextNode, didSelectActiveEntityType type: ActiveEntityType)
|
||||||
|
}
|
||||||
|
|
||||||
final class StatusNode: ASCellNode {
|
final class StatusNode: ASCellNode {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
weak var delegate: StatusNodeDelegate? // needs assign on main queue
|
||||||
|
|
||||||
static let avatarImageSize = CGSize(width: 42, height: 42)
|
static let avatarImageSize = CGSize(width: 42, height: 42)
|
||||||
static let avatarImageCornerRadius: CGFloat = 4
|
static let avatarImageCornerRadius: CGFloat = 4
|
||||||
|
|
||||||
|
static let statusContentAppearance: MastodonStatusContent.Appearance = {
|
||||||
|
let linkAttributes: [NSAttributedString.Key: Any] = [
|
||||||
|
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)),
|
||||||
|
.foregroundColor: Asset.Colors.brandBlue.color
|
||||||
|
]
|
||||||
|
return MastodonStatusContent.Appearance(
|
||||||
|
attributes: [
|
||||||
|
.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)),
|
||||||
|
.foregroundColor: Asset.Colors.Label.primary.color
|
||||||
|
],
|
||||||
|
urlAttributes: linkAttributes,
|
||||||
|
hashtagAttributes: linkAttributes,
|
||||||
|
mentionAttributes: linkAttributes
|
||||||
|
)
|
||||||
|
}()
|
||||||
|
|
||||||
let avatarImageNode: ASNetworkImageNode = {
|
let avatarImageNode: ASNetworkImageNode = {
|
||||||
let node = ASNetworkImageNode()
|
let node = ASNetworkImageNode()
|
||||||
node.contentMode = .scaleAspectFill
|
node.contentMode = .scaleAspectFill
|
||||||
node.defaultImage = UIImage.placeholder(color: .systemFill)
|
node.defaultImage = UIImage.placeholder(color: .systemFill)
|
||||||
|
node.forcedSize = StatusNode.avatarImageSize
|
||||||
node.cornerRadius = StatusNode.avatarImageCornerRadius
|
node.cornerRadius = StatusNode.avatarImageCornerRadius
|
||||||
// node.cornerRoundingType = .precomposited
|
// node.cornerRoundingType = .precomposited
|
||||||
|
// node.shouldRenderProgressImages = true
|
||||||
return node
|
return node
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -30,6 +54,11 @@ final class StatusNode: ASCellNode {
|
||||||
let nameDotTextNode = ASTextNode()
|
let nameDotTextNode = ASTextNode()
|
||||||
let dateTextNode = ASTextNode()
|
let dateTextNode = ASTextNode()
|
||||||
let usernameTextNode = ASTextNode()
|
let usernameTextNode = ASTextNode()
|
||||||
|
let statusContentTextNode: ASMetaEditableTextNode = {
|
||||||
|
let node = ASMetaEditableTextNode()
|
||||||
|
node.scrollEnabled = false
|
||||||
|
return node
|
||||||
|
}()
|
||||||
|
|
||||||
init(status: Status) {
|
init(status: Status) {
|
||||||
super.init()
|
super.init()
|
||||||
|
@ -39,6 +68,7 @@ final class StatusNode: ASCellNode {
|
||||||
if let url = (status.reblog ?? status).author.avatarImageURL() {
|
if let url = (status.reblog ?? status).author.avatarImageURL() {
|
||||||
avatarImageNode.url = url
|
avatarImageNode.url = url
|
||||||
}
|
}
|
||||||
|
|
||||||
nameTextNode.attributedText = NSAttributedString(string: status.author.displayNameWithFallback, attributes: [
|
nameTextNode.attributedText = NSAttributedString(string: status.author.displayNameWithFallback, attributes: [
|
||||||
.foregroundColor: Asset.Colors.Label.primary.color,
|
.foregroundColor: Asset.Colors.Label.primary.color,
|
||||||
.font: UIFont.systemFont(ofSize: 17, weight: .semibold)
|
.font: UIFont.systemFont(ofSize: 17, weight: .semibold)
|
||||||
|
@ -65,10 +95,29 @@ final class StatusNode: ASCellNode {
|
||||||
// }
|
// }
|
||||||
// .store(in: &self.disposeBag)
|
// .store(in: &self.disposeBag)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
usernameTextNode.attributedText = NSAttributedString(string: "@" + status.author.acct, attributes: [
|
usernameTextNode.attributedText = NSAttributedString(string: "@" + status.author.acct, attributes: [
|
||||||
.foregroundColor: Asset.Colors.Label.secondary.color,
|
.foregroundColor: Asset.Colors.Label.secondary.color,
|
||||||
.font: UIFont.systemFont(ofSize: 15, weight: .regular)
|
.font: UIFont.systemFont(ofSize: 15, weight: .regular)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
statusContentTextNode.metaEditableTextNodeDelegate = self
|
||||||
|
if let parseResult = try? MastodonStatusContent.parse(
|
||||||
|
content: (status.reblog ?? status).content,
|
||||||
|
emojiDict: (status.reblog ?? status).emojiDict
|
||||||
|
) {
|
||||||
|
statusContentTextNode.attributedText = parseResult.trimmedAttributedString(appearance: StatusNode.statusContentAppearance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnterDisplayState() {
|
||||||
|
super.didEnterDisplayState()
|
||||||
|
|
||||||
|
statusContentTextNode.textView.isEditable = false
|
||||||
|
statusContentTextNode.textView.textDragInteraction?.isEnabled = false
|
||||||
|
statusContentTextNode.textView.linkTextAttributes = [
|
||||||
|
.foregroundColor: Asset.Colors.brandBlue.color
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
|
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
|
||||||
|
@ -99,11 +148,26 @@ final class StatusNode: ASCellNode {
|
||||||
headerStack.children = headerStackChildren
|
headerStack.children = headerStackChildren
|
||||||
|
|
||||||
let verticalStack = ASStackLayoutSpec.vertical()
|
let verticalStack = ASStackLayoutSpec.vertical()
|
||||||
|
verticalStack.spacing = 10
|
||||||
verticalStack.children = [
|
verticalStack.children = [
|
||||||
headerStack
|
headerStack,
|
||||||
|
statusContentTextNode,
|
||||||
]
|
]
|
||||||
|
|
||||||
return verticalStack
|
return verticalStack
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - ASEditableTextNodeDelegate
|
||||||
|
extension StatusNode: ASMetaEditableTextNodeDelegate {
|
||||||
|
func metaEditableTextNode(_ textNode: ASMetaEditableTextNode, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
||||||
|
guard let activityEntityType = ActiveEntityType(url: URL) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
delegate?.statusNode(self, statusContentTextNode: textNode, didSelectActiveEntityType: activityEntityType)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,8 +33,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
application.registerForRemoteNotifications()
|
application.registerForRemoteNotifications()
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
PerformanceMonitor.shared().start()
|
// PerformanceMonitor.shared().start()
|
||||||
// ASDisplayNode.shouldShowRangeDebugOverlay = true
|
// ASDisplayNode.shouldShowRangeDebugOverlay = true
|
||||||
|
// ASControlNode.enableHitTestDebug = true
|
||||||
|
// ASImageNode.shouldShowImageScalingOverlay = true
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
Loading…
Reference in New Issue