forked from shadowfacts/Tusker
parent
572c5a0824
commit
adaf8dc217
|
@ -14,7 +14,6 @@ import OSLog
|
|||
import Pachyderm
|
||||
import Intents
|
||||
import HTMLStreamer
|
||||
import WebURL
|
||||
import UIKit
|
||||
import TuskerPreferences
|
||||
|
||||
|
@ -238,8 +237,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
for match in emojiRegex.matches(in: content.body, range: NSRange(location: 0, length: content.body.utf16.count)).reversed() {
|
||||
let emojiName = (content.body as NSString).substring(with: match.range(at: 1))
|
||||
guard let emoji = notification.status?.emojis.first(where: { $0.shortcode == emojiName }),
|
||||
let url = URL(emoji.url),
|
||||
let (data, _) = try? await URLSession.shared.data(from: url),
|
||||
let (data, _) = try? await URLSession.shared.data(from: emoji.url),
|
||||
let image = UIImage(data: data) else {
|
||||
continue
|
||||
}
|
||||
|
@ -368,17 +366,7 @@ private func decodeBase64URL(_ s: String) -> Data? {
|
|||
// copied from HTMLConverter.Callbacks, blergh
|
||||
private struct HTMLCallbacks: HTMLConversionCallbacks {
|
||||
static func makeURL(string: String) -> URL? {
|
||||
// Converting WebURL to URL is a small but non-trivial expense (since it works by
|
||||
// serializing the WebURL as a string and then having Foundation parse it again),
|
||||
// so, if available, use the system parser which doesn't require another round trip.
|
||||
if let url = try? URL.ParseStrategy().parse(string) {
|
||||
url
|
||||
} else if let web = WebURL(string),
|
||||
let url = URL(web) {
|
||||
url
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
try? URL.ParseStrategy().parse(string)
|
||||
}
|
||||
|
||||
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
|
||||
|
|
|
@ -7,6 +7,7 @@ let package = Package(
|
|||
name: "Pachyderm",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
.macOS(.v13),
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
|
@ -16,7 +17,6 @@ let package = Package(
|
|||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
.package(url: "https://github.com/karwa/swift-url.git", exact: "0.4.2"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
|
@ -24,8 +24,6 @@ let package = Package(
|
|||
.target(
|
||||
name: "Pachyderm",
|
||||
dependencies: [
|
||||
.product(name: "WebURL", package: "swift-url"),
|
||||
.product(name: "WebURLFoundationExtras", package: "swift-url"),
|
||||
],
|
||||
swiftSettings: [
|
||||
.swiftLanguageMode(.v5)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
/**
|
||||
The base Mastodon API client.
|
||||
|
@ -202,8 +201,8 @@ public struct Client: Sendable {
|
|||
let wellKnown = Request<WellKnown>(method: .get, path: "/.well-known/nodeinfo")
|
||||
let wellKnownResults = try await run(wellKnown).0
|
||||
if let url = wellKnownResults.links.first(where: { $0.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0" }),
|
||||
let href = WebURL(url.href),
|
||||
href.host == WebURL(self.baseURL)?.host {
|
||||
let href = try? URL.ParseStrategy().parse(url.href),
|
||||
href.host == self.baseURL.host() {
|
||||
let nodeInfo = Request<NodeInfo>(method: .get, path: Endpoint(stringLiteral: href.path))
|
||||
return try await run(nodeInfo).0
|
||||
} else {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct Announcement: Decodable, Sendable, Hashable, Identifiable {
|
||||
public let id: String
|
||||
|
@ -60,7 +59,7 @@ extension Announcement {
|
|||
public struct Account: Decodable, Sendable, Hashable {
|
||||
public let id: String
|
||||
public let username: String
|
||||
public let url: WebURL
|
||||
@URLDecoder public var url: URL
|
||||
public let acct: String
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +67,7 @@ extension Announcement {
|
|||
extension Announcement {
|
||||
public struct Status: Decodable, Sendable, Hashable {
|
||||
public let id: String
|
||||
public let url: WebURL
|
||||
@URLDecoder public var url: URL
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,18 +7,17 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct Card: Codable, Sendable {
|
||||
public let url: WebURL
|
||||
@URLDecoder public var url: URL
|
||||
public let title: String
|
||||
public let description: String
|
||||
public let image: WebURL?
|
||||
@OptionalURLDecoder public var image: URL?
|
||||
public let kind: Kind
|
||||
public let authorName: String?
|
||||
public let authorURL: WebURL?
|
||||
@OptionalURLDecoder public var authorURL: URL?
|
||||
public let providerName: String?
|
||||
public let providerURL: WebURL?
|
||||
@OptionalURLDecoder public var providerURL: URL?
|
||||
public let html: String?
|
||||
public let width: Int?
|
||||
public let height: Int?
|
||||
|
@ -27,15 +26,15 @@ public struct Card: Codable, Sendable {
|
|||
public let history: [History]?
|
||||
|
||||
public init(
|
||||
url: WebURL,
|
||||
url: URL,
|
||||
title: String,
|
||||
description: String,
|
||||
image: WebURL? = nil,
|
||||
image: URL? = nil,
|
||||
kind: Card.Kind,
|
||||
authorName: String? = nil,
|
||||
authorURL: WebURL? = nil,
|
||||
authorURL: URL? = nil,
|
||||
providerName: String? = nil,
|
||||
providerURL: WebURL? = nil,
|
||||
providerURL: URL? = nil,
|
||||
html: String? = nil,
|
||||
width: Int? = nil,
|
||||
height: Int? = nil,
|
||||
|
@ -61,15 +60,15 @@ public struct Card: Codable, Sendable {
|
|||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.url = try container.decode(WebURL.self, forKey: .url)
|
||||
self._url = try container.decode(URLDecoder.self, forKey: .url)
|
||||
self.title = try container.decode(String.self, forKey: .title)
|
||||
self.description = try container.decode(String.self, forKey: .description)
|
||||
self.kind = try container.decode(Kind.self, forKey: .kind)
|
||||
self.image = try? container.decodeIfPresent(WebURL.self, forKey: .image)
|
||||
self._image = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .image) ?? nil
|
||||
self.authorName = try? container.decodeIfPresent(String.self, forKey: .authorName)
|
||||
self.authorURL = try? container.decodeIfPresent(WebURL.self, forKey: .authorURL)
|
||||
self._authorURL = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .authorURL) ?? nil
|
||||
self.providerName = try? container.decodeIfPresent(String.self, forKey: .providerName)
|
||||
self.providerURL = try? container.decodeIfPresent(WebURL.self, forKey: .providerURL)
|
||||
self._providerURL = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .providerURL) ?? nil
|
||||
self.html = try? container.decodeIfPresent(String.self, forKey: .html)
|
||||
self.width = try? container.decodeIfPresent(Int.self, forKey: .width)
|
||||
self.height = try? container.decodeIfPresent(Int.self, forKey: .height)
|
||||
|
|
|
@ -7,14 +7,11 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct Emoji: Codable, Sendable {
|
||||
public let shortcode: String
|
||||
// these shouldn't need to be WebURLs as they're not external resources,
|
||||
// but some instances (pleroma?) has emoji urls that Foundation considers malformed so we use WebURL to be more lenient
|
||||
public let url: WebURL
|
||||
public let staticURL: WebURL
|
||||
@URLDecoder public var url: URL
|
||||
@URLDecoder public var staticURL: URL
|
||||
public let visibleInPicker: Bool
|
||||
public let category: String?
|
||||
|
||||
|
@ -22,13 +19,8 @@ public struct Emoji: Codable, Sendable {
|
|||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.shortcode = try container.decode(String.self, forKey: .shortcode)
|
||||
do {
|
||||
self.url = try container.decode(WebURL.self, forKey: .url)
|
||||
} catch {
|
||||
let s = try? container.decode(String.self, forKey: .url)
|
||||
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath + [CodingKeys.url], debugDescription: "Could not decode URL '\(s ?? "<failed to decode string>")'", underlyingError: error))
|
||||
}
|
||||
self.staticURL = try container.decode(WebURL.self, forKey: .staticURL)
|
||||
self._url = try container.decode(URLDecoder.self, forKey: .url)
|
||||
self._staticURL = try container.decode(URLDecoder.self, forKey: .staticURL)
|
||||
self.visibleInPicker = try container.decode(Bool.self, forKey: .visibleInPicker)
|
||||
self.category = try container.decodeIfPresent(String.self, forKey: .category)
|
||||
}
|
||||
|
|
|
@ -7,12 +7,10 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
import WebURLFoundationExtras
|
||||
|
||||
public struct Hashtag: Codable, Sendable {
|
||||
public let name: String
|
||||
public let url: WebURL
|
||||
@URLDecoder public var url: URL
|
||||
/// Only present when returned from the trending hashtags endpoint
|
||||
public let history: [History]?
|
||||
/// Only present on Mastodon >= 4 and when logged in
|
||||
|
@ -20,7 +18,7 @@ public struct Hashtag: Codable, Sendable {
|
|||
|
||||
public init(name: String, url: URL) {
|
||||
self.name = name
|
||||
self.url = WebURL(url)!
|
||||
self.url = url
|
||||
self.history = nil
|
||||
self.following = nil
|
||||
}
|
||||
|
@ -29,7 +27,7 @@ public struct Hashtag: Codable, Sendable {
|
|||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.name = try container.decode(String.self, forKey: .name)
|
||||
// pixelfed (possibly others) don't fully escape special characters in the hashtag url
|
||||
self.url = try container.decode(WebURL.self, forKey: .url)
|
||||
self._url = try container.decode(URLDecoder.self, forKey: .url)
|
||||
self.history = try container.decodeIfPresent([History].self, forKey: .history)
|
||||
self.following = try container.decodeIfPresent(Bool.self, forKey: .following)
|
||||
}
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct Mention: Codable, Sendable {
|
||||
public let url: WebURL
|
||||
@URLDecoder public var url: URL
|
||||
public let username: String
|
||||
public let acct: String
|
||||
/// The instance-local ID of the user being mentioned.
|
||||
|
@ -21,15 +20,10 @@ public struct Mention: Codable, Sendable {
|
|||
self.username = try container.decode(String.self, forKey: .username)
|
||||
self.acct = try container.decode(String.self, forKey: .acct)
|
||||
self.id = try container.decode(String.self, forKey: .id)
|
||||
do {
|
||||
self.url = try container.decode(WebURL.self, forKey: .url)
|
||||
} catch {
|
||||
let s = try? container.decode(String.self, forKey: .url)
|
||||
throw DecodingError.dataCorruptedError(forKey: .url, in: container, debugDescription: "Could not decode URL '\(s ?? "<failed to decode string>")'")
|
||||
}
|
||||
self._url = try container.decode(URLDecoder.self, forKey: .url)
|
||||
}
|
||||
|
||||
public init(url: WebURL, username: String, acct: String, id: String) {
|
||||
public init(url: URL, username: String, acct: String, id: String) {
|
||||
self.url = url
|
||||
self.username = username
|
||||
self.acct = acct
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct Notification: Decodable, Sendable {
|
||||
public let id: String
|
||||
|
@ -18,7 +17,7 @@ public struct Notification: Decodable, Sendable {
|
|||
// Only present for pleroma emoji reactions
|
||||
// Either an emoji or :shortcode: (for akkoma custom emoji reactions)
|
||||
public let emoji: String?
|
||||
public let emojiURL: WebURL?
|
||||
@OptionalURLDecoder public var emojiURL: URL?
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
@ -33,7 +32,7 @@ public struct Notification: Decodable, Sendable {
|
|||
self.account = try container.decode(Account.self, forKey: .account)
|
||||
self.status = try container.decodeIfPresent(Status.self, forKey: .status)
|
||||
self.emoji = try container.decodeIfPresent(String.self, forKey: .emoji)
|
||||
self.emojiURL = try container.decodeIfPresent(WebURL.self, forKey: .emojiURL)
|
||||
self._emojiURL = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .emojiURL) ?? nil
|
||||
}
|
||||
|
||||
public static func dismiss(id notificationID: String) -> Request<Empty> {
|
||||
|
|
|
@ -6,14 +6,13 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct PushNotification: Decodable {
|
||||
public let accessToken: String
|
||||
public let preferredLocale: String
|
||||
public let notificationID: String
|
||||
public let notificationType: Notification.Kind
|
||||
public let icon: WebURL
|
||||
@URLDecoder public var icon: URL
|
||||
public let title: String
|
||||
public let body: String
|
||||
|
||||
|
@ -29,7 +28,7 @@ public struct PushNotification: Decodable {
|
|||
self.notificationID = i.description
|
||||
}
|
||||
self.notificationType = try container.decode(Notification.Kind.self, forKey: .notificationType)
|
||||
self.icon = try container.decode(WebURL.self, forKey: .icon)
|
||||
self._icon = try container.decode(URLDecoder.self, forKey: .icon)
|
||||
self.title = try container.decode(String.self, forKey: .title)
|
||||
self.body = try container.decode(String.self, forKey: .body)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public final class Status: StatusProtocol, Decodable, Sendable {
|
||||
/// The pseudo-visibility used by instance types (Akkoma) that overload the visibility for local-only posts.
|
||||
|
@ -15,7 +14,8 @@ public final class Status: StatusProtocol, Decodable, Sendable {
|
|||
|
||||
public let id: String
|
||||
public let uri: String
|
||||
public let url: WebURL?
|
||||
private let _url: OptionalURLDecoder
|
||||
public var url: URL? { _url.wrappedValue }
|
||||
public let account: Account
|
||||
public let inReplyToID: String?
|
||||
public let inReplyToAccountID: String?
|
||||
|
@ -55,13 +55,13 @@ public final class Status: StatusProtocol, Decodable, Sendable {
|
|||
self.id = try container.decode(String.self, forKey: .id)
|
||||
self.uri = try container.decode(String.self, forKey: .uri)
|
||||
do {
|
||||
self.url = try container.decodeIfPresent(WebURL.self, forKey: .url)
|
||||
self._url = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .url) ?? nil
|
||||
} catch {
|
||||
let s = try? container.decode(String.self, forKey: .url)
|
||||
if s == "" {
|
||||
self.url = nil
|
||||
self._url = OptionalURLDecoder(wrappedValue: nil)
|
||||
} else {
|
||||
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath + [CodingKeys.url], debugDescription: "Could not decode URL '\(s ?? "<failed to decode string>")'", underlyingError: error))
|
||||
throw error
|
||||
}
|
||||
}
|
||||
self.account = try container.decode(Account.self, forKey: .account)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct NotificationGroup: Identifiable, Hashable, Sendable {
|
||||
public private(set) var notifications: [Notification]
|
||||
|
@ -150,7 +149,7 @@ public struct NotificationGroup: Identifiable, Hashable, Sendable {
|
|||
case poll
|
||||
case update
|
||||
case status
|
||||
case emojiReaction(String, WebURL?)
|
||||
case emojiReaction(String, URL?)
|
||||
case unknown
|
||||
|
||||
var notificationKind: Notification.Kind {
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// URLDecoder.swift
|
||||
// Pachyderm
|
||||
//
|
||||
// Created by Shadowfacts on 12/15/24.
|
||||
//
|
||||
import Foundation
|
||||
|
||||
private let parseStrategy = URL.ParseStrategy()
|
||||
.scheme(.required)
|
||||
.user(.optional)
|
||||
.password(.optional)
|
||||
.host(.required)
|
||||
.port(.optional)
|
||||
.path(.optional)
|
||||
.query(.optional)
|
||||
.fragment(.optional)
|
||||
|
||||
private let formatStyle = URL.FormatStyle()
|
||||
.scheme(.always)
|
||||
.user(.omitWhen(.user, matches: [""]))
|
||||
.password(.omitWhen(.password, matches: [""]))
|
||||
.host(.always)
|
||||
.port(.omitIfHTTPFamily)
|
||||
.path(.always)
|
||||
.query(.omitWhen(.query, matches: [""]))
|
||||
.fragment(.omitWhen(.fragment, matches: [""]))
|
||||
|
||||
@propertyWrapper
|
||||
public struct URLDecoder: Codable, Sendable, Hashable {
|
||||
public var wrappedValue: URL
|
||||
|
||||
public init(wrappedValue: URL) {
|
||||
self.wrappedValue = wrappedValue
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let s = try decoder.singleValueContainer().decode(String.self)
|
||||
self.wrappedValue = try parseStrategy.parse(s)
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(wrappedValue.formatted(formatStyle))
|
||||
}
|
||||
}
|
||||
|
||||
@propertyWrapper
|
||||
public struct OptionalURLDecoder: Codable, Sendable, Hashable, ExpressibleByNilLiteral {
|
||||
public var wrappedValue: URL?
|
||||
|
||||
public init(wrappedValue: URL?) {
|
||||
self.wrappedValue = wrappedValue
|
||||
}
|
||||
|
||||
public init(nilLiteral: ()) {
|
||||
self.wrappedValue = nil
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
if container.decodeNil() {
|
||||
self.wrappedValue = nil
|
||||
} else {
|
||||
let s = try container.decode(String.self)
|
||||
do {
|
||||
self.wrappedValue = try parseStrategy.parse(s)
|
||||
} catch {
|
||||
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Could not decode URL '\(s)'", underlyingError: error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
if let wrappedValue {
|
||||
try container.encode(wrappedValue.formatted(formatStyle))
|
||||
} else {
|
||||
try container.encodeNil()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,20 +6,21 @@
|
|||
//
|
||||
|
||||
import XCTest
|
||||
import WebURL
|
||||
import WebURLFoundationExtras
|
||||
@testable import Pachyderm
|
||||
|
||||
class URLTests: XCTestCase {
|
||||
|
||||
func testDecodeURL() {
|
||||
XCTAssertNotNil(WebURL(URL(string: "https://xn--baw-joa.social/@unituebingen")!))
|
||||
XCTAssertNotNil(WebURL("https://xn--baw-joa.social/@unituebingen"))
|
||||
XCTAssertNotNil(URLComponents(string: "https://xn--baw-joa.social/test/é"))
|
||||
XCTAssertNotNil(WebURL("https://xn--baw-joa.social/test/é"))
|
||||
if #available(iOS 16.0, *) {
|
||||
XCTAssertNotNil(try? URL.ParseStrategy().parse("https://xn--baw-joa.social/test/é"))
|
||||
XCTAssertNotNil(try? URL.ParseStrategy().parse("http://見.香港/热狗/🌭"))
|
||||
}
|
||||
XCTAssertNotNil(try? URL.ParseStrategy().parse("https://xn--baw-joa.social/test/é"))
|
||||
XCTAssertNotNil(try? URL.ParseStrategy().parse("http://見.香港/热狗/🌭"))
|
||||
}
|
||||
|
||||
func testRoundtripURL() throws {
|
||||
let orig = URLDecoder(wrappedValue: URL(string: "https://example.com")!)
|
||||
let encoded = try JSONEncoder().encode(orig)
|
||||
print(String(data: encoded, encoding: .utf8)!)
|
||||
let decoded = try JSONDecoder().decode(URLDecoder.self, from: encoded)
|
||||
XCTAssertEqual(orig.wrappedValue, decoded.wrappedValue)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import SwiftUI
|
||||
import ComposeUI
|
||||
import TuskerComponents
|
||||
import WebURLFoundationExtras
|
||||
import Combine
|
||||
import TuskerPreferences
|
||||
|
||||
|
@ -46,7 +45,7 @@ class ShareHostingController: UIHostingController<ShareHostingController.View> {
|
|||
currentAccountContainerView: { AnyView(SwitchAccountContainerView(content: $0, mastodonContextPublisher: mastodonContextPublisher)) },
|
||||
replyContentView: { _, _ in fatalError("replies aren't allowed in share sheet") },
|
||||
emojiImageView: {
|
||||
AnyView(AsyncImage(url: URL($0.url)!) {
|
||||
AnyView(AsyncImage(url: $0.url) {
|
||||
$0
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
|
|
|
@ -103,7 +103,6 @@
|
|||
D630C3E12BC61C6700208903 /* UserAccounts in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3E02BC61C6700208903 /* UserAccounts */; };
|
||||
D630C3E52BC6313400208903 /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3E42BC6313400208903 /* Pachyderm */; };
|
||||
D630C4232BC7842C00208903 /* HTMLStreamer in Frameworks */ = {isa = PBXBuildFile; productRef = D630C4222BC7842C00208903 /* HTMLStreamer */; };
|
||||
D630C4252BC7845800208903 /* WebURL in Frameworks */ = {isa = PBXBuildFile; productRef = D630C4242BC7845800208903 /* WebURL */; };
|
||||
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; };
|
||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; };
|
||||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
|
||||
|
@ -164,7 +163,6 @@
|
|||
D6620ACE2511A0ED00312CA0 /* StatusStateResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */; };
|
||||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; };
|
||||
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; };
|
||||
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = D6676CA427A8D0020052936B /* WebURLFoundationExtras */; };
|
||||
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; };
|
||||
D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */; };
|
||||
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D674A50827F9128D00BA03AC /* Pachyderm */; };
|
||||
|
@ -818,7 +816,6 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D630C4252BC7845800208903 /* WebURL in Frameworks */,
|
||||
D62220472C7EA8DF003E43B7 /* TuskerPreferences in Frameworks */,
|
||||
D630C4232BC7842C00208903 /* HTMLStreamer in Frameworks */,
|
||||
D630C3E52BC6313400208903 /* Pachyderm in Frameworks */,
|
||||
|
@ -855,7 +852,6 @@
|
|||
D6934F2C2BA7AD32002B1C8D /* GalleryVC in Frameworks */,
|
||||
D6552367289870790048A653 /* ScreenCorners in Frameworks */,
|
||||
D60BB3942B30076F00DAEA65 /* HTMLStreamer in Frameworks */,
|
||||
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */,
|
||||
D63CC702290EC0B8000E19DE /* Sentry in Frameworks */,
|
||||
D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */,
|
||||
);
|
||||
|
@ -1781,7 +1777,6 @@
|
|||
D630C3E02BC61C6700208903 /* UserAccounts */,
|
||||
D630C3E42BC6313400208903 /* Pachyderm */,
|
||||
D630C4222BC7842C00208903 /* HTMLStreamer */,
|
||||
D630C4242BC7845800208903 /* WebURL */,
|
||||
D62220462C7EA8DF003E43B7 /* TuskerPreferences */,
|
||||
);
|
||||
productName = NotificationExtension;
|
||||
|
@ -1833,7 +1828,6 @@
|
|||
);
|
||||
name = Tusker;
|
||||
packageProductDependencies = (
|
||||
D6676CA427A8D0020052936B /* WebURLFoundationExtras */,
|
||||
D674A50827F9128D00BA03AC /* Pachyderm */,
|
||||
D6552366289870790048A653 /* ScreenCorners */,
|
||||
D63CC701290EC0B8000E19DE /* Sentry */,
|
||||
|
@ -1960,7 +1954,6 @@
|
|||
);
|
||||
mainGroup = D6D4DDC3212518A000E1C4BB;
|
||||
packageReferences = (
|
||||
D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */,
|
||||
D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */,
|
||||
D63CC700290EC0B8000E19DE /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
|
||||
D60BB3922B30076F00DAEA65 /* XCRemoteSwiftPackageReference "HTMLStreamer" */,
|
||||
|
@ -3274,14 +3267,6 @@
|
|||
minimumVersion = 1.0.1;
|
||||
};
|
||||
};
|
||||
D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/karwa/swift-url";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 0.4.2;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
|
@ -3319,11 +3304,6 @@
|
|||
package = D60BB3922B30076F00DAEA65 /* XCRemoteSwiftPackageReference "HTMLStreamer" */;
|
||||
productName = HTMLStreamer;
|
||||
};
|
||||
D630C4242BC7845800208903 /* WebURL */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */;
|
||||
productName = WebURL;
|
||||
};
|
||||
D635237029B78A7D009ED5E7 /* TuskerComponents */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = TuskerComponents;
|
||||
|
@ -3342,11 +3322,6 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
productName = TTTKit;
|
||||
};
|
||||
D6676CA427A8D0020052936B /* WebURLFoundationExtras */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */;
|
||||
productName = WebURLFoundationExtras;
|
||||
};
|
||||
D674A50827F9128D00BA03AC /* Pachyderm */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Pachyderm;
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
@objc(FollowedHashtag)
|
||||
public final class FollowedHashtag: NSManagedObject {
|
||||
|
@ -33,6 +32,6 @@ extension FollowedHashtag {
|
|||
convenience init(hashtag: Hashtag, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
self.name = hashtag.name
|
||||
self.url = URL(hashtag.url)!
|
||||
self.url = hashtag.url
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import UserAccounts
|
||||
|
||||
@objc(SavedHashtag)
|
||||
|
@ -42,6 +41,6 @@ extension SavedHashtag {
|
|||
self.init(context: context)
|
||||
self.accountID = account.id
|
||||
self.name = hashtag.name
|
||||
self.url = URL(hashtag.url)!
|
||||
self.url = hashtag.url
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
@objc(StatusMO)
|
||||
public final class StatusMO: NSManagedObject, StatusProtocol {
|
||||
|
@ -136,7 +135,7 @@ extension StatusMO {
|
|||
self.sensitive = status.sensitive
|
||||
self.spoilerText = status.spoilerText
|
||||
self.uri = status.uri
|
||||
self.url = status.url != nil ? URL(status.url!) : nil
|
||||
self.url = status.url
|
||||
self.visibility = status.visibility
|
||||
self.poll = status.poll
|
||||
self.localOnly = status.localOnly ?? false
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import HTMLStreamer
|
||||
import WebURL
|
||||
import WebURLFoundationExtras
|
||||
import Pachyderm
|
||||
|
||||
class HTMLConverter {
|
||||
|
||||
|
@ -45,17 +44,7 @@ extension HTMLConverter {
|
|||
// note: this is duplicated in NotificationExtension
|
||||
struct Callbacks: HTMLConversionCallbacks {
|
||||
static func makeURL(string: String) -> URL? {
|
||||
// Converting WebURL to URL is a small but non-trivial expense (since it works by
|
||||
// serializing the WebURL as a string and then having Foundation parse it again),
|
||||
// so, if available, use the system parser which doesn't require another round trip.
|
||||
if let url = try? URL.ParseStrategy().parse(string) {
|
||||
url
|
||||
} else if let web = WebURL(string),
|
||||
let url = URL(web) {
|
||||
url
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
try? URL.ParseStrategy().parse(string)
|
||||
}
|
||||
|
||||
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURL
|
||||
|
||||
class AnnouncementContentTextView: ContentTextView {
|
||||
|
||||
|
@ -30,7 +29,7 @@ class AnnouncementContentTextView: ContentTextView {
|
|||
|
||||
override func getMention(for url: URL, text: String) -> Mention? {
|
||||
announcement?.mentions.first {
|
||||
URL($0.url) == url
|
||||
$0.url == url
|
||||
}.map {
|
||||
Mention(url: $0.url, username: $0.username, acct: $0.acct, id: $0.id)
|
||||
}
|
||||
|
@ -38,7 +37,7 @@ class AnnouncementContentTextView: ContentTextView {
|
|||
|
||||
override func getHashtag(for url: URL, text: String) -> Hashtag? {
|
||||
announcement?.tags.first {
|
||||
URL($0.url) == url
|
||||
$0.url == url
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import SwiftUI
|
||||
import Pachyderm
|
||||
import TuskerComponents
|
||||
import WebURLFoundationExtras
|
||||
|
||||
struct AnnouncementListRow: View {
|
||||
@Binding var announcement: Announcement
|
||||
|
@ -116,8 +115,8 @@ struct AnnouncementListRow: View {
|
|||
let url: URL?
|
||||
let staticURL: URL?
|
||||
if case .custom(let emoji) = reaction {
|
||||
url = URL(emoji.url)
|
||||
staticURL = URL(emoji.staticURL)
|
||||
url = emoji.url
|
||||
staticURL = emoji.staticURL
|
||||
} else {
|
||||
url = nil
|
||||
staticURL = nil
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURL
|
||||
import WebURLFoundationExtras
|
||||
|
||||
private let mastodonRemoteStatusRegex = try! NSRegularExpression(pattern: "^/@.+@.+/\\d{18}")
|
||||
private func isLikelyMastodonRemoteStatus(url: URL) -> Bool {
|
||||
|
@ -229,10 +227,10 @@ class ConversationViewController: UIViewController {
|
|||
let location = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "location") {
|
||||
effectiveURL = location
|
||||
} else {
|
||||
effectiveURL = WebURL(url)!.serialized(excludingFragment: true)
|
||||
effectiveURL = url.formatted(.url.fragment(.never))
|
||||
}
|
||||
} else {
|
||||
effectiveURL = WebURL(url)!.serialized(excludingFragment: true)
|
||||
effectiveURL = url.formatted(.url.fragment(.never))
|
||||
}
|
||||
|
||||
let request = Client.search(query: effectiveURL, types: [.statuses], resolve: true)
|
||||
|
|
|
@ -10,7 +10,6 @@ import UIKit
|
|||
import Combine
|
||||
import Pachyderm
|
||||
import CoreData
|
||||
import WebURLFoundationExtras
|
||||
|
||||
class ExploreViewController: UIViewController, UICollectionViewDelegate, CollectionViewController {
|
||||
|
||||
|
@ -560,10 +559,7 @@ extension ExploreViewController: UICollectionViewDragDelegate {
|
|||
activity.displaysAuxiliaryScene = true
|
||||
provider = NSItemProvider(object: activity)
|
||||
case let .savedHashtag(hashtag):
|
||||
guard let url = URL(hashtag.url) else {
|
||||
return []
|
||||
}
|
||||
provider = NSItemProvider(object: url as NSURL)
|
||||
provider = NSItemProvider(object: hashtag.url as NSURL)
|
||||
if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: accountID) {
|
||||
activity.displaysAuxiliaryScene = true
|
||||
provider.registerObject(activity, visibility: .all)
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import Combine
|
||||
|
||||
class TrendingHashtagsViewController: UIViewController, CollectionViewController {
|
||||
|
@ -277,11 +276,10 @@ extension TrendingHashtagsViewController: UICollectionViewDelegate {
|
|||
extension TrendingHashtagsViewController: UICollectionViewDragDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||
case let .tag(hashtag) = item,
|
||||
let url = URL(hashtag.url) else {
|
||||
case let .tag(hashtag) = item else {
|
||||
return []
|
||||
}
|
||||
let provider = NSItemProvider(object: url as NSURL)
|
||||
let provider = NSItemProvider(object: hashtag.url as NSURL)
|
||||
if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: mastodonController.accountInfo!.id) {
|
||||
activity.displaysAuxiliaryScene = true
|
||||
provider.registerObject(activity, visibility: .all)
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#if !os(visionOS)
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import HTMLStreamer
|
||||
|
||||
class TrendingLinkCardCollectionViewCell: UICollectionViewCell {
|
||||
|
@ -71,7 +70,7 @@ class TrendingLinkCardCollectionViewCell: UICollectionViewCell {
|
|||
self.card = card
|
||||
self.thumbnailView.image = nil
|
||||
|
||||
thumbnailView.update(for: card.image.flatMap { URL($0) }, blurhash: card.blurhash)
|
||||
thumbnailView.update(for: card.image, blurhash: card.blurhash)
|
||||
|
||||
let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
titleLabel.text = title
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#if os(visionOS)
|
||||
import SwiftUI
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import HTMLStreamer
|
||||
|
||||
struct TrendingLinkCardView: View {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import SafariServices
|
||||
import Combine
|
||||
#if os(visionOS)
|
||||
|
@ -293,21 +292,19 @@ extension TrendingLinksViewController: UICollectionViewDelegate {
|
|||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard case .link(let card) = dataSource.itemIdentifier(for: indexPath),
|
||||
let url = URL(card.url) else {
|
||||
guard case .link(let card) = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return
|
||||
}
|
||||
selected(url: url)
|
||||
selected(url: card.url)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard case .link(let card) = dataSource.itemIdentifier(for: indexPath),
|
||||
let url = URL(card.url),
|
||||
let cell = collectionView.cellForItem(at: indexPath) else {
|
||||
return nil
|
||||
}
|
||||
return UIContextMenuConfiguration {
|
||||
let vc = SFSafariViewController(url: url)
|
||||
let vc = SFSafariViewController(url: card.url)
|
||||
#if !os(visionOS)
|
||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||
#endif
|
||||
|
@ -324,11 +321,10 @@ extension TrendingLinksViewController: UICollectionViewDelegate {
|
|||
|
||||
extension TrendingLinksViewController: UICollectionViewDragDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
guard case .link(let card) = dataSource.itemIdentifier(for: indexPath),
|
||||
let url = URL(card.url) else {
|
||||
guard case .link(let card) = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return []
|
||||
}
|
||||
return [UIDragItem(itemProvider: NSItemProvider(object: url as NSURL))]
|
||||
return [UIDragItem(itemProvider: NSItemProvider(object: card.url as NSURL))]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -513,9 +513,7 @@ extension TrendsViewController: UICollectionViewDelegate {
|
|||
show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case let .link(card):
|
||||
if let url = URL(card.url) {
|
||||
selected(url: url)
|
||||
}
|
||||
selected(url: card.url)
|
||||
|
||||
case let .status(id, state):
|
||||
selected(status: id, state: state.copy())
|
||||
|
@ -544,12 +542,9 @@ extension TrendsViewController: UICollectionViewDelegate {
|
|||
}
|
||||
|
||||
case let .link(card):
|
||||
guard let url = URL(card.url) else {
|
||||
return nil
|
||||
}
|
||||
let cell = collectionView.cellForItem(at: indexPath)!
|
||||
return UIContextMenuConfiguration {
|
||||
let vc = SFSafariViewController(url: url)
|
||||
let vc = SFSafariViewController(url: card.url)
|
||||
#if !os(visionOS)
|
||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||
#endif
|
||||
|
@ -624,10 +619,7 @@ extension TrendsViewController: UICollectionViewDragDelegate {
|
|||
return []
|
||||
|
||||
case let .tag(hashtag):
|
||||
guard let url = URL(hashtag.url) else {
|
||||
return []
|
||||
}
|
||||
let provider = NSItemProvider(object: url as NSURL)
|
||||
let provider = NSItemProvider(object: hashtag.url as NSURL)
|
||||
if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: mastodonController.accountInfo!.id) {
|
||||
activity.displaysAuxiliaryScene = true
|
||||
provider.registerObject(activity, visibility: .all)
|
||||
|
@ -635,10 +627,7 @@ extension TrendsViewController: UICollectionViewDragDelegate {
|
|||
return [UIDragItem(itemProvider: provider)]
|
||||
|
||||
case let .link(card):
|
||||
guard let url = URL(card.url) else {
|
||||
return []
|
||||
}
|
||||
return [UIDragItem(itemProvider: NSItemProvider(object: url as NSURL))]
|
||||
return [UIDragItem(itemProvider: NSItemProvider(object: card.url as NSURL))]
|
||||
|
||||
case let .status(id, _):
|
||||
guard let status = mastodonController.persistentContainer.status(for: id),
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
import UserAccounts
|
||||
import WebURL
|
||||
|
||||
class FastSwitchingAccountView: UIView {
|
||||
|
||||
|
@ -131,11 +130,7 @@ class FastSwitchingAccountView: UIView {
|
|||
|
||||
private func setupAccount(account: UserAccountInfo) {
|
||||
usernameLabel.text = account.username
|
||||
if let domain = WebURL.Domain(account.instanceURL.host!) {
|
||||
instanceLabel.text = domain.render(.uncheckedUnicodeString)
|
||||
} else {
|
||||
instanceLabel.text = account.instanceURL.host!
|
||||
}
|
||||
instanceLabel.text = account.instanceURL.host(percentEncoded: false)
|
||||
let controller = MastodonController.getForAccount(account)
|
||||
avatarTask = Task {
|
||||
guard let account = try? await controller.getOwnAccount(),
|
||||
|
|
|
@ -155,7 +155,7 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
|||
fetchCustomEmojiImage?.1.cancel()
|
||||
case .emojiReaction(let emojiOrShortcode, let url):
|
||||
iconImageView.image = nil
|
||||
if let url = url.flatMap({ URL($0) }),
|
||||
if let url,
|
||||
fetchCustomEmojiImage?.0 != url {
|
||||
fetchCustomEmojiImage?.1.cancel()
|
||||
let task = Task {
|
||||
|
|
|
@ -740,7 +740,7 @@ extension NotificationsCollectionViewController: UICollectionViewDragDelegate {
|
|||
return cell.dragItemsForBeginning(session: session)
|
||||
case .poll, .update:
|
||||
let status = group.notifications.first!.status!
|
||||
let provider = NSItemProvider(object: URL(status.url!)! as NSURL)
|
||||
let provider = NSItemProvider(object: status.url! as NSURL)
|
||||
let activity = UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: mastodonController.accountInfo!.id)
|
||||
activity.displaysAuxiliaryScene = true
|
||||
provider.registerObject(activity, visibility: .all)
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import SwiftUI
|
||||
import Pachyderm
|
||||
import WebURL
|
||||
|
||||
struct MockStatusView: View {
|
||||
@ObservedObject private var preferences = Preferences.shared
|
||||
|
@ -136,8 +135,8 @@ private struct MockStatusCardView: UIViewRepresentable {
|
|||
let view = StatusCardView()
|
||||
view.isUserInteractionEnabled = false
|
||||
let card = StatusCardView.CardData(
|
||||
url: WebURL("https://vaccor.space/tusker")!,
|
||||
image: WebURL("https://vaccor.space/tusker/img/icon.png")!,
|
||||
url: URL(string: "https://vaccor.space/tusker")!,
|
||||
image: URL(string: "https://vaccor.space/tusker/img/icon.png")!,
|
||||
title: "Tusker",
|
||||
description: "Tusker is an iOS app for Mastodon"
|
||||
)
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import SwiftUI
|
||||
import UserAccounts
|
||||
import WebURL
|
||||
|
||||
struct PrefsAccountView: View {
|
||||
let account: UserAccountInfo
|
||||
|
@ -19,12 +18,7 @@ struct PrefsAccountView: View {
|
|||
VStack(alignment: .prefsAvatar) {
|
||||
Text(verbatim: account.username)
|
||||
.foregroundColor(.primary)
|
||||
let instance = if let domain = WebURL.Domain(account.instanceURL.host!) {
|
||||
domain.render(.uncheckedUnicodeString)
|
||||
} else {
|
||||
account.instanceURL.host!
|
||||
}
|
||||
Text(verbatim: instance)
|
||||
Text(verbatim: account.instanceURL.host(percentEncoded: false)!)
|
||||
.font(.caption)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import UIKit
|
||||
import Combine
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
fileprivate let accountCell = "accountCell"
|
||||
fileprivate let statusCell = "statusCell"
|
||||
|
@ -538,7 +537,7 @@ extension SearchResultsViewController: UICollectionViewDragDelegate {
|
|||
url = account.url
|
||||
activity = UserActivityManager.showProfileActivity(id: id, accountID: accountInfo.id)
|
||||
case .hashtag(let tag):
|
||||
url = URL(tag.url)!
|
||||
url = tag.url
|
||||
activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: tag.name), accountID: accountInfo.id)!
|
||||
case .status(let id, _):
|
||||
guard let status = mastodonController.persistentContainer.status(for: id),
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURL
|
||||
|
||||
class StatusActionAccountListViewController: UIViewController {
|
||||
|
||||
|
@ -183,7 +182,7 @@ extension StatusActionAccountListViewController {
|
|||
enum ActionType {
|
||||
case favorite
|
||||
case reblog
|
||||
case emojiReaction(String, WebURL?)
|
||||
case emojiReaction(String, URL?)
|
||||
|
||||
init?(_ groupKind: NotificationGroup.Kind) {
|
||||
switch groupKind {
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import UIKit
|
||||
import SafariServices
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
|
@ -154,12 +153,7 @@ extension MenuActionProvider {
|
|||
}
|
||||
}
|
||||
|
||||
let shareSection: [UIMenuElement]
|
||||
if let url = URL(hashtag.url) {
|
||||
shareSection = actionsForURL(url, source: source)
|
||||
} else {
|
||||
shareSection = []
|
||||
}
|
||||
let shareSection = actionsForURL(hashtag.url, source: source)
|
||||
|
||||
return [
|
||||
UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection),
|
||||
|
@ -375,14 +369,11 @@ extension MenuActionProvider {
|
|||
}
|
||||
|
||||
func actionsForTrendingLink(card: Card, source: PopoverSource) -> [UIMenuElement] {
|
||||
guard let url = URL(card.url) else {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
openInSafariAction(url: url),
|
||||
openInSafariAction(url: card.url),
|
||||
createAction(identifier: "share", title: "Share…", systemImageName: "square.and.arrow.up", handler: { [weak self] (_) in
|
||||
guard let self else { return }
|
||||
self.navigationDelegate?.showMoreOptions(forURL: url, source: source)
|
||||
self.navigationDelegate?.showMoreOptions(forURL: card.url, source: source)
|
||||
}),
|
||||
createAction(identifier: "postlink", title: "Post this Link", systemImageName: "square.and.pencil", handler: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
@ -393,7 +384,7 @@ extension MenuActionProvider {
|
|||
text += title
|
||||
text += ":\n"
|
||||
}
|
||||
text += url.absoluteString
|
||||
text += card.url.absoluteString
|
||||
|
||||
let draft = self.mastodonController!.createDraft(text: text)
|
||||
self.navigationDelegate?.compose(editing: draft)
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import SwiftUI
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import os
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
|
@ -54,7 +53,7 @@ struct AccountDisplayNameView: View {
|
|||
}
|
||||
|
||||
group.enter()
|
||||
let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in
|
||||
let request = ImageCache.emojis.get(emoji.url) { (_, image) in
|
||||
defer { group.leave() }
|
||||
guard let image = image else { return }
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
import os
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
|
@ -73,7 +72,7 @@ extension BaseEmojiLabel {
|
|||
|
||||
foundEmojis = true
|
||||
|
||||
if let image = ImageCache.emojis.get(URL(emoji.url)!)?.image {
|
||||
if let image = ImageCache.emojis.get(emoji.url)?.image {
|
||||
// if the image is cached, add it immediately.
|
||||
// we generate the thumbnail on the main thread, because it's usually fast enough
|
||||
// and the delay caused by doing it asynchronously looks works.
|
||||
|
@ -90,7 +89,7 @@ extension BaseEmojiLabel {
|
|||
// otherwise, perform the network request
|
||||
|
||||
group.enter()
|
||||
let request = ImageCache.emojis.getFromSource(URL(emoji.url)!) { (_, image) in
|
||||
let request = ImageCache.emojis.getFromSource(emoji.url) { (_, image) in
|
||||
guard let image else {
|
||||
group.leave()
|
||||
return
|
||||
|
@ -98,7 +97,7 @@ extension BaseEmojiLabel {
|
|||
image.prepareThumbnail(of: emojiImageSize(image)) { thumbnail in
|
||||
guard let thumbnail = thumbnail?.cgImage,
|
||||
case let rescaled = UIImage(cgImage: thumbnail, scale: screenScale, orientation: .up),
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: rescaled) else {
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: emoji.url, image: rescaled) else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
import SafariServices
|
||||
import WebURL
|
||||
import WebURLFoundationExtras
|
||||
import Combine
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import SwiftUI
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
struct CustomEmojiImageView: View {
|
||||
let emoji: Emoji
|
||||
|
@ -35,7 +34,7 @@ struct CustomEmojiImageView: View {
|
|||
|
||||
@MainActor
|
||||
private func loadImage() {
|
||||
request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in
|
||||
request = ImageCache.emojis.get(emoji.url) { (_, image) in
|
||||
DispatchQueue.main.async {
|
||||
self.request = nil
|
||||
if let image = image {
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
import SafariServices
|
||||
import WebURL
|
||||
import WebURLFoundationExtras
|
||||
import HTMLStreamer
|
||||
|
||||
class StatusCardView: UIView {
|
||||
|
@ -184,14 +182,14 @@ class StatusCardView: UIView {
|
|||
if sensitive {
|
||||
if let blurhash = card.blurhash {
|
||||
imageView.blurImage = false
|
||||
imageView.showOnlyBlurHash(blurhash, for: URL(image)!)
|
||||
imageView.showOnlyBlurHash(blurhash, for: image)
|
||||
} else {
|
||||
// if we don't have a blurhash, load the image and show it behind a blur
|
||||
imageView.blurImage = true
|
||||
imageView.update(for: URL(image), blurhash: nil)
|
||||
imageView.update(for: image, blurhash: nil)
|
||||
}
|
||||
} else {
|
||||
imageView.update(for: URL(image), blurhash: card.blurhash)
|
||||
imageView.update(for: image, blurhash: card.blurhash)
|
||||
}
|
||||
imageView.isHidden = false
|
||||
leadingSpacer.isHidden = true
|
||||
|
@ -210,8 +208,8 @@ class StatusCardView: UIView {
|
|||
descriptionLabel.text = description
|
||||
descriptionLabel.isHidden = description.isEmpty
|
||||
|
||||
if let host = card.url.host {
|
||||
domainLabel.text = host.serialized
|
||||
if let host = card.url.host(percentEncoded: false) {
|
||||
domainLabel.text = host
|
||||
domainLabel.isHidden = false
|
||||
} else {
|
||||
domainLabel.isHidden = true
|
||||
|
@ -238,7 +236,7 @@ class StatusCardView: UIView {
|
|||
setNeedsDisplay()
|
||||
|
||||
if let card = card, let delegate = navigationDelegate {
|
||||
delegate.selected(url: URL(card.url)!)
|
||||
delegate.selected(url: card.url)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,8 +246,8 @@ class StatusCardView: UIView {
|
|||
}
|
||||
|
||||
struct CardData: Equatable {
|
||||
let url: WebURL
|
||||
let image: WebURL?
|
||||
let url: URL
|
||||
let image: URL?
|
||||
let title: String
|
||||
let description: String
|
||||
let blurhash: String?
|
||||
|
@ -262,7 +260,7 @@ class StatusCardView: UIView {
|
|||
self.blurhash = card.blurhash
|
||||
}
|
||||
|
||||
init(url: WebURL, image: WebURL? = nil, title: String, description: String, blurhash: String? = nil) {
|
||||
init(url: URL, image: URL? = nil, title: String, description: String, blurhash: String? = nil) {
|
||||
self.url = url
|
||||
self.image = image
|
||||
self.title = title
|
||||
|
@ -278,13 +276,13 @@ extension StatusCardView: UIContextMenuInteractionDelegate {
|
|||
guard let card = card else { return nil }
|
||||
|
||||
return UIContextMenuConfiguration(identifier: nil) {
|
||||
let vc = SFSafariViewController(url: URL(card.url)!)
|
||||
let vc = SFSafariViewController(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)) ?? []
|
||||
let actions = self.actionProvider?.actionsForURL(card.url, source: .view(self)) ?? []
|
||||
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
class StatusContentTextView: ContentTextView {
|
||||
|
||||
|
@ -26,7 +25,7 @@ class StatusContentTextView: ContentTextView {
|
|||
let mastodonController = mastodonController,
|
||||
let status = mastodonController.persistentContainer.status(for: statusID) {
|
||||
mention = status.mentions.first { (mention) in
|
||||
url.host == mention.url.host!.serialized && (
|
||||
url.host() == mention.url.host() && (
|
||||
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
|
||||
|
@ -44,7 +43,7 @@ class StatusContentTextView: ContentTextView {
|
|||
let mastodonController = mastodonController,
|
||||
let status = mastodonController.persistentContainer.status(for: statusID) {
|
||||
hashtag = status.hashtags.first { (hashtag) in
|
||||
URL(hashtag.url) == url
|
||||
hashtag.url == url
|
||||
}
|
||||
} else {
|
||||
hashtag = nil
|
||||
|
|
Loading…
Reference in New Issue