Compare commits

...

9 Commits

13 changed files with 134 additions and 65 deletions

View File

@ -1,5 +1,14 @@
# Changelog # Changelog
## 2022.1 (40)
Bugfixes:
- Fix selecting reblogged statuses in the timeline
- Fix links/mentions/hashtags in the timeline not being tappable
- Fix mentions from Misskey opening in the browser rather than the profile screen
- Fix crash when leaving timeline tab before it finished loading
- Fix status cells in the timeline not deselected when tapped in split navigation mode on iPad
- Fix keyboard shortcuts not working on iPad
## 2022.1 (39) ## 2022.1 (39)
This is a(nother) hotfix for the previous build. Their changelogs are included below. This is a(nother) hotfix for the previous build. Their changelogs are included below.

View File

@ -9,14 +9,14 @@
import Foundation import Foundation
import WebURL import WebURL
public class Mention: Codable { public struct Mention: Codable {
public let url: WebURL public let url: WebURL
public let username: String public let username: String
public let acct: String public let acct: String
/// The instance-local ID of the user being mentioned. /// The instance-local ID of the user being mentioned.
public let id: String public let id: String
public required init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.username = try container.decode(String.self, forKey: .username) self.username = try container.decode(String.self, forKey: .username)
self.acct = try container.decode(String.self, forKey: .acct) self.acct = try container.decode(String.self, forKey: .acct)

View File

@ -42,6 +42,7 @@
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; }; D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; }; D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; }; D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
D61DC84628F498F200B82C6E /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61DC84528F498F200B82C6E /* Logging.swift */; };
D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; }; D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; };
D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; }; D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; };
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; }; D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; };
@ -394,6 +395,7 @@
D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; }; D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; };
D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; }; D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; };
D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; }; D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; };
D61DC84528F498F200B82C6E /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = "<group>"; }; D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = "<group>"; };
D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = "<group>"; }; D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = "<group>"; };
D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; }; D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; };
@ -1376,6 +1378,7 @@
D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */, D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */,
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */, D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
D64D0AAC2128D88B005A6F37 /* LocalData.swift */, D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
D61DC84528F498F200B82C6E /* Logging.swift */,
D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */, D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */,
D6B81F432560390300F6E31D /* MenuController.swift */, D6B81F432560390300F6E31D /* MenuController.swift */,
D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */, D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */,
@ -1846,6 +1849,7 @@
D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */, D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */,
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */, D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */,
D61ABEF628EE74D400B29151 /* StatusCollectionViewCell.swift in Sources */, D61ABEF628EE74D400B29151 /* StatusCollectionViewCell.swift in Sources */,
D61DC84628F498F200B82C6E /* Logging.swift in Sources */,
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */, D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */,
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */, D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */,
D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */, D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */,
@ -2213,7 +2217,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 39; CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2242,7 +2246,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 39; CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2352,7 +2356,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 39; CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -2379,7 +2383,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 39; CURRENT_PROJECT_VERSION = 40;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;

View File

@ -8,6 +8,19 @@
<dict> <dict>
<key>DEFAULT-OPTIONS</key> <key>DEFAULT-OPTIONS</key>
<dict> <dict>
<key>TTL</key>
<dict>
<key>Fault</key>
<integer>30</integer>
<key>Error</key>
<integer>30</integer>
<key>Debug</key>
<integer>15</integer>
<key>Info</key>
<integer>30</integer>
<key>Default</key>
<integer>30</integer>
</dict>
<key>Level</key> <key>Level</key>
<dict> <dict>
<key>Persist</key> <key>Persist</key>

42
Tusker/Logging.swift Normal file
View File

