parent
8d6c63d5e9
commit
8021868599
@ -299,6 +299,7 @@
|
|||||||
D6C4532D2BCB86AC00E26A0E /* AppearancePrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C4532C2BCB86AC00E26A0E /* AppearancePrefsView.swift */; };
|
D6C4532D2BCB86AC00E26A0E /* AppearancePrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C4532C2BCB86AC00E26A0E /* AppearancePrefsView.swift */; };
|
||||||
D6C4532F2BCB873400E26A0E /* MockStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C4532E2BCB873400E26A0E /* MockStatusView.swift */; };
|
D6C4532F2BCB873400E26A0E /* MockStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C4532E2BCB873400E26A0E /* MockStatusView.swift */; };
|
||||||
D6C453372BCE1CEF00E26A0E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D6C4532A2BCAD7F900E26A0E /* PrivacyInfo.xcprivacy */; };
|
D6C453372BCE1CEF00E26A0E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D6C4532A2BCAD7F900E26A0E /* PrivacyInfo.xcprivacy */; };
|
||||||
|
D6C5F0642D6AEC0A0019F85B /* MastodonController+Resolve.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C5F0632D6AEC050019F85B /* MastodonController+Resolve.swift */; };
|
||||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
||||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
|
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
|
||||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
||||||
@ -728,6 +729,7 @@
|
|||||||
D6C4532A2BCAD7F900E26A0E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
D6C4532A2BCAD7F900E26A0E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||||
D6C4532C2BCB86AC00E26A0E /* AppearancePrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePrefsView.swift; sourceTree = "<group>"; };
|
D6C4532C2BCB86AC00E26A0E /* AppearancePrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePrefsView.swift; sourceTree = "<group>"; };
|
||||||
D6C4532E2BCB873400E26A0E /* MockStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStatusView.swift; sourceTree = "<group>"; };
|
D6C4532E2BCB873400E26A0E /* MockStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStatusView.swift; sourceTree = "<group>"; };
|
||||||
|
D6C5F0632D6AEC050019F85B /* MastodonController+Resolve.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonController+Resolve.swift"; sourceTree = "<group>"; };
|
||||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
|
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
|
||||||
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
||||||
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
||||||
@ -1741,6 +1743,7 @@
|
|||||||
children = (
|
children = (
|
||||||
D6F953EF21251A2900CF0F2B /* MastodonController.swift */,
|
D6F953EF21251A2900CF0F2B /* MastodonController.swift */,
|
||||||
D630C3C92BC59FF500208903 /* MastodonController+Push.swift */,
|
D630C3C92BC59FF500208903 /* MastodonController+Push.swift */,
|
||||||
|
D6C5F0632D6AEC050019F85B /* MastodonController+Resolve.swift */,
|
||||||
D61ABEFD28F1C92600B29151 /* FavoriteService.swift */,
|
D61ABEFD28F1C92600B29151 /* FavoriteService.swift */,
|
||||||
D621733228F1D5ED004C7DB1 /* ReblogService.swift */,
|
D621733228F1D5ED004C7DB1 /* ReblogService.swift */,
|
||||||
D6F6A54F291F058600F496A8 /* CreateListService.swift */,
|
D6F6A54F291F058600F496A8 /* CreateListService.swift */,
|
||||||
@ -2161,6 +2164,7 @@
|
|||||||
D6F6A552291F098700F496A8 /* RenameListService.swift in Sources */,
|
D6F6A552291F098700F496A8 /* RenameListService.swift in Sources */,
|
||||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||||
D698F46B2BD079F00054DB14 /* AnnouncementListRow.swift in Sources */,
|
D698F46B2BD079F00054DB14 /* AnnouncementListRow.swift in Sources */,
|
||||||
|
D6C5F0642D6AEC0A0019F85B /* MastodonController+Resolve.swift in Sources */,
|
||||||
D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */,
|
D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */,
|
||||||
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */,
|
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */,
|
||||||
D6934F362BA8E020002B1C8D /* GifvGalleryContentViewController.swift in Sources */,
|
D6934F362BA8E020002B1C8D /* GifvGalleryContentViewController.swift in Sources */,
|
||||||
|
63
Tusker/API/MastodonController+Resolve.swift
Normal file
63
Tusker/API/MastodonController+Resolve.swift
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// MastodonController+Resolve.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 2/23/25.
|
||||||
|
// Copyright © 2025 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import WebURL
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
extension MastodonController {
|
||||||
|
@MainActor
|
||||||
|
func resolveRemoteStatus(url: URL) async throws -> StatusMO {
|
||||||
|
let effectiveURL: String
|
||||||
|
if isLikelyMastodonRemoteStatus(url: url) {
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
// Mastodon uses an intermediate redirect page for browsers which requires user input that we don't want.
|
||||||
|
request.addValue("application/activity+json", forHTTPHeaderField: "accept")
|
||||||
|
if let (_, response) = try? await URLSession.appDefault.data(for: request, delegate: RedirectBlocker()),
|
||||||
|
let location = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "location") {
|
||||||
|
effectiveURL = location
|
||||||
|
} else {
|
||||||
|
effectiveURL = WebURL(url)!.serialized(excludingFragment: true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
effectiveURL = WebURL(url)!.serialized(excludingFragment: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = Client.search(query: effectiveURL, types: [.statuses], resolve: true)
|
||||||
|
|
||||||
|
let (results, _) = try await run(request)
|
||||||
|
let statuses = results.statuses.compactMap(\.value)
|
||||||
|
// Don't try to exactly match effective URL because the URL form Mastodon
|
||||||
|
// uses for the ActivityPub redirect doesn't match what's returned by the API.
|
||||||
|
// Instead we just assume that, if only one status was returned, it worked.
|
||||||
|
guard statuses.count == 1 else {
|
||||||
|
throw UnableToResolveError()
|
||||||
|
}
|
||||||
|
let status = statuses[0]
|
||||||
|
return persistentContainer.addOrUpdateOnViewContext(status: status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let mastodonRemoteStatusRegex = try! NSRegularExpression(pattern: "^/@.+@.+/\\d{18}")
|
||||||
|
private func isLikelyMastodonRemoteStatus(url: URL) -> Bool {
|
||||||
|
let path = url.path
|
||||||
|
let range = NSRange(location: 0, length: path.utf16.count)
|
||||||
|
return mastodonRemoteStatusRegex.numberOfMatches(in: path, range: range) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class RedirectBlocker: NSObject, URLSessionTaskDelegate, Sendable {
|
||||||
|
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
|
||||||
|
completionHandler(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct UnableToResolveError: LocalizedError {
|
||||||
|
var errorDescription: String? {
|
||||||
|
"Unable to resolve status from URL"
|
||||||
|
}
|
||||||
|
}
|
@ -11,13 +11,6 @@ import Pachyderm
|
|||||||
import WebURL
|
import WebURL
|
||||||
import WebURLFoundationExtras
|
import WebURLFoundationExtras
|
||||||
|
|
||||||
private let mastodonRemoteStatusRegex = try! NSRegularExpression(pattern: "^/@.+@.+/\\d{18}")
|
|
||||||
private func isLikelyMastodonRemoteStatus(url: URL) -> Bool {
|
|
||||||
let path = url.path
|
|
||||||
let range = NSRange(location: 0, length: path.utf16.count)
|
|
||||||
return mastodonRemoteStatusRegex.numberOfMatches(in: path, range: range) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConversationViewController: UIViewController {
|
class ConversationViewController: UIViewController {
|
||||||
|
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
@ -215,38 +208,8 @@ class ConversationViewController: UIViewController {
|
|||||||
indicator.startAnimating()
|
indicator.startAnimating()
|
||||||
state = .loading(indicator)
|
state = .loading(indicator)
|
||||||
|
|
||||||
let effectiveURL: String
|
|
||||||
final class RedirectBlocker: NSObject, URLSessionTaskDelegate, Sendable {
|
|
||||||
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
|
|
||||||
completionHandler(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isLikelyMastodonRemoteStatus(url: url) {
|
|
||||||
var request = URLRequest(url: url)
|
|
||||||
// Mastodon uses an intermediate redirect page for browsers which requires user input that we don't want.
|
|
||||||
request.addValue("application/activity+json", forHTTPHeaderField: "accept")
|
|
||||||
if let (_, response) = try? await URLSession.appDefault.data(for: request, delegate: RedirectBlocker()),
|
|
||||||
let location = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "location") {
|
|
||||||
effectiveURL = location
|
|
||||||
} else {
|
|
||||||
effectiveURL = WebURL(url)!.serialized(excludingFragment: true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
effectiveURL = WebURL(url)!.serialized(excludingFragment: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = Client.search(query: effectiveURL, types: [.statuses], resolve: true)
|
|
||||||
do {
|
do {
|
||||||
let (results, _) = try await mastodonController.run(request)
|
let status = try await mastodonController.resolveRemoteStatus(url: url)
|
||||||
let statuses = results.statuses.compactMap(\.value)
|
|
||||||
// Don't try to exactly match effective URL because the URL form Mastodon
|
|
||||||
// uses for the ActivityPub redirect doesn't match what's returned by the API.
|
|
||||||
// Instead we just assume that, if only one status was returned, it worked.
|
|
||||||
guard statuses.count == 1 else {
|
|
||||||
throw UnableToResolveError()
|
|
||||||
}
|
|
||||||
let status = statuses[0]
|
|
||||||
_ = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status)
|
|
||||||
mode = .localID(status.id)
|
mode = .localID(status.id)
|
||||||
return status.id
|
return status.id
|
||||||
} catch {
|
} catch {
|
||||||
@ -311,7 +274,6 @@ class ConversationViewController: UIViewController {
|
|||||||
guard case .displaying(_) = state else {
|
guard case .displaying(_) = state else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let error = error as! Client.Error
|
|
||||||
|
|
||||||
let config = ToastConfiguration(from: error, with: "Error Loading Context", in: self) { [weak self] toast in
|
let config = ToastConfiguration(from: error, with: "Error Loading Context", in: self) { [weak self] toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
@ -368,9 +330,7 @@ class ConversationViewController: UIViewController {
|
|||||||
subtitle.adjustsFontForContentSizeCategory = true
|
subtitle.adjustsFontForContentSizeCategory = true
|
||||||
subtitle.numberOfLines = 0
|
subtitle.numberOfLines = 0
|
||||||
subtitle.textAlignment = .center
|
subtitle.textAlignment = .center
|
||||||
if let error = error as? UnableToResolveError {
|
if let error = error as? Client.Error {
|
||||||
subtitle.text = error.localizedDescription
|
|
||||||
} else if let error = error as? Client.Error {
|
|
||||||
subtitle.text = error.localizedDescription
|
subtitle.text = error.localizedDescription
|
||||||
} else {
|
} else {
|
||||||
subtitle.text = error.localizedDescription
|
subtitle.text = error.localizedDescription
|
||||||
@ -433,11 +393,6 @@ extension ConversationViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension ConversationViewController {
|
extension ConversationViewController {
|
||||||
struct UnableToResolveError: Error {
|
|
||||||
var localizedDescription: String {
|
|
||||||
"Unable to resolve status from URL"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ConversationViewController {
|
extension ConversationViewController {
|
||||||
|
@ -123,6 +123,23 @@ extension MenuActionProvider {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func actionsForResolvingStatusURL(_ url: URL, source: PopoverSource) -> [UIMenuElement] {
|
||||||
|
guard let mastodonController else {
|
||||||
|
return actionsForURL(url, source: source)
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
UIDeferredMenuElement({ completionHandler in
|
||||||
|
Task {
|
||||||
|
if let status = try? await mastodonController.resolveRemoteStatus(url: url) {
|
||||||
|
completionHandler(self.actionsForStatus(status, source: source))
|
||||||
|
} else {
|
||||||
|
completionHandler(self.actionsForURL(url, source: source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
func actionsForHashtag(_ hashtag: Hashtag, source: PopoverSource) -> [UIMenuElement] {
|
func actionsForHashtag(_ hashtag: Hashtag, source: PopoverSource) -> [UIMenuElement] {
|
||||||
var actionsSection: [UIMenuElement] = []
|
var actionsSection: [UIMenuElement] = []
|
||||||
if let mastodonController = mastodonController,
|
if let mastodonController = mastodonController,
|
||||||
@ -410,6 +427,29 @@ extension MenuActionProvider {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contextMenuConfigurationForURL(_ url: URL, source: PopoverSource) -> UIContextMenuConfiguration {
|
||||||
|
if let mastodonController,
|
||||||
|
isLikelyResolvableAsStatus(url) {
|
||||||
|
return UIContextMenuConfiguration {
|
||||||
|
ConversationViewController(resolving: url, mastodonController: mastodonController)
|
||||||
|
} actionProvider: { _ in
|
||||||
|
let actions = self.actionsForResolvingStatusURL(url, source: source)
|
||||||
|
return UIMenu(children: actions)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return UIContextMenuConfiguration {
|
||||||
|
let vc = SFSafariViewController(url: url)
|
||||||
|
#if !os(visionOS)
|
||||||
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
|
#endif
|
||||||
|
return vc
|
||||||
|
} actionProvider: { _ in
|
||||||
|
let actions = self.actionsForURL(url, source: source)
|
||||||
|
return UIMenu(children: actions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func createAction(identifier: String, title: String, systemImageName: String?, handler: @escaping (UIAction) -> Void) -> UIAction {
|
private func createAction(identifier: String, title: String, systemImageName: String?, handler: @escaping (UIAction) -> Void) -> UIAction {
|
||||||
let image: UIImage?
|
let image: UIImage?
|
||||||
if let name = systemImageName {
|
if let name = systemImageName {
|
||||||
|
@ -216,7 +216,7 @@ private let statusPathRegex = try! NSRegularExpression(
|
|||||||
options: .caseInsensitive
|
options: .caseInsensitive
|
||||||
)
|
)
|
||||||
|
|
||||||
private func isLikelyResolvableAsStatus(_ url: URL) -> Bool {
|
func isLikelyResolvableAsStatus(_ url: URL) -> Bool {
|
||||||
let path = url.path
|
let path = url.path
|
||||||
let range = NSRange(location: 0, length: path.utf16.count)
|
let range = NSRange(location: 0, length: path.utf16.count)
|
||||||
return statusPathRegex.numberOfMatches(in: path, range: range) == 1
|
return statusPathRegex.numberOfMatches(in: path, range: range) == 1
|
||||||
|
@ -273,20 +273,33 @@ extension ContentTextView: UIContextMenuInteractionDelegate {
|
|||||||
// Store the previewed link range for use in the previewForHighlighting method
|
// Store the previewed link range for use in the previewForHighlighting method
|
||||||
currentPreviewedLinkRange = range
|
currentPreviewedLinkRange = range
|
||||||
|
|
||||||
let preview: UIContextMenuContentPreviewProvider = {
|
let preview: UIContextMenuContentPreviewProvider
|
||||||
self.getViewController(forLink: link, inRange: range)
|
let actions: UIContextMenuActionProvider
|
||||||
}
|
if let mastodonController,
|
||||||
let actions: UIContextMenuActionProvider = { (_) in
|
isLikelyResolvableAsStatus(link) {
|
||||||
let text = (self.text as NSString).substring(with: range)
|
preview = {
|
||||||
let actions: [UIMenuElement]
|
ConversationViewController(resolving: link, mastodonController: mastodonController)
|
||||||
if let mention = self.getMention(for: link, text: text) {
|
}
|
||||||
actions = self.actionsForProfile(accountID: mention.id, source: .view(self))
|
actions = { _ in
|
||||||
} else if let tag = self.getHashtag(for: link, text: text) {
|
let actions = self.actionsForResolvingStatusURL(link, source: .view(self))
|
||||||
actions = self.actionsForHashtag(tag, source: .view(self))
|
return UIMenu(children: actions)
|
||||||
} else {
|
}
|
||||||
actions = self.actionsForURL(link, source: .view(self))
|
} else {
|
||||||
|
preview = {
|
||||||
|
self.getViewController(forLink: link, inRange: range)
|
||||||
|
}
|
||||||
|
actions = { (_) in
|
||||||
|
let text = (self.text as NSString).substring(with: range)
|
||||||
|
let actions: [UIMenuElement]
|
||||||
|
if let mention = self.getMention(for: link, text: text) {
|
||||||
|
actions = self.actionsForProfile(accountID: mention.id, source: .view(self))
|
||||||
|
} else if let tag = self.getHashtag(for: link, text: text) {
|
||||||
|
actions = self.actionsForHashtag(tag, source: .view(self))
|
||||||
|
} else {
|
||||||
|
actions = self.actionsForURL(link, source: .view(self))
|
||||||
|
}
|
||||||
|
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
||||||
}
|
}
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return UIContextMenuConfiguration(identifier: nil, previewProvider: preview, actionProvider: actions)
|
return UIContextMenuConfiguration(identifier: nil, previewProvider: preview, actionProvider: actions)
|
||||||
|
@ -183,15 +183,7 @@ extension ProfileFieldValueView: UIContextMenuInteractionDelegate, MenuActionPro
|
|||||||
UIMenu(children: self.actionsForHashtag(hashtag, source: .view(self)))
|
UIMenu(children: self.actionsForHashtag(hashtag, source: .view(self)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return UIContextMenuConfiguration {
|
return self.contextMenuConfigurationForURL(url, source: .view(self))
|
||||||
let vc = SFSafariViewController(url: url)
|
|
||||||
#if !os(visionOS)
|
|
||||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
|
||||||
#endif
|
|
||||||
return vc
|
|
||||||
} actionProvider: { _ in
|
|
||||||
UIMenu(children: self.actionsForURL(url, source: .view(self)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,16 +277,7 @@ extension StatusCardView: UIContextMenuInteractionDelegate {
|
|||||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
guard let card = card else { return nil }
|
guard let card = card else { return nil }
|
||||||
|
|
||||||
return UIContextMenuConfiguration(identifier: nil) {
|
return self.actionProvider?.contextMenuConfigurationForURL(URL(card.url)!, source: .view(self))
|
||||||
let vc = SFSafariViewController(url: URL(card.url)!)
|
|
||||||
#if !os(visionOS)
|
|
||||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
|
||||||
#endif
|
|
||||||
return vc
|
|
||||||
} actionProvider: { (_) in
|
|
||||||
let actions = self.actionProvider?.actionsForURL(URL(card.url)!, source: .view(self)) ?? []
|
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user