Improve "Open in Mastodon" by using Search API (#888)
* feat(ActionExtension): Improve "Open in Mastodon" by using Search aPI * Add code comment
This commit is contained in:
parent
8e7d115944
commit
3ec9e603df
|
@ -573,6 +573,7 @@
|
||||||
27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+URL.swift"; sourceTree = "<group>"; };
|
27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+URL.swift"; sourceTree = "<group>"; };
|
||||||
2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowedTagsViewModel+DiffableDataSource.swift"; sourceTree = "<group>"; };
|
2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowedTagsViewModel+DiffableDataSource.swift"; sourceTree = "<group>"; };
|
||||||
2A1FE47D2938C11200784BF1 /* Collection+IsNotEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+IsNotEmpty.swift"; sourceTree = "<group>"; };
|
2A1FE47D2938C11200784BF1 /* Collection+IsNotEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+IsNotEmpty.swift"; sourceTree = "<group>"; };
|
||||||
|
2A33625329759B4200481A90 /* OpenInActionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OpenInActionExtension.entitlements; sourceTree = "<group>"; };
|
||||||
2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewModel.swift; sourceTree = "<group>"; };
|
2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewModel.swift; sourceTree = "<group>"; };
|
||||||
2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsTableViewCell.swift; sourceTree = "<group>"; };
|
2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewController.swift; sourceTree = "<group>"; };
|
2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1333,6 +1334,7 @@
|
||||||
2A71F53C296DBDA80049F54A /* OpenInActionExtension */ = {
|
2A71F53C296DBDA80049F54A /* OpenInActionExtension */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
2A33625329759B4200481A90 /* OpenInActionExtension.entitlements */,
|
||||||
2A71F53D296DBDA80049F54A /* Media.xcassets */,
|
2A71F53D296DBDA80049F54A /* Media.xcassets */,
|
||||||
2A71F53E296DBDA80049F54A /* Action.js */,
|
2A71F53E296DBDA80049F54A /* Action.js */,
|
||||||
2A71F53F296DBDA80049F54A /* ActionRequestHandler.swift */,
|
2A71F53F296DBDA80049F54A /* ActionRequestHandler.swift */,
|
||||||
|
@ -3939,6 +3941,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_ENTITLEMENTS = OpenInActionExtension/OpenInActionExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
@ -3969,6 +3972,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_ENTITLEMENTS = OpenInActionExtension/OpenInActionExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
@ -3998,6 +4002,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_ENTITLEMENTS = OpenInActionExtension/OpenInActionExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
@ -4027,6 +4032,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CODE_SIGN_ENTITLEMENTS = OpenInActionExtension/OpenInActionExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
|
|
@ -8,40 +8,23 @@
|
||||||
var Action = function() {};
|
var Action = function() {};
|
||||||
|
|
||||||
Action.prototype = {
|
Action.prototype = {
|
||||||
|
|
||||||
run: function(arguments) {
|
run: function(arguments) {
|
||||||
var payload = {
|
var payload = {
|
||||||
"username": detectUsername(),
|
|
||||||
"url": document.documentURI
|
"url": document.documentURI
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments.completionFunction(payload)
|
arguments.completionFunction(payload)
|
||||||
},
|
},
|
||||||
|
|
||||||
finalize: function(arguments) {
|
finalize: function(arguments) {
|
||||||
let alertMessage = arguments["alert"]
|
const alertMessage = arguments["alert"]
|
||||||
|
const openURL = arguments["openURL"]
|
||||||
|
|
||||||
if (alertMessage) {
|
if (alertMessage) {
|
||||||
alert(alertMessage)
|
alert(alertMessage)
|
||||||
} else {
|
} else if (openURL) {
|
||||||
window.location = arguments["openURL"]
|
window.location = openURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function detectUsername() {
|
|
||||||
var uriUsername = document.documentURI.match("(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))")
|
|
||||||
|
|
||||||
if (Array.isArray(uriUsername)) {
|
|
||||||
return uriUsername[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var querySelector = document.head.querySelector('[property="profile:username"]')
|
|
||||||
if (querySelector !== null && typeof querySelector === "object") {
|
|
||||||
return querySelector.content
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
var ExtensionPreprocessingJS = new Action
|
var ExtensionPreprocessingJS = new Action
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Combine
|
||||||
import UIKit
|
import UIKit
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
import MastodonCore
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
|
||||||
|
@ -16,6 +17,11 @@ class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
|
||||||
var extensionContext: NSExtensionContext?
|
var extensionContext: NSExtensionContext?
|
||||||
var cancellables = [AnyCancellable]()
|
var cancellables = [AnyCancellable]()
|
||||||
|
|
||||||
|
/// Capturing a static shared instance of AppContext here as otherwise there
|
||||||
|
/// will be lifecycle issues and we don't want to keep multiple AppContexts around
|
||||||
|
/// in case there another Action Extension process is spawned
|
||||||
|
private static let appContext = AppContext()
|
||||||
|
|
||||||
func beginRequest(with context: NSExtensionContext) {
|
func beginRequest(with context: NSExtensionContext) {
|
||||||
// Do not call super in an Action extension with no user interface
|
// Do not call super in an Action extension with no user interface
|
||||||
self.extensionContext = context
|
self.extensionContext = context
|
||||||
|
@ -44,10 +50,8 @@ class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let username = results["username"] as? String {
|
if let url = results["url"] as? String {
|
||||||
self?.completeWithOpenUserProfile(username)
|
self?.performSearch(for: url)
|
||||||
} else if let url = results["url"] as? String {
|
|
||||||
self?.continueWithSearch(url)
|
|
||||||
} else {
|
} else {
|
||||||
self?.doneWithInvalidLink()
|
self?.doneWithInvalidLink()
|
||||||
}
|
}
|
||||||
|
@ -56,13 +60,53 @@ class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search API
|
||||||
private extension ActionRequestHandler {
|
private extension ActionRequestHandler {
|
||||||
func completeWithOpenUserProfile(_ username: String) {
|
func performSearch(for url: String) {
|
||||||
doneWithResults([
|
guard
|
||||||
"openURL": "mastodon://profile/\(username)"
|
let activeAuthenticationBox = Self.appContext
|
||||||
])
|
.authenticationService
|
||||||
|
.mastodonAuthenticationBoxes
|
||||||
|
.first
|
||||||
|
else {
|
||||||
|
return doneWithResults(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
Mastodon.API
|
||||||
|
.V2
|
||||||
|
.Search
|
||||||
|
.search(
|
||||||
|
session: .shared,
|
||||||
|
domain: activeAuthenticationBox.domain,
|
||||||
|
query: .init(q: url, resolve: true),
|
||||||
|
authorization: activeAuthenticationBox.userAuthorization
|
||||||
|
)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { completion in
|
||||||
|
// no-op
|
||||||
|
} receiveValue: { [weak self] result in
|
||||||
|
let value = result.value
|
||||||
|
if let foundAccount = value.accounts.first {
|
||||||
|
self?.doneWithResults([
|
||||||
|
"openURL": "mastodon://profile/\(foundAccount.acct)"
|
||||||
|
])
|
||||||
|
} else if let foundStatus = value.statuses.first {
|
||||||
|
self?.doneWithResults([
|
||||||
|
"openURL": "mastodon://status/\(foundStatus.id)"
|
||||||
|
])
|
||||||
|
} else if let foundHashtag = value.hashtags.first {
|
||||||
|
self?.continueWithSearch(foundHashtag.name)
|
||||||
|
} else {
|
||||||
|
self?.continueWithSearch(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to In-App Search
|
||||||
|
private extension ActionRequestHandler {
|
||||||
func continueWithSearch(_ query: String) {
|
func continueWithSearch(_ query: String) {
|
||||||
guard
|
guard
|
||||||
let url = URL(string: query),
|
let url = URL(string: query),
|
||||||
|
@ -95,7 +139,10 @@ private extension ActionRequestHandler {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action response handling
|
||||||
|
private extension ActionRequestHandler {
|
||||||
func doneWithInvalidLink() {
|
func doneWithInvalidLink() {
|
||||||
doneWithResults(["alert": L10n.Extension.OpenIn.invalidLinkError])
|
doneWithResults(["alert": L10n.Extension.OpenIn.invalidLinkError])
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.org.joinmastodon.app</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
Loading…
Reference in New Issue