@ -0,0 +1,42 @@
//
// Logging.swift
// Tusker
//
// Created by Shadowfacts on 10/10/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import Foundation
import OSLog
struct Logging {
private init() {}
static let general = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "General")
static func getLogData() -> Data? {
do {
let store = try OSLogStore(scope: .currentProcessIdentifier)
// past hour
let position = store.position(date: Date().addingTimeInterval(-60 * 60))
let entries = try store.getEntries(at: position, matching: NSPredicate(format: "subsystem = %@", Bundle.main.bundleIdentifier!))
var data = Data()
for entry in entries {
guard let entry = entry as? OSLogEntryLog else {
continue
}
data.append(contentsOf: entry.date.formatted(.iso8601).utf8)
data.append(32) // ' '
data.append(91) // '['
data.append(contentsOf: entry.category.utf8)
data.append(93) // ']'
data.append(32) // ' '
data.append(contentsOf: entry.composedMessage.utf8)
data.append(10) // '\n'
}
return data
} catch {
return nil
}
}
}

View File

@ -48,7 +48,7 @@ class IssueReporterViewController: UIViewController {
self.logDataTask = Task(priority: .userInitiated) { self.logDataTask = Task(priority: .userInitiated) {
return await withCheckedContinuation({ continuation in return await withCheckedContinuation({ continuation in
DispatchQueue.global().async { DispatchQueue.global().async {
continuation.resume(returning: getLogData()) continuation.resume(returning: Logging.getLogData())
} }
}) })
} }
@ -158,29 +158,3 @@ extension IssueReporterViewController: MFMailComposeViewControllerDelegate {
} }
} }
} }
fileprivate func getLogData() -> Data? {
do {
let store = try OSLogStore(scope: .currentProcessIdentifier)
// past hour
let position = store.position(date: Date().addingTimeInterval(-60 * 60))
let entries = try store.getEntries(at: position, matching: NSPredicate(format: "subsystem = %@", Bundle.main.bundleIdentifier!))
var data = Data()
for entry in entries {
guard let entry = entry as? OSLogEntryLog else {
continue
}
data.append(contentsOf: entry.date.formatted(.iso8601).utf8)
data.append(32) // ' '
data.append(91) // '['
data.append(contentsOf: entry.category.utf8)
data.append(93) // ']'
data.append(32) // ' '
data.append(contentsOf: entry.composedMessage.utf8)
data.append(10) // '\n'
}
return data
} catch {
return nil
}
}

View File

@ -20,9 +20,6 @@ class MainSplitViewController: UISplitViewController {
private var tabBarViewController: MainTabBarViewController! private var tabBarViewController: MainTabBarViewController!
// private var secondaryNavController: UINavigationController! {
// viewController(for: .secondary) as? UINavigationController
// }
private var secondaryNavController: SplitNavigationController! { private var secondaryNavController: SplitNavigationController! {
viewController(for: .secondary) as? SplitNavigationController viewController(for: .secondary) as? SplitNavigationController
} }
@ -49,8 +46,6 @@ class MainSplitViewController: UISplitViewController {
setViewController(sidebar, for: .primary) setViewController(sidebar, for: .primary)
primaryBackgroundStyle = .sidebar primaryBackgroundStyle = .sidebar
// let secondaryNav = EnhancedNavigationViewController()
// secondaryNav.useBrowserStyleNavigation = true
let splitNav = SplitNavigationController() let splitNav = SplitNavigationController()
setViewController(splitNav, for: .secondary) setViewController(splitNav, for: .secondary)
// don't unnecesarily construct a content VC unless the we're in actually split mode // don't unnecesarily construct a content VC unless the we're in actually split mode

View File

@ -175,6 +175,9 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
return return
} }
var snapshot = dataSource.snapshot() var snapshot = dataSource.snapshot()
guard snapshot.indexOfSection(.statuses) != nil else {
return
}
let items = snapshot.itemIdentifiers(inSection: .statuses) let items = snapshot.itemIdentifiers(inSection: .statuses)
let pageSize = 20 let pageSize = 20
let numberOfPagesToPrune = (items.count - lastVisibleIndexPath.row - 1) / pageSize let numberOfPagesToPrune = (items.count - lastVisibleIndexPath.row - 1) / pageSize
@ -368,7 +371,9 @@ extension TimelineViewController: UICollectionViewDelegate {
case .publicTimelineDescription: case .publicTimelineDescription:
removeTimelineDescriptionCell() removeTimelineDescriptionCell()
case .status(id: let id, state: let state): case .status(id: let id, state: let state):
selected(status: id, state: state.copy()) let status = mastodonController.persistentContainer.status(for: id)!
// if the status in the timeline is a reblog, show the status that it is a reblog of
selected(status: status.reblog?.id ?? id, state: state.copy())
case .loadingIndicator, .confirmLoadMore: case .loadingIndicator, .confirmLoadMore:
fatalError("unreachable") fatalError("unreachable")
} }

View File

@ -301,13 +301,16 @@ extension MenuActionProvider {
}), at: 0) }), at: 0)
} }
var shareSection = [ var shareSection: [UIAction] = []
openInSafariAction(url: status.url!), if let url = status.url {
createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in shareSection.append(openInSafariAction(url: url))
guard let self = self else { return } } else {
self.navigationDelegate?.showMoreOptions(forStatus: status.id, sourceView: sourceView) Logging.general.fault("Status missing URL: id=\(status.id, privacy: .public), reblog=\((status.reblog?.id).debugDescription, privacy: .public)")
}), }
] shareSection.append(createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in
guard let self = self else { return }
self.navigationDelegate?.showMoreOptions(forStatus: status.id, sourceView: sourceView)
}))
addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: accountID)) addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: accountID))

View File

@ -55,11 +55,18 @@ class SplitNavigationController: UIViewController {
if let tableVC = sender as? UITableViewController, if let tableVC = sender as? UITableViewController,
let selectedIndexPath = tableVC.tableView.indexPathForSelectedRow { let selectedIndexPath = tableVC.tableView.indexPathForSelectedRow {
tableVC.tableView.deselectRow(at: selectedIndexPath, animated: true) tableVC.tableView.deselectRow(at: selectedIndexPath, animated: true)
} else if let sender = sender as? UIViewController,
let collectionView = sender.view as? UICollectionView {
// the collection view's animation speed is weirdly fast, so we do it slower
UIView.animate(withDuration: 0.5, delay: 0) {
collectionView.indexPathsForSelectedItems?.forEach { collectionView.deselectItem(at: $0, animated: false) }
}
} }
} else { } else {
self.rootNav.pushViewController(vc, animated: true) self.rootNav.pushViewController(vc, animated: true)
} }
} }
secondaryNav.owner = self
secondaryNav.closeSecondaryImpl = { [unowned self] in secondaryNav.closeSecondaryImpl = { [unowned self] in
self.popToRootViewController(animated: true) self.popToRootViewController(animated: true)
} }
@ -72,25 +79,18 @@ class SplitNavigationController: UIViewController {
// it needs a UINavigationController to be this VC's first child, otherwise it will embed this VC inside // it needs a UINavigationController to be this VC's first child, otherwise it will embed this VC inside
// yet another UINavigationController, which can then cause a crash when we try to embed a nav controller inside // yet another UINavigationController, which can then cause a crash when we try to embed a nav controller inside
// of ourself (because nested nav controllers are forbidden) // of ourself (because nested nav controllers are forbidden)
rootNav.willMove(toParent: self) // and because of that, the view needs to be added here, in between the addChild/didMove(toParent:) calls
// and so the view needs to be loaded immediately
loadViewIfNeeded()
addChild(rootNav) addChild(rootNav)
rootNav.didMove(toParent: self)
secondaryNav.willMove(toParent: self)
addChild(secondaryNav)
secondaryNav.didMove(toParent: self)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
rootNav.view.translatesAutoresizingMaskIntoConstraints = false rootNav.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(rootNav.view) view.addSubview(rootNav.view)
rootNav.didMove(toParent: self)
addChild(secondaryNav)
secondaryNav.view.translatesAutoresizingMaskIntoConstraints = false secondaryNav.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(secondaryNav.view) view.addSubview(secondaryNav.view)
secondaryNav.didMove(toParent: self)
separatorView.backgroundColor = .separator separatorView.backgroundColor = .separator
separatorView.translatesAutoresizingMaskIntoConstraints = false separatorView.translatesAutoresizingMaskIntoConstraints = false
@ -114,6 +114,16 @@ class SplitNavigationController: UIViewController {
updateSecondaryNavVisibility() updateSecondaryNavVisibility()
} }
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// NOTE: as explained by the large comment above, viewDidLoad is called during initialization, and so things may not be fully setup when it is
}
override func show(_ vc: UIViewController, sender: Any?) { override func show(_ vc: UIViewController, sender: Any?) {
if !canShowSecondaryNav { if !canShowSecondaryNav {
rootNav.pushViewController(vc, animated: true) rootNav.pushViewController(vc, animated: true)
@ -247,6 +257,7 @@ private class SplitRootNavigationController: UINavigationController {
} }
private class SplitSecondaryNavigationController: EnhancedNavigationViewController { private class SplitSecondaryNavigationController: EnhancedNavigationViewController {
fileprivate unowned var owner: SplitNavigationController!
fileprivate var closeSecondaryImpl: (() -> Void)! fileprivate var closeSecondaryImpl: (() -> Void)!
override var viewControllers: [UIViewController] { override var viewControllers: [UIViewController] {
@ -257,6 +268,12 @@ private class SplitSecondaryNavigationController: EnhancedNavigationViewControll
} }
} }
override var next: UIResponder? {
// ordinarily, the next responder in the chain would be the SplitNavigationController's view
// but that would bypass the VC in the root nav, so we reroute the repsonder chain to include it
owner.viewControllers.first!.view
}
private func configureSecondarySplitCloseButton(for viewController: UIViewController) { private func configureSecondarySplitCloseButton(for viewController: UIViewController) {
guard viewController.navigationItem.leftBarButtonItem?.tag != ViewTags.splitNavCloseSecondaryButton else { guard viewController.navigationItem.leftBarButtonItem?.tag != ViewTags.splitNavCloseSecondaryButton else {
return return

View File

@ -140,7 +140,10 @@ extension TuskerNavigationDelegate {
private func moreOptions(forStatus statusID: String) -> UIActivityViewController { private func moreOptions(forStatus statusID: String) -> UIActivityViewController {
guard let status = apiController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") } guard let status = apiController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
guard let url = status.url else { fatalError("Missing url for status \(statusID)") } guard let url = status.url else {
Logging.general.fault("Status missing URL: id=\(status.id, privacy: .public), reblog=\((status.reblog?.id).debugDescription, privacy: .public)")
fatalError("Cannot create UIActivityViewController for status without URL")
}
return UIActivityViewController(activityItems: [url, StatusActivityItemSource(status)], applicationActivities: nil) return UIActivityViewController(activityItems: [url, StatusActivityItemSource(status)], applicationActivities: nil)
} }

View File

@ -84,6 +84,7 @@ extension StatusCollectionViewCell {
updateUIForPreferences(status: status) updateUIForPreferences(status: status)
contentContainer.contentTextView.setTextFrom(status: status) contentContainer.contentTextView.setTextFrom(status: status)
contentContainer.contentTextView.navigationDelegate = delegate
contentContainer.attachmentsView.delegate = self contentContainer.attachmentsView.delegate = self
contentContainer.attachmentsView.updateUI(status: status) contentContainer.attachmentsView.updateUI(status: status)
contentContainer.cardView.updateUI(status: status) contentContainer.cardView.updateUI(status: status)

View File

@ -26,8 +26,11 @@ class StatusContentTextView: ContentTextView {
let mastodonController = mastodonController, let mastodonController = mastodonController,
let status = mastodonController.persistentContainer.status(for: statusID) { let status = mastodonController.persistentContainer.status(for: statusID) {
mention = status.mentions.first { (mention) in mention = status.mentions.first { (mention) in
// Mastodon and Pleroma include the @ in the <a> text, GNU Social does not url.host == mention.url.host!.serialized && (
(text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host!.serialized text.dropFirst() == mention.username // Mastodon and Pleroma include @ in the text
|| text.dropFirst() == mention.acct // Misskey includes @ and uses the whole acct
|| text == mention.username // GNU Social does not include the @ in the text, so we don't need to drop it
)
} }
} else { } else {
mention = nil mention = nil