feat(ActionExtension): Implement Open in Search as Fallback

This commit is contained in:
Marcus Kida 2023-01-11 13:09:23 +01:00
parent 89c808ea11
commit 68fe3cb8f2
No known key found for this signature in database
GPG Key ID: 19FF64E08013CA40
3 changed files with 87 additions and 71 deletions

View File

@ -242,41 +242,56 @@ extension SceneDelegate {
if !UIApplication.shared.canOpenURL(url) { return }
#if DEBUG
print("source application = \(sendingAppID ?? "Unknown")")
print("url = \(url)")
#endif
switch url.host {
case "post":
showComposeViewController()
case "profile":
let components = url.pathComponents
if components.count == 2 && components[0] == "/" {
let addr = components[1]
if let authContext = coordinator?.authContext {
let profileViewModel = RemoteProfileViewModel(
context: AppContext.shared,
authContext: authContext,
acct: components[1]
)
self.coordinator?.present(
scene: .profile(viewModel: profileViewModel),
from: nil,
transition: .show
)
}
}
guard
components.count == 2,
components[0] == "/",
let authContext = coordinator?.authContext
else { return }
let profileViewModel = RemoteProfileViewModel(
context: AppContext.shared,
authContext: authContext,
acct: components[1]
)
self.coordinator?.present(
scene: .profile(viewModel: profileViewModel),
from: nil,
transition: .show
)
case "status":
let components = url.pathComponents
if components.count == 2 && components[0] == "/" {
let statusId = components[1]
// View post from user
if let authContext = coordinator?.authContext {
let threadViewModel = RemoteThreadViewModel(context: AppContext.shared,
authContext: authContext,
statusID: statusId)
coordinator?.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
}
}
guard
components.count == 2,
components[0] == "/",
let authContext = coordinator?.authContext
else { return }
let statusId = components[1]
// View post from user
let threadViewModel = RemoteThreadViewModel(
context: AppContext.shared,
authContext: authContext,
statusID: statusId
)
coordinator?.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
case "search":
let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems
guard
let authContext = coordinator?.authContext,
let searchQuery = queryItems?.first(where: { $0.name == "query" })?.value
else { return }
let viewModel = SearchDetailViewModel(authContext: authContext, initialSearchText: searchQuery)
coordinator?.present(scene: .searchDetail(viewModel: viewModel), from: nil, transition: .show)
default:
return
}

View File

@ -10,25 +10,16 @@ var Action = function() {};
Action.prototype = {
run: function(arguments) {
var username = detectUsername()
if (username) {
arguments.completionFunction({ "username" : username })
var payload = {
"username": detectUsername(),
"url": document.documentURI
}
arguments.completionFunction(payload)
},
finalize: function(arguments) {
var openURL = arguments["openURL"]
var error = arguments["error"]
if (error) {
alert(error)
} else if (openURL) {
window.location = openURL
}
window.location = arguments["openURL"]
}
};
@ -39,12 +30,13 @@ function detectUsername() {
if (typeof uriUsername === "Array") {
return uriUsername[0]
}
var querySelector = document.head.querySelector('[property="profile:username"]')
if (typeof querySelector === "Object") {
return querySelector.content
}
return document.head.querySelector('[property="profile:username"]').content
return undefined
}
function detectPost() {
}
var ExtensionPreprocessingJS = new Action

View File

@ -16,47 +16,56 @@ class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
// Do not call super in an Action extension with no user interface
self.extensionContext = context
guard
let itemProvider = context.inputItems
.compactMap({ $0 as? NSExtensionItem })
.reduce([NSItemProvider](), { partialResult, acc in
var nextResult = partialResult
nextResult += acc.attachments ?? []
return nextResult
})
.filter({ $0.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) })
.first
else {
return self.completeWithNotFoundError()
let itemProvider = context.inputItems
.compactMap({ $0 as? NSExtensionItem })
.reduce([NSItemProvider](), { partialResult, acc in
var nextResult = partialResult
nextResult += acc.attachments ?? []
return nextResult
})
.filter({ $0.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) })
.first
guard let itemProvider = itemProvider else {
return doneWithResults(nil)
}
itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil, completionHandler: { (item, error) in
guard
let dictionary = item as? [String: Any],
let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? [String: Any]? ?? [:]
else {
return self.completeWithNotFoundError()
}
itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil, completionHandler: { [weak self] item, error in
DispatchQueue.main.async {
self.completeWithOpenUserProfile(results)
guard
let dictionary = item as? NSDictionary,
let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary
else {
self?.doneWithResults(nil)
return
}
if let username = results["username"] as? String {
self?.completeWithOpenUserProfile(username)
} else if let url = results["url"] as? String {
self?.completeWithSearch(url)
} else {
self?.doneWithResults(nil)
}
}
})
}
}
private extension ActionRequestHandler {
func completeWithOpenUserProfile(_ results: [String: Any]) {
guard let username = results["username"] as? String else { return }
func completeWithOpenUserProfile(_ username: String) {
doneWithResults([
"openURL": "mastodon://profile/\(username)"
])
}
func completeWithNotFoundError() {
func completeWithSearch(_ query: String) {
guard let query = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
return doneWithResults(nil)
}
doneWithResults(
["error": "Failed to find username. Are you sure this is a Mastodon Profile page?"]
["openURL": "mastodon://search?query=\(query)"]
)
}