Compare commits
4 Commits
5b70c713b2
...
9b3cc61dcb
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 9b3cc61dcb | |
Shadowfacts | 0c37b99a68 | |
Shadowfacts | f96d1d780c | |
Shadowfacts | 5a5364ad3b |
|
@ -16,7 +16,7 @@ let package = Package(
|
|||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
.package(url: "https://github.com/karwa/swift-url.git", from: "0.3.1"),
|
||||
.package(url: "https://github.com/karwa/swift-url.git", branch: "main"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
|
|
|
@ -12,13 +12,13 @@ import WebURLFoundationExtras
|
|||
|
||||
public class Hashtag: Codable {
|
||||
public let name: String
|
||||
public let url: URL
|
||||
public let url: WebURL
|
||||
/// Only present when returned from the trending hashtags endpoint
|
||||
public let history: [History]?
|
||||
|
||||
public init(name: String, url: URL) {
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.url = WebURL(url)!
|
||||
self.history = nil
|
||||
}
|
||||
|
||||
|
@ -26,24 +26,14 @@ public class Hashtag: Codable {
|
|||
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
|
||||
do {
|
||||
let webURL = try container.decode(WebURL.self, forKey: .url)
|
||||
if let url = URL(webURL) {
|
||||
self.url = url
|
||||
} else {
|
||||
let s = try? container.decode(String.self, forKey: .url)
|
||||
throw DecodingError.dataCorruptedError(forKey: .url, in: container, debugDescription: "unable to convert WebURL \(s?.debugDescription ?? "nil") to URL")
|
||||
}
|
||||
} catch {
|
||||
self.url = try container.decode(URL.self, forKey: .url)
|
||||
}
|
||||
self.url = try container.decode(WebURL.self, forKey: .url)
|
||||
self.history = try container.decodeIfPresent([History].self, forKey: .history)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(url.absoluteString, forKey: .url)
|
||||
try container.encode(url, forKey: .url)
|
||||
try container.encodeIfPresent(history, forKey: .history)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,9 @@
|
|||
|
||||
import Foundation
|
||||
import WebURL
|
||||
import WebURLFoundationExtras
|
||||
|
||||
public class Mention: Codable {
|
||||
public let url: URL
|
||||
public let url: WebURL
|
||||
public let username: String
|
||||
public let acct: String
|
||||
/// The instance-local ID of the user being mentioned.
|
||||
|
@ -22,17 +21,7 @@ public class Mention: Codable {
|
|||
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 {
|
||||
let webURL = try container.decode(WebURL.self, forKey: .url)
|
||||
if let url = URL(webURL) {
|
||||
self.url = url
|
||||
} else {
|
||||
let s = try? container.decode(String.self, forKey: .url)
|
||||
throw DecodingError.dataCorruptedError(forKey: .url, in: container, debugDescription: "unable to convert WebURL \(s?.debugDescription ?? "nil") to URL")
|
||||
}
|
||||
} catch {
|
||||
self.url = try container.decode(URL.self, forKey: .url)
|
||||
}
|
||||
self.url = try container.decode(WebURL.self, forKey: .url)
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
|
|
|
@ -12,9 +12,14 @@ import WebURLFoundationExtras
|
|||
class URLTests: XCTestCase {
|
||||
|
||||
func testDecodeURL() {
|
||||
print(WebURL(URL(string: "https://xn--baw-joa.social/@unituebingen")!))
|
||||
let url = WebURL("https://xn--baw-joa.social/@unituebingen")
|
||||
print(url)
|
||||
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://見.香港/热狗/🌭"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
@objc(SavedHashtag)
|
||||
public final class SavedHashtag: NSManagedObject {
|
||||
|
@ -32,6 +33,6 @@ extension SavedHashtag {
|
|||
convenience init(hashtag: Hashtag, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
self.name = hashtag.name
|
||||
self.url = hashtag.url
|
||||
self.url = URL(hashtag.url)!
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,9 @@ struct ComposeAttachmentsList: View {
|
|||
.listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
// todo: scrollDisabled doesn't remove the need for manually calculating the frame height
|
||||
.frame(height: totalListHeight)
|
||||
.scrollDisabledIfAvailable(totalHeight: totalListHeight)
|
||||
.onAppear(perform: self.didAppear)
|
||||
.onReceive(draft.$attachments, perform: self.attachmentsChanged)
|
||||
}
|
||||
|
@ -212,6 +214,16 @@ fileprivate extension View {
|
|||
self
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
@ViewBuilder
|
||||
func scrollDisabledIfAvailable(totalHeight: CGFloat) -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
self.scrollDisabled(true)
|
||||
} else {
|
||||
self.frame(height: totalHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
|||
import Combine
|
||||
import Pachyderm
|
||||
import CoreData
|
||||
import WebURLFoundationExtras
|
||||
|
||||
class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
||||
|
||||
|
@ -582,7 +583,10 @@ extension ExploreViewController: UICollectionViewDragDelegate {
|
|||
activity.displaysAuxiliaryScene = true
|
||||
provider = NSItemProvider(object: activity)
|
||||
case let .savedHashtag(hashtag):
|
||||
provider = NSItemProvider(object: hashtag.url as NSURL)
|
||||
guard let url = URL(hashtag.url) else {
|
||||
return []
|
||||
}
|
||||
provider = NSItemProvider(object: url as NSURL)
|
||||
if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: accountID) {
|
||||
activity.displaysAuxiliaryScene = true
|
||||
provider.registerObject(activity, visibility: .all)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
class TrendingHashtagsViewController: UIViewController {
|
||||
|
||||
|
@ -105,10 +106,11 @@ 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 else {
|
||||
case let .tag(hashtag) = item,
|
||||
let url = URL(hashtag.url) else {
|
||||
return []
|
||||
}
|
||||
let provider = NSItemProvider(object: hashtag.url as NSURL)
|
||||
let provider = NSItemProvider(object: 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,6 +9,7 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
import SafariServices
|
||||
import WebURLFoundationExtras
|
||||
|
||||
class SearchViewController: UIViewController {
|
||||
|
||||
|
@ -295,7 +296,10 @@ extension SearchViewController: UICollectionViewDragDelegate {
|
|||
}
|
||||
switch item {
|
||||
case let .tag(hashtag):
|
||||
let provider = NSItemProvider(object: hashtag.url as NSURL)
|
||||
guard let url = URL(hashtag.url) else {
|
||||
return []
|
||||
}
|
||||
let provider = NSItemProvider(object: 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,6 +9,7 @@
|
|||
import UIKit
|
||||
import SafariServices
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
protocol MenuActionProvider: AnyObject {
|
||||
var navigationDelegate: TuskerNavigationDelegate? { get }
|
||||
|
@ -116,7 +117,12 @@ extension MenuActionProvider {
|
|||
actionsSection = []
|
||||
}
|
||||
|
||||
let shareSection = actionsForURL(hashtag.url, sourceView: sourceView)
|
||||
let shareSection: [UIMenuElement]
|
||||
if let url = URL(hashtag.url) {
|
||||
shareSection = actionsForURL(url, sourceView: sourceView)
|
||||
} else {
|
||||
shareSection = []
|
||||
}
|
||||
|
||||
return [
|
||||
UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection),
|
||||
|
|
|
@ -270,8 +270,9 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
|||
|
||||
extension ContentTextView: UITextViewDelegate {
|
||||
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
||||
// disable the text view's link interactions, we handle tapping links ourself with a gesture recognizer
|
||||
return false
|
||||
// generally disable the text view's link interactions, we handle tapping links ourself with a gesture recognizer
|
||||
// the builtin data detectors use the x-apple-data-detectors scheme, and we allow the text view to handle those itself
|
||||
return URL.scheme == "x-apple-data-detectors"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,10 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
|||
]
|
||||
|
||||
contentTextView.defaultFont = .systemFont(ofSize: 18)
|
||||
contentTextView.dataDetectorTypes = [.flightNumber, .address, .shipmentTrackingNumber, .phoneNumber]
|
||||
if #available(iOS 16.0, *) {
|
||||
contentTextView.dataDetectorTypes.formUnion([.money, .physicalValue])
|
||||
}
|
||||
|
||||
profileDetailContainerView.addInteraction(UIContextMenuInteraction(delegate: self))
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21179.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21169.4"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
|
@ -108,7 +108,7 @@
|
|||
<action selector="collapseButtonPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="JaH-xX-UOD"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" delaysContentTouches="NO" editable="NO" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="waJ-f5-LKv" customClass="StatusContentTextView" customModule="Tusker" customModuleProvider="target">
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" delaysContentTouches="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="waJ-f5-LKv" customClass="StatusContentTextView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="83" width="277" height="82.5"/>
|
||||
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
|
||||
<color key="textColor" systemColor="labelColor"/>
|
||||
|
@ -272,14 +272,14 @@
|
|||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="arrowshape.turn.up.left.fill" catalog="system" width="128" height="106"/>
|
||||
<image name="chevron.down" catalog="system" width="128" height="72"/>
|
||||
<image name="arrowshape.turn.up.left.fill" catalog="system" width="128" height="104"/>
|
||||
<image name="chevron.down" catalog="system" width="128" height="70"/>
|
||||
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
||||
<image name="pin.fill" catalog="system" width="119" height="128"/>
|
||||
<image name="pin.fill" catalog="system" width="116" height="128"/>
|
||||
<image name="repeat" catalog="system" width="128" height="98"/>
|
||||
<image name="star.fill" catalog="system" width="128" height="116"/>
|
||||
<systemColor name="labelColor">
|
||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURLFoundationExtras
|
||||
|
||||
class StatusContentTextView: ContentTextView {
|
||||
|
||||
|
@ -27,7 +28,7 @@ class StatusContentTextView: ContentTextView {
|
|||
let status = mastodonController.persistentContainer.status(for: statusID) {
|
||||
mention = status.mentions.first { (mention) in
|
||||
// Mastodon and Pleroma include the @ in the <a> text, GNU Social does not
|
||||
(text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host!
|
||||
(text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host!.serialized
|
||||
}
|
||||
} else {
|
||||
mention = nil
|
||||
|
@ -41,7 +42,7 @@ class StatusContentTextView: ContentTextView {
|
|||
let mastodonController = mastodonController,
|
||||
let status = mastodonController.persistentContainer.status(for: statusID) {
|
||||
hashtag = status.hashtags.first { (hashtag) in
|
||||
hashtag.url == url
|
||||
URL(hashtag.url) == url
|
||||
}
|
||||
} else {
|
||||
hashtag = nil
|
||||
|
|
Loading…
Reference in New Issue