Replace content labels with text views
UITextView uses TextKit internally, unlike UILabel, so no additional code is needed to keep the TextKit and view representations of the text in sync since they are one and the same. This means that detecting which character was tapped in a content text view is much more accurate, which means link handling is substantially imrpoved. Fixes #20
This commit is contained in:
parent
23de131290
commit
b5a41badcc
|
@ -12,7 +12,6 @@
|
|||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */; };
|
||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427033922B31269000D31B6 /* AdvancedPrefsView.swift */; };
|
||||
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427037B22B316B9000D31B6 /* SilentActionPrefs.swift */; };
|
||||
04496BD721625361001F1B23 /* ContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04496BD621625361001F1B23 /* ContentLabel.swift */; };
|
||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450531E22B0097E00100BA2 /* Timline+UI.swift */; };
|
||||
0454DDAF22B462EF00B8BB8E /* GalleryExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0454DDAE22B462EF00B8BB8E /* GalleryExpandAnimationController.swift */; };
|
||||
0454DDB122B467AA00B8BB8E /* GalleryShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0454DDB022B467AA00B8BB8E /* GalleryShrinkAnimationController.swift */; };
|
||||
|
@ -74,6 +73,9 @@
|
|||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
|
||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
|
||||
D620483223D2A6A3008A63EF /* CompositionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483123D2A6A3008A63EF /* CompositionState.swift */; };
|
||||
D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; };
|
||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; };
|
||||
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; };
|
||||
D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; };
|
||||
D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachment.swift */; };
|
||||
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; };
|
||||
|
@ -199,7 +201,6 @@
|
|||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */; };
|
||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
||||
D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */; };
|
||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
|
||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
||||
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */; };
|
||||
|
@ -210,7 +211,6 @@
|
|||
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */; };
|
||||
D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDE4212518A200E1C4BB /* TuskerTests.swift */; };
|
||||
D6D4DDF0212518A200E1C4BB /* TuskerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDEF212518A200E1C4BB /* TuskerUITests.swift */; };
|
||||
D6D58DF922074B74009C8DD9 /* LinkLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D58DF822074B74009C8DD9 /* LinkLabel.swift */; };
|
||||
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */; };
|
||||
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */; };
|
||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0DC8D216EDF1E00369478 /* Previewing.swift */; };
|
||||
|
@ -282,7 +282,6 @@
|
|||
0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BehaviorPrefsView.swift; sourceTree = "<group>"; };
|
||||
0427033922B31269000D31B6 /* AdvancedPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPrefsView.swift; sourceTree = "<group>"; };
|
||||
0427037B22B316B9000D31B6 /* SilentActionPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SilentActionPrefs.swift; sourceTree = "<group>"; };
|
||||
04496BD621625361001F1B23 /* ContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLabel.swift; sourceTree = "<group>"; };
|
||||
0450531E22B0097E00100BA2 /* Timline+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timline+UI.swift"; sourceTree = "<group>"; };
|
||||
0454DDAE22B462EF00B8BB8E /* GalleryExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryExpandAnimationController.swift; sourceTree = "<group>"; };
|
||||
0454DDB022B467AA00B8BB8E /* GalleryShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryShrinkAnimationController.swift; sourceTree = "<group>"; };
|
||||
|
@ -346,6 +345,9 @@
|
|||
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>"; };
|
||||
D620483123D2A6A3008A63EF /* CompositionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionState.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>"; };
|
||||
D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; };
|
||||
D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||
D626493423BD94CE00612E6E /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; };
|
||||
D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; };
|
||||
|
@ -469,7 +471,6 @@
|
|||
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
|
||||
D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentLabel.swift; sourceTree = "<group>"; };
|
||||
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
||||
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
||||
D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentsContainerView.swift; sourceTree = "<group>"; };
|
||||
|
@ -486,7 +487,6 @@
|
|||
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TuskerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D6D4DDEF212518A200E1C4BB /* TuskerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerUITests.swift; sourceTree = "<group>"; };
|
||||
D6D4DDF1212518A200E1C4BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D6D58DF822074B74009C8DD9 /* LinkLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkLabel.swift; sourceTree = "<group>"; };
|
||||
D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningCopyMode.swift; sourceTree = "<group>"; };
|
||||
D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preferences+Notification.swift"; sourceTree = "<group>"; };
|
||||
D6E0DC8D216EDF1E00369478 /* Previewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Previewing.swift; sourceTree = "<group>"; };
|
||||
|
@ -856,6 +856,7 @@
|
|||
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
||||
D626493423BD94CE00612E6E /* CompositionAttachment.swift */,
|
||||
D6285B5221EA708700FE4B39 /* StatusFormat.swift */,
|
||||
D620483123D2A6A3008A63EF /* CompositionState.swift */,
|
||||
);
|
||||
path = Compose;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1099,9 +1100,9 @@
|
|||
D6BED1722126661300F02DA0 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6D58DF822074B74009C8DD9 /* LinkLabel.swift */,
|
||||
04496BD621625361001F1B23 /* ContentLabel.swift */,
|
||||
D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */,
|
||||
D620483323D3801D008A63EF /* LinkTextView.swift */,
|
||||
D620483523D38075008A63EF /* ContentTextView.swift */,
|
||||
D620483723D38190008A63EF /* StatusContentTextView.swift */,
|
||||
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
||||
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */,
|
||||
|
@ -1619,9 +1620,7 @@
|
|||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||
D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */,
|
||||
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */,
|
||||
D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */,
|
||||
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */,
|
||||
D6D58DF922074B74009C8DD9 /* LinkLabel.swift in Sources */,
|
||||
0454DDAF22B462EF00B8BB8E /* GalleryExpandAnimationController.swift in Sources */,
|
||||
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */,
|
||||
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
|
||||
|
@ -1631,6 +1630,7 @@
|
|||
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
|
||||
D6945C3623AC6C09005C403C /* SavedInstancesManager.swift in Sources */,
|
||||
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */,
|
||||
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */,
|
||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||
0411610022B442870030A9B7 /* AttachmentViewController.swift in Sources */,
|
||||
D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */,
|
||||
|
@ -1640,6 +1640,7 @@
|
|||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */,
|
||||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
|
||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
|
||||
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */,
|
||||
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
|
||||
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */,
|
||||
|
@ -1693,9 +1694,9 @@
|
|||
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */,
|
||||
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */,
|
||||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
|
||||
D620483423D3801D008A63EF /* LinkTextView.swift in Sources */,
|
||||
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */,
|
||||
D627943523A5525100D38C68 /* StatusActivity.swift in Sources */,
|
||||
04496BD721625361001F1B23 /* ContentLabel.swift in Sources */,
|
||||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */,
|
||||
|
@ -1715,6 +1716,7 @@
|
|||
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */,
|
||||
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
|
||||
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */,
|
||||
D620483223D2A6A3008A63EF /* CompositionState.swift in Sources */,
|
||||
D6BC9DB5232D4CE3002CA326 /* NotificationsMode.swift in Sources */,
|
||||
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,
|
||||
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
||||
|
|
|
@ -58,8 +58,12 @@ protocol TuskerNavigationDelegate {
|
|||
extension TuskerNavigationDelegate where Self: UIViewController {
|
||||
|
||||
func show(_ vc: UIViewController) {
|
||||
if vc is LargeImageViewController || vc is GalleryViewController || vc is SFSafariViewController {
|
||||
present(vc, animated: true)
|
||||
} else {
|
||||
show(vc, sender: self)
|
||||
}
|
||||
}
|
||||
|
||||
func selected(account accountID: String) {
|
||||
// don't open if the account is the same as the current one
|
||||
|
|
|
@ -14,7 +14,7 @@ class ComposeStatusReplyView: UIView {
|
|||
@IBOutlet weak var avatarImageView: UIImageView!
|
||||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var contentLabel: StatusContentLabel!
|
||||
@IBOutlet weak var statusContentTextView: StatusContentTextView!
|
||||
|
||||
static func create() -> ComposeStatusReplyView {
|
||||
return UINib(nibName: "ComposeStatusReplyView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeStatusReplyView
|
||||
|
@ -34,7 +34,7 @@ class ComposeStatusReplyView: UIView {
|
|||
func updateUI(for status: Status) {
|
||||
displayNameLabel.text = status.account.realDisplayName
|
||||
usernameLabel.text = "@\(status.account.acct)"
|
||||
contentLabel.statusID = status.id
|
||||
statusContentTextView.statusID = status.id
|
||||
|
||||
ImageCache.avatars.get(status.account.avatar) { (data) in
|
||||
guard let data = data else { return }
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -39,23 +39,24 @@
|
|||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Content" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OEF-Hj-v3f" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="24.5" width="301" height="626.5"/>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" scrollEnabled="NO" delaysContentTouches="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="atN-ay-ceL" customClass="StatusContentTextView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="25" width="301" height="626"/>
|
||||
<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" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="atN-ay-ceL" secondAttribute="bottom" id="3ub-qq-laF"/>
|
||||
<constraint firstItem="Sdv-dB-Plm" firstAttribute="leading" secondItem="2cE-sS-Uut" secondAttribute="leading" id="6v5-7p-9gm"/>
|
||||
<constraint firstAttribute="bottom" secondItem="OEF-Hj-v3f" secondAttribute="bottom" id="IEQ-Ab-tsP"/>
|
||||
<constraint firstItem="OEF-Hj-v3f" firstAttribute="top" secondItem="Sdv-dB-Plm" secondAttribute="bottom" constant="4" id="J5s-TU-odB"/>
|
||||
<constraint firstItem="Sdv-dB-Plm" firstAttribute="top" secondItem="2cE-sS-Uut" secondAttribute="top" id="YmP-yU-sfe"/>
|
||||
<constraint firstItem="OEF-Hj-v3f" firstAttribute="leading" secondItem="2cE-sS-Uut" secondAttribute="leading" id="bbW-07-e2x"/>
|
||||
<constraint firstItem="0yZ-71-eTj" firstAttribute="top" secondItem="2cE-sS-Uut" secondAttribute="top" id="bdX-ge-bMT"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0yZ-71-eTj" secondAttribute="trailing" constant="8" id="hU7-aZ-ibI"/>
|
||||
<constraint firstItem="atN-ay-ceL" firstAttribute="leading" secondItem="2cE-sS-Uut" secondAttribute="leading" id="k5c-jg-Dy8"/>
|
||||
<constraint firstItem="0yZ-71-eTj" firstAttribute="leading" secondItem="Sdv-dB-Plm" secondAttribute="trailing" constant="8" id="m0X-YU-m3V"/>
|
||||
<constraint firstAttribute="trailing" secondItem="OEF-Hj-v3f" secondAttribute="trailing" id="xqX-4X-lJl"/>
|
||||
<constraint firstItem="atN-ay-ceL" firstAttribute="top" secondItem="0yZ-71-eTj" secondAttribute="bottom" constant="4" id="pXc-4g-PAe"/>
|
||||
<constraint firstAttribute="trailing" secondItem="atN-ay-ceL" secondAttribute="trailing" id="qcg-bA-8ba"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
|
@ -72,8 +73,8 @@
|
|||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<connections>
|
||||
<outlet property="avatarImageView" destination="Ypn-Ed-MTq" id="eea-bc-klc"/>
|
||||
<outlet property="contentLabel" destination="OEF-Hj-v3f" id="GBI-ib-5T0"/>
|
||||
<outlet property="displayNameLabel" destination="Sdv-dB-Plm" id="RxW-Ra-Ups"/>
|
||||
<outlet property="statusContentTextView" destination="atN-ay-ceL" id="i6A-Rd-rJp"/>
|
||||
<outlet property="usernameLabel" destination="0yZ-71-eTj" id="VQm-Dq-3zP"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="138.40000000000001" y="-72.863568215892059"/>
|
||||
|
|
|
@ -1,229 +0,0 @@
|
|||
//
|
||||
// ContentLabel.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 10/1/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SafariServices
|
||||
import Pachyderm
|
||||
import SwiftSoup
|
||||
|
||||
class ContentLabel: LinkLabel {
|
||||
|
||||
private static let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
|
||||
var navigationDelegate: TuskerNavigationDelegate?
|
||||
|
||||
// MARK: - Emojis
|
||||
func setEmojis(_ emojis: [Emoji]) {
|
||||
guard !emojis.isEmpty else { return }
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
let mutAttrString = NSMutableAttributedString(attributedString: self.attributedText!)
|
||||
let string = mutAttrString.string
|
||||
let matches = ContentLabel.emojiRegex.matches(in: string, options: [], range: NSRange(location: 0, length: mutAttrString.length))
|
||||
for match in matches.reversed() {
|
||||
let shortcode = (string as NSString).substring(with: match.range(at: 1))
|
||||
guard let emoji = emojis.first(where: { $0.shortcode == shortcode }) else {
|
||||
continue
|
||||
}
|
||||
|
||||
group.enter()
|
||||
ImageCache.emojis.get(emoji.url) { (data) in
|
||||
guard let data = data, let image = UIImage(data: data) else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
let attachment = self.createEmojiTextAttachment(image: image, index: match.range.location)
|
||||
mutAttrString.replaceCharacters(in: match.range, with: NSAttributedString(attachment: attachment))
|
||||
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
self.attributedText = mutAttrString
|
||||
self.setNeedsLayout()
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://github.com/ReticentJohn/Amaroq/blob/7c5b7088eb9fd1611dcb0f47d43bf8df093e142c/DireFloof/InlineImageHelpers.m
|
||||
func createEmojiTextAttachment(image: UIImage, index: Int) -> NSTextAttachment {
|
||||
let font = self.font!
|
||||
|
||||
let adjustedCapHeight = font.capHeight - 1
|
||||
var imageSizeMatchingFontSize = CGSize(width: image.size.width * (adjustedCapHeight / image.size.height), height: adjustedCapHeight)
|
||||
|
||||
let defaultScale: CGFloat = 1.4
|
||||
imageSizeMatchingFontSize = CGSize(width: imageSizeMatchingFontSize.width * defaultScale, height: imageSizeMatchingFontSize.height * defaultScale)
|
||||
let textColor = self.textColor!
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(imageSizeMatchingFontSize, false, 0.0)
|
||||
textColor.set()
|
||||
image.draw(in: CGRect(origin: .zero, size: imageSizeMatchingFontSize))
|
||||
let attachmentImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
let attachment = NSTextAttachment()
|
||||
attachment.image = attachmentImage
|
||||
return attachment
|
||||
}
|
||||
|
||||
// MARK: - HTML Parsing
|
||||
func setTextFromHtml(_ html: String) {
|
||||
let doc = try! SwiftSoup.parse(html)
|
||||
let body = doc.body()!
|
||||
|
||||
let (attributedText, links) = attributedTextForHTMLNode(body)
|
||||
let mutAttrString = NSMutableAttributedString(attributedString: attributedText)
|
||||
|
||||
// only trailing whitespace can be trimmed here
|
||||
// when posting an attachment without any text, pleromafe includes U+200B ZERO WIDTH SPACE at the beginning
|
||||
// this would get trimmed and cause range out of bounds crashes
|
||||
mutAttrString.trimTrailingCharactersInSet(.whitespacesAndNewlines)
|
||||
|
||||
self.links = []
|
||||
let linkAttributes: [NSAttributedString.Key: Any] = [
|
||||
.foregroundColor: UIColor.systemBlue,
|
||||
]
|
||||
for (range, url) in links {
|
||||
mutAttrString.addAttributes(linkAttributes, range: range)
|
||||
self.links.append(Link(range: range, url: url))
|
||||
}
|
||||
|
||||
self.attributedText = mutAttrString
|
||||
}
|
||||
|
||||
private func attributedTextForHTMLNode(_ node: Node) -> (NSAttributedString, [NSRange: URL]) {
|
||||
switch node {
|
||||
case let node as TextNode:
|
||||
return (NSAttributedString(string: node.text()), [:])
|
||||
case let node as Element:
|
||||
var links = [NSRange: URL]()
|
||||
let attributed = NSMutableAttributedString()
|
||||
for child in node.getChildNodes() {
|
||||
let (text, childLinks) = attributedTextForHTMLNode(child)
|
||||
for (range, url) in childLinks {
|
||||
let newRange = NSRange(location: range.location + attributed.length, length: range.length)
|
||||
links[newRange] = url
|
||||
}
|
||||
attributed.append(text)
|
||||
}
|
||||
|
||||
switch node.tagName() {
|
||||
case "br":
|
||||
attributed.append(NSAttributedString(string: "\n"))
|
||||
case "a":
|
||||
if let link = try? node.attr("href"),
|
||||
let url = URL(string: link) {
|
||||
links[attributed.fullRange] = url
|
||||
}
|
||||
case "p":
|
||||
attributed.append(NSAttributedString(string: "\n\n"))
|
||||
case "em", "i":
|
||||
let currentFont: UIFont = attributed.attribute(.font, at: 0, effectiveRange: nil) as? UIFont ?? self.font
|
||||
attributed.addAttribute(.font, value: currentFont.addingTraits(.traitItalic)!, range: attributed.fullRange)
|
||||
case "strong", "b":
|
||||
let currentFont: UIFont = attributed.attribute(.font, at: 0, effectiveRange: nil) as? UIFont ?? self.font
|
||||
attributed.addAttribute(.font, value: currentFont.addingTraits(.traitBold)!, range: attributed.fullRange)
|
||||
case "del":
|
||||
attributed.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: attributed.fullRange)
|
||||
case "code":
|
||||
attributed.addAttribute(.font, value: UIFont(name: "Menlo", size: font!.pointSize)!, range: attributed.fullRange)
|
||||
case "pre":
|
||||
attributed.addAttribute(.font, value: UIFont(name: "Menlo", size: font!.pointSize)!, range: attributed.fullRange)
|
||||
attributed.append(NSAttributedString(string: "\n\n"))
|
||||
case "ol", "ul":
|
||||
attributed.trimLeadingCharactersInSet(.whitespacesAndNewlines)
|
||||
attributed.append(NSAttributedString(string: "\n"))
|
||||
break
|
||||
case "li":
|
||||
let parentEl = node.parent()!
|
||||
let parentTag = parentEl.tagName()
|
||||
let bullet: NSAttributedString
|
||||
if parentTag == "ol" {
|
||||
let index = (try? node.elementSiblingIndex()) ?? 0
|
||||
// we use the monospace digit font so that the periods of all the list items line up
|
||||
bullet = NSAttributedString(string: "\(index + 1).\t", attributes: [.font: UIFont.monospacedDigitSystemFont(ofSize: font!.pointSize, weight: .regular)])
|
||||
} else if parentTag == "ul" {
|
||||
bullet = NSAttributedString(string: "\u{2022}\t")
|
||||
} else {
|
||||
bullet = NSAttributedString(string: "")
|
||||
}
|
||||
// inserting bullets at the beginning of the string shifts all the links down, so we adjust the link ranges
|
||||
for (range, url) in links {
|
||||
let newRange = NSRange(location: range.location + bullet.length - 1, length: range.length)
|
||||
links[newRange] = url
|
||||
links.removeValue(forKey: range)
|
||||
}
|
||||
attributed.insert(bullet, at: 0)
|
||||
attributed.append(NSAttributedString(string: "\n"))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return (attributed, links)
|
||||
default:
|
||||
fatalError("Unexpected node type: \(type(of: node))")
|
||||
}
|
||||
}
|
||||
|
||||
func getViewController(forLink url: URL, inRange range: NSRange) -> UIViewController {
|
||||
let text = (self.text! as NSString).substring(with: range)
|
||||
|
||||
if let mention = getMention(for: url, text: text) {
|
||||
return ProfileTableViewController(accountID: mention.id)
|
||||
} else if let tag = getHashtag(for: url, text: text) {
|
||||
return HashtagTimelineViewController(for: tag)
|
||||
} else {
|
||||
return SFSafariViewController(url: url)
|
||||
}
|
||||
}
|
||||
|
||||
func getViewController(forLinkAt point: CGPoint) -> UIViewController? {
|
||||
guard let link = getLink(atPoint: point) else {
|
||||
return nil
|
||||
}
|
||||
return getViewController(forLink: link.url, inRange: link.range)
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
override func linkTapped(_ link: LinkLabel.Link) {
|
||||
let text = (self.text! as NSString).substring(with: link.range)
|
||||
|
||||
if let mention = getMention(for: link.url, text: text) {
|
||||
navigationDelegate?.selected(mention: mention)
|
||||
} else if let tag = getHashtag(for: link.url, text: text) {
|
||||
navigationDelegate?.selected(tag: tag)
|
||||
} else {
|
||||
navigationDelegate?.selected(url: link.url)
|
||||
}
|
||||
}
|
||||
|
||||
override func linkLongPressed(_ link: LinkLabel.Link) {
|
||||
navigationDelegate?.showMoreOptions(forURL: link.url, sourceView: self)
|
||||
}
|
||||
|
||||
// MARK: - Navigation
|
||||
func getMention(for url: URL, text: String) -> Mention? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHashtag(for url: URL, text: String) -> Hashtag? {
|
||||
if text.starts(with: "#") {
|
||||
let tag = String(text.dropFirst())
|
||||
return Hashtag(name: tag, url: url)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
//
|
||||
// ContentTextView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/18/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftSoup
|
||||
import Pachyderm
|
||||
import SafariServices
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
|
||||
class ContentTextView: LinkTextView {
|
||||
|
||||
// todo: should be weak
|
||||
var navigationDelegate: TuskerNavigationDelegate?
|
||||
|
||||
var defaultFont: UIFont = .systemFont(ofSize: 17)
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
delegate = self
|
||||
|
||||
addInteraction(UIContextMenuInteraction(delegate: self))
|
||||
|
||||
textDragInteraction?.isEnabled = false
|
||||
}
|
||||
|
||||
// MARK: - Emojis
|
||||
func setEmojis(_ emojis: [Emoji]) {
|
||||
guard !emojis.isEmpty else { return }
|
||||
|
||||
let emojiImages = CachedDictionary<UIImage>(name: "ContentTextView Emoji Images")
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for emoji in emojis {
|
||||
group.enter()
|
||||
ImageCache.emojis.get(emoji.url) { (data) in
|
||||
defer { group.leave() }
|
||||
guard let data = data, let image = UIImage(data: data) else {
|
||||
return
|
||||
}
|
||||
emojiImages[emoji.shortcode] = image
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
let mutAttrString = NSMutableAttributedString(attributedString: self.attributedText!)
|
||||
let string = mutAttrString.string
|
||||
let matches = emojiRegex.matches(in: string, options: [], range: mutAttrString.fullRange)
|
||||
// replaces the emojis started from the end of the string as to not alter the indexes of the other emojis
|
||||
for match in matches.reversed() {
|
||||
let shortcode = (string as NSString).substring(with: match.range(at: 1))
|
||||
guard let emojiImage = emojiImages[shortcode] else {
|
||||
continue
|
||||
}
|
||||
|
||||
let attachment = self.createEmojiTextAttachment(image: emojiImage, index: match.range.location)
|
||||
let attachmentStr = NSAttributedString(attachment: attachment)
|
||||
mutAttrString.replaceCharacters(in: match.range, with: attachmentStr)
|
||||
}
|
||||
|
||||
self.attributedText = mutAttrString
|
||||
self.setNeedsLayout()
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://github.com/ReticentJohn/Amaroq/blob/7c5b7088eb9fd1611dcb0f47d43bf8df093e142c/DireFloof/InlineImageHelpers.m
|
||||
private func createEmojiTextAttachment(image: UIImage, index: Int) -> NSTextAttachment {
|
||||
let font = self.font!
|
||||
|
||||
let adjustedCapHeight = font.capHeight - 1
|
||||
var imageSizeMatchingFontSize = CGSize(width: image.size.width * (adjustedCapHeight / image.size.height), height: adjustedCapHeight)
|
||||
|
||||
let defaultScale: CGFloat = 1.4
|
||||
imageSizeMatchingFontSize = CGSize(width: imageSizeMatchingFontSize.width * defaultScale, height: imageSizeMatchingFontSize.height * defaultScale)
|
||||
let textColor = self.textColor ?? UIColor.label
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(imageSizeMatchingFontSize, false, 0.0)
|
||||
textColor.set()
|
||||
image.draw(in: CGRect(origin: .zero, size: imageSizeMatchingFontSize))
|
||||
let attachmentImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
let attachment = NSTextAttachment()
|
||||
attachment.image = attachmentImage
|
||||
return attachment
|
||||
}
|
||||
|
||||
// MARK: - HTML Parsing
|
||||
func setTextFromHtml(_ html: String) {
|
||||
let doc = try! SwiftSoup.parse(html)
|
||||
let body = doc.body()!
|
||||
|
||||
let attributedText = attributedTextForHTMLNode(body)
|
||||
let mutAttrString = NSMutableAttributedString(attributedString: attributedText)
|
||||
mutAttrString.trimTrailingCharactersInSet(.whitespacesAndNewlines)
|
||||
|
||||
self.attributedText = mutAttrString
|
||||
}
|
||||
|
||||
func attributedTextForHTMLNode(_ node: Node) -> NSAttributedString {
|
||||
switch node {
|
||||
case let node as TextNode:
|
||||
return NSAttributedString(string: node.text(), attributes: [.font: defaultFont])
|
||||
case let node as Element:
|
||||
let attributed = NSMutableAttributedString(string: "", attributes: [.font: defaultFont])
|
||||
for child in node.getChildNodes() {
|
||||
attributed.append(attributedTextForHTMLNode(child))
|
||||
}
|
||||
|
||||
switch node.tagName() {
|
||||
case "br":
|
||||
attributed.append(NSAttributedString(string: "\n"))
|
||||
case "a":
|
||||
if let link = try? node.attr("href"),
|
||||
let url = URL(string: link) {
|
||||
attributed.addAttribute(.link, value: url, range: attributed.fullRange)
|
||||
}
|
||||
case "p":
|
||||
attributed.append(NSAttributedString(string: "\n\n"))
|
||||
case "em", "i":
|
||||
let currentFont: UIFont = attributed.attribute(.font, at: 0, effectiveRange: nil) as? UIFont ?? self.font!
|
||||
attributed.addAttribute(.font, value: currentFont.addingTraits(.traitItalic)!, range: attributed.fullRange)
|
||||
case "strong", "b":
|
||||
let currentFont: UIFont = attributed.attribute(.font, at: 0, effectiveRange: nil) as? UIFont ?? self.font!
|
||||
attributed.addAttribute(.font, value: currentFont.addingTraits(.traitBold)!, range: attributed.fullRange)
|
||||
case "del":
|
||||
attributed.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: attributed.fullRange)
|
||||
case "code":
|
||||
attributed.addAttribute(.font, value: UIFont.monospacedSystemFont(ofSize: self.font!.pointSize, weight: .regular), range: attributed.fullRange)
|
||||
case "pre":
|
||||
attributed.addAttribute(.font, value: UIFont.monospacedSystemFont(ofSize: self.font!.pointSize, weight: .regular), range: attributed.fullRange)
|
||||
attributed.append(NSAttributedString(string: "\n\n"))
|
||||
case "ol", "ul":
|
||||
attributed.trimLeadingCharactersInSet(.whitespacesAndNewlines)
|
||||
attributed.append(NSAttributedString(string: "\n\n"))
|
||||
case "li":
|
||||
let parentEl = node.parent()!
|
||||
let parentTag = parentEl.tagName()
|
||||
let bullet: NSAttributedString
|
||||
if parentTag == "ol" {
|
||||
let index = (try? node.elementSiblingIndex()) ?? 0
|
||||
// we use the monospace digit font so that the periods of all the list items line up
|
||||
bullet = NSAttributedString(string: "\(index + 1).\t", attributes: [.font: UIFont.monospacedDigitSystemFont(ofSize: self.font!.pointSize, weight: .regular)])
|
||||
} else if parentTag == "ul" {
|
||||
bullet = NSAttributedString(string: "\u{2022}\t")
|
||||
} else {
|
||||
bullet = NSAttributedString()
|
||||
}
|
||||
attributed.insert(bullet, at: 0)
|
||||
attributed.append(NSAttributedString(string: "\n"))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return attributed
|
||||
default:
|
||||
fatalError("Unexpected node type \(type(of: node))")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
// only accept touches that are over a link
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if getLinkAtPoint(point) != nil {
|
||||
return self
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getLinkAtPoint(_ point: CGPoint) -> (URL, NSRange)? {
|
||||
let locationInTextContainer = CGPoint(x: point.x - textContainerInset.left, y: point.y - textContainerInset.top)
|
||||
var partialFraction: CGFloat = 0
|
||||
let characterIndex = layoutManager.characterIndex(for: locationInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: &partialFraction)
|
||||
if characterIndex < textStorage.length {
|
||||
var range = NSRange()
|
||||
if let link = textStorage.attribute(.link, at: characterIndex, longestEffectiveRange: &range, in: textStorage.fullRange) as? URL {
|
||||
return (link, range)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Navigation
|
||||
|
||||
func getViewController(forLink url: URL, inRange range: NSRange) -> UIViewController {
|
||||
let text = (self.text as NSString).substring(with: range)
|
||||
|
||||
if let mention = getMention(for: url, text: text) {
|
||||
return ProfileTableViewController(accountID: mention.id)
|
||||
} else if let tag = getHashtag(for: url, text: text) {
|
||||
return HashtagTimelineViewController(for: tag)
|
||||
} else {
|
||||
return SFSafariViewController(url: url)
|
||||
}
|
||||
}
|
||||
|
||||
open func getMention(for url: URL, text: String) -> Mention? {
|
||||
return nil
|
||||
}
|
||||
|
||||
open func getHashtag(for url: URL, text: String) -> Hashtag? {
|
||||
if text.starts(with: "#") {
|
||||
let tag = String(text.dropFirst())
|
||||
return Hashtag(name: tag, url: url)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ContentTextView: UITextViewDelegate {
|
||||
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
||||
let text = (self.text as NSString).substring(with: characterRange)
|
||||
switch interaction {
|
||||
case .invokeDefaultAction:
|
||||
if let mention = getMention(for: URL, text: text) {
|
||||
navigationDelegate?.selected(mention: mention)
|
||||
} else if let tag = getHashtag(for: URL, text: text) {
|
||||
navigationDelegate?.selected(tag: tag)
|
||||
} else {
|
||||
navigationDelegate?.selected(url: URL)
|
||||
}
|
||||
case .presentActions:
|
||||
print("present actions")
|
||||
case .preview:
|
||||
print("preview")
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension ContentTextView: MenuPreviewProvider {
|
||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
fatalError("unimplemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension ContentTextView: UIContextMenuInteractionDelegate {
|
||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||
if let (link, range) = getLinkAtPoint(location) {
|
||||
let preview: UIContextMenuContentPreviewProvider = {
|
||||
self.getViewController(forLink: link, inRange: range)
|
||||
}
|
||||
let actions: UIContextMenuActionProvider = { (_) in
|
||||
let text = (self.text as NSString).substring(with: range)
|
||||
let actions: [UIAction]
|
||||
if let mention = self.getMention(for: link, text: text) {
|
||||
actions = self.actionsForProfile(accountID: mention.id, sourceView: self)
|
||||
} else if let tag = self.getHashtag(for: link, text: text) {
|
||||
actions = self.actionsForHashtag(tag, sourceView: self)
|
||||
} else {
|
||||
actions = self.actionsForURL(link, sourceView: self)
|
||||
}
|
||||
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
||||
}
|
||||
return UIContextMenuConfiguration(identifier: nil, previewProvider: preview, actionProvider: actions)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||
if let viewController = animator.previewViewController {
|
||||
animator.preferredCommitStyle = .pop
|
||||
animator.addCompletion {
|
||||
self.navigationDelegate?.show(viewController)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ class InstanceTableViewCell: UITableViewCell {
|
|||
@IBOutlet weak var thumbnailImageView: UIImageView!
|
||||
@IBOutlet weak var domainLabel: UILabel!
|
||||
@IBOutlet weak var adultLabel: UILabel!
|
||||
@IBOutlet weak var descriptionLabel: ContentLabel!
|
||||
@IBOutlet weak var descriptionTextView: ContentTextView!
|
||||
|
||||
var instance: Instance?
|
||||
var selectorInstance: InstanceSelector.Instance?
|
||||
|
@ -37,7 +37,7 @@ class InstanceTableViewCell: UITableViewCell {
|
|||
|
||||
domainLabel.text = instance.domain
|
||||
adultLabel.isHidden = instance.category != .adult
|
||||
descriptionLabel.setTextFromHtml(instance.description)
|
||||
descriptionTextView.setTextFromHtml(instance.description)
|
||||
updateThumbnail(url: instance.proxiedThumbnailURL)
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class InstanceTableViewCell: UITableViewCell {
|
|||
|
||||
domainLabel.text = URLComponents(string: instance.uri)?.host ?? instance.uri
|
||||
adultLabel.isHidden = true
|
||||
descriptionLabel.setTextFromHtml(instance.description)
|
||||
descriptionTextView.setTextFromHtml(instance.description)
|
||||
|
||||
if let thumbnail = instance.thumbnail {
|
||||
updateThumbnail(url: thumbnail)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15703"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -27,7 +27,7 @@
|
|||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="QG1-xB-nmt">
|
||||
<rect key="frame" x="88" y="0.0" width="200" height="47"/>
|
||||
<rect key="frame" x="88" y="0.0" width="200" height="63"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="XtJ-BL-iHb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="26.5"/>
|
||||
|
@ -51,15 +51,12 @@
|
|||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Instance Description" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aD6-LG-BWG" customClass="ContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="26.5" width="200" height="20.5"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<accessibilityTraits key="traits" staticText="YES" notEnabled="YES"/>
|
||||
</accessibility>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Instance Description" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Z5t-Zl-040" customClass="ContentTextView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="26.5" width="200" height="36.5"/>
|
||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
|
@ -75,7 +72,7 @@
|
|||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="adultLabel" destination="ekk-aL-7Pq" id="vzP-Gm-QF7"/>
|
||||
<outlet property="descriptionLabel" destination="aD6-LG-BWG" id="KNk-Gq-cDU"/>
|
||||
<outlet property="descriptionTextView" destination="Z5t-Zl-040" id="Iz3-bX-Zh2"/>
|
||||
<outlet property="domainLabel" destination="SjP-Nk-sSH" id="QPQ-n6-yoK"/>
|
||||
<outlet property="thumbnailImageView" destination="e2C-wt-pkK" id="KeP-Xf-0Tn"/>
|
||||
</connections>
|
||||
|
|
|
@ -1,185 +0,0 @@
|
|||
//
|
||||
// LinkLabel.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 2/3/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class LinkLabel: UILabel {
|
||||
|
||||
typealias Link = (range: NSRange, url: URL)
|
||||
|
||||
let layoutManager = NSLayoutManager()
|
||||
let textContainer = NSTextContainer(size: .zero)
|
||||
var textStorage: NSTextStorage!
|
||||
|
||||
var links = [Link]()
|
||||
|
||||
var selectedLinkAttributes: [NSAttributedString.Key: Any] = [
|
||||
// .backgroundColor: UIColor(hue: 0, saturation: 0, brightness: 0.9, alpha: 1)
|
||||
.backgroundColor: UIColor.secondarySystemBackground
|
||||
]
|
||||
|
||||
var selectedLinkRange: NSRange? {
|
||||
didSet {
|
||||
if let oldValue = oldValue {
|
||||
removeSelectedLinkAttributes(oldValue)
|
||||
}
|
||||
if let newValue = selectedLinkRange {
|
||||
addSelectedLinkAttributes(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var attributedText: NSAttributedString? {
|
||||
didSet {
|
||||
guard let attributedText = attributedText else { return }
|
||||
|
||||
textStorage?.removeLayoutManager(layoutManager)
|
||||
|
||||
textStorage = NSTextStorage(attributedString: attributedText)
|
||||
textStorage.addLayoutManager(layoutManager)
|
||||
}
|
||||
}
|
||||
|
||||
override var text: String? {
|
||||
willSet {
|
||||
fatalError("LinkLabel does not support non-attributed text")
|
||||
}
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
isUserInteractionEnabled = true
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(labelTapped(_:)))
|
||||
tapRecognizer.delegate = self
|
||||
addGestureRecognizer(tapRecognizer)
|
||||
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(labelLongPressed(_:)))
|
||||
longPressRecognizer.delegate = self
|
||||
addGestureRecognizer(longPressRecognizer)
|
||||
|
||||
layoutManager.addTextContainer(textContainer)
|
||||
textContainer.lineFragmentPadding = 0.0
|
||||
textContainer.lineBreakMode = lineBreakMode
|
||||
textContainer.maximumNumberOfLines = numberOfLines
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
textContainer.size = bounds.size
|
||||
}
|
||||
|
||||
func getLink(atPoint point: CGPoint) -> Link? {
|
||||
let labelSize = bounds.size
|
||||
let textBoundingBox = layoutManager.usedRect(for: textContainer)
|
||||
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
|
||||
y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
|
||||
let locationOfTouchInTextContainer = CGPoint(x: point.x - textContainerOffset.x,
|
||||
y: point.y - textContainerOffset.y)
|
||||
// let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
|
||||
let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouchInTextContainer, in: textContainer)
|
||||
|
||||
if let link = links.first(where: { $0.range.contains(indexOfCharacter) }) {
|
||||
return link
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func addSelectedLinkAttributes(_ range: NSRange) {
|
||||
let mutAttrString = NSMutableAttributedString(attributedString: attributedText!)
|
||||
mutAttrString.addAttributes(selectedLinkAttributes, range: range)
|
||||
self.attributedText = mutAttrString
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
func removeSelectedLinkAttributes(_ range: NSRange) {
|
||||
let mutAttrString = NSMutableAttributedString(attributedString: attributedText!)
|
||||
selectedLinkAttributes.keys.forEach { mutAttrString.removeAttribute($0, range: range) }
|
||||
self.attributedText = mutAttrString
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if let touch = touches.first, onTouch(touch) {
|
||||
return
|
||||
}
|
||||
super.touchesBegan(touches, with: event)
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if let touch = touches.first, onTouch(touch) {
|
||||
return
|
||||
}
|
||||
super.touchesMoved(touches, with: event)
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if let touch = touches.first, onTouch(touch) {
|
||||
return
|
||||
}
|
||||
super.touchesEnded(touches, with: event)
|
||||
}
|
||||
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if let touch = touches.first, onTouch(touch) {
|
||||
return
|
||||
}
|
||||
super.touchesCancelled(touches, with: event)
|
||||
}
|
||||
|
||||
func onTouch(_ touch: UITouch) -> Bool {
|
||||
let location = touch.location(in: self)
|
||||
let link = getLink(atPoint: location)
|
||||
|
||||
switch touch.phase {
|
||||
case .began, .moved:
|
||||
selectedLinkRange = link?.range
|
||||
case .cancelled, .ended:
|
||||
selectedLinkRange = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return link != nil
|
||||
}
|
||||
|
||||
@objc func labelTapped(_ recognizer: UITapGestureRecognizer) {
|
||||
let location = recognizer.location(in: self)
|
||||
guard let link = getLink(atPoint: location) else {
|
||||
return
|
||||
}
|
||||
|
||||
linkTapped(link)
|
||||
}
|
||||
|
||||
@objc func labelLongPressed(_ recognizer: UILongPressGestureRecognizer) {
|
||||
let location = recognizer.location(in: self)
|
||||
guard let link = getLink(atPoint: location) else {
|
||||
return
|
||||
}
|
||||
|
||||
linkLongPressed(link)
|
||||
}
|
||||
|
||||
func linkTapped(_ link: Link) {
|
||||
}
|
||||
|
||||
func linkLongPressed(_ link: Link) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension LinkLabel: UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||
let location = touch.location(in: self)
|
||||
let link = getLink(atPoint: location)
|
||||
return link != nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// LinkTextView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/18/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class LinkTextView: UITextView {
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
delaysContentTouches = false
|
||||
isScrollEnabled = false
|
||||
isEditable = false
|
||||
isUserInteractionEnabled = true
|
||||
isSelectable = true // is this necessary?
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
|
|
@ -23,7 +23,7 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var followsYouLabel: UILabel!
|
||||
@IBOutlet weak var noteLabel: StatusContentLabel!
|
||||
@IBOutlet weak var noteTextView: StatusContentTextView!
|
||||
@IBOutlet weak var fieldsStackView: UIStackView!
|
||||
@IBOutlet weak var fieldNamesStackView: UIStackView!
|
||||
@IBOutlet weak var fieldValuesStack: UIStackView!
|
||||
|
@ -78,9 +78,9 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
noteLabel.navigationDelegate = delegate
|
||||
noteLabel.setTextFromHtml(account.note)
|
||||
noteLabel.setEmojis(account.emojis)
|
||||
noteTextView.navigationDelegate = delegate
|
||||
noteTextView.setTextFromHtml(account.note)
|
||||
noteTextView.setEmojis(account.emojis)
|
||||
|
||||
if accountID != MastodonController.account.id {
|
||||
// don't show relationship label for the user's own account
|
||||
|
@ -103,16 +103,18 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
nameLabel.text = field.name
|
||||
nameLabel.font = .boldSystemFont(ofSize: 17)
|
||||
nameLabel.textAlignment = .right
|
||||
nameLabel.numberOfLines = 0
|
||||
fieldNamesStackView.addArrangedSubview(nameLabel)
|
||||
|
||||
let valueLabel = ContentLabel()
|
||||
valueLabel.setTextFromHtml(field.value)
|
||||
valueLabel.setEmojis(account.emojis)
|
||||
valueLabel.font = .systemFont(ofSize: 17)
|
||||
valueLabel.textAlignment = .left
|
||||
valueLabel.awakeFromNib() // TODO: this shouldn't be necessary
|
||||
valueLabel.navigationDelegate = delegate
|
||||
fieldValuesStack.addArrangedSubview(valueLabel)
|
||||
let valueTextView = ContentTextView()
|
||||
valueTextView.isSelectable = false
|
||||
valueTextView.font = .systemFont(ofSize: 17)
|
||||
valueTextView.setTextFromHtml(field.value)
|
||||
valueTextView.setEmojis(account.emojis)
|
||||
valueTextView.textAlignment = .left
|
||||
valueTextView.awakeFromNib()
|
||||
valueTextView.navigationDelegate = delegate
|
||||
fieldValuesStack.addArrangedSubview(valueTextView)
|
||||
}
|
||||
} else {
|
||||
fieldsStackView.isHidden = true
|
||||
|
@ -150,27 +152,27 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
|||
|
||||
}
|
||||
|
||||
extension ProfileHeaderTableViewCell: MenuPreviewProvider {
|
||||
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
let noteLabelPoint = noteLabel.convert(location, from: self)
|
||||
if noteLabel.bounds.contains(noteLabelPoint),
|
||||
let link = noteLabel.getLink(atPoint: noteLabelPoint) {
|
||||
return (
|
||||
content: { self.noteLabel.getViewController(forLink: link.url, inRange: link.range) },
|
||||
actions: {
|
||||
let text = (self.noteLabel.text! as NSString).substring(with: link.range)
|
||||
if let mention = self.noteLabel.getMention(for: link.url, text: text) {
|
||||
return self.actionsForProfile(accountID: mention.id, sourceView: self)
|
||||
} else if let hashtag = self.noteLabel.getHashtag(for: link.url, text: text) {
|
||||
return self.actionsForHashtag(hashtag, sourceView: self)
|
||||
} else {
|
||||
return self.actionsForURL(link.url, sourceView: self)
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
//extension ProfileHeaderTableViewCell: MenuPreviewProvider {
|
||||
// var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||
// func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
// let noteLabelPoint = noteLabel.convert(location, from: self)
|
||||
// if noteLabel.bounds.contains(noteLabelPoint),
|
||||
// let link = noteLabel.getLink(atPoint: noteLabelPoint) {
|
||||
// return (
|
||||
// content: { self.noteLabel.getViewController(forLink: link.url, inRange: link.range) },
|
||||
// actions: {
|
||||
// let text = (self.noteLabel.text! as NSString).substring(with: link.range)
|
||||
// if let mention = self.noteLabel.getMention(for: link.url, text: text) {
|
||||
// return self.actionsForProfile(accountID: mention.id, sourceView: self)
|
||||
// } else if let hashtag = self.noteLabel.getHashtag(for: link.url, text: text) {
|
||||
// return self.actionsForHashtag(hashtag, sourceView: self)
|
||||
// } else {
|
||||
// return self.actionsForURL(link.url, sourceView: self)
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// } else {
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -66,12 +66,13 @@
|
|||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Note" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="I0n-aP-dJP" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="37" height="12"/>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" delaysContentTouches="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bnc-3t-t7t" customClass="StatusContentTextView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="337" height="12"/>
|
||||
<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" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="sHU-GU-klv">
|
||||
|
@ -165,7 +166,7 @@
|
|||
<outlet property="followsYouLabel" destination="a32-1a-xXZ" id="phY-0L-NnN"/>
|
||||
<outlet property="headerImageView" destination="Fw7-OL-iy5" id="6sv-E5-D73"/>
|
||||
<outlet property="moreButtonVisualEffectView" destination="mQY-XN-PfZ" id="t7l-wg-nj0"/>
|
||||
<outlet property="noteLabel" destination="I0n-aP-dJP" id="7yW-mE-jxY"/>
|
||||
<outlet property="noteTextView" destination="bnc-3t-t7t" id="dV2-7U-gSd"/>
|
||||
<outlet property="usernameLabel" destination="MIj-OR-NOR" id="e1I-N7-rKx"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="40.799999999999997" y="110.64467766116942"/>
|
||||
|
|
|
@ -17,7 +17,7 @@ protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
|
|||
class BaseStatusTableViewCell: UITableViewCell {
|
||||
var delegate: StatusTableViewCellDelegate? {
|
||||
didSet {
|
||||
contentLabel.navigationDelegate = delegate
|
||||
contentTextView.navigationDelegate = delegate
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
@IBOutlet weak var contentWarningLabel: UILabel!
|
||||
@IBOutlet weak var collapseButton: UIButton!
|
||||
@IBOutlet weak var contentLabel: StatusContentLabel!
|
||||
@IBOutlet weak var contentTextView: StatusContentTextView!
|
||||
@IBOutlet weak var attachmentsView: AttachmentsContainerView!
|
||||
@IBOutlet weak var replyButton: UIButton!
|
||||
@IBOutlet weak var favoriteButton: UIButton!
|
||||
|
@ -88,7 +88,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
collapseButton.layer.masksToBounds = true
|
||||
collapseButton.layer.cornerRadius = 5
|
||||
|
||||
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentLabel!, attachmentsView!]
|
||||
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!]
|
||||
attachmentsView.isAccessibilityElement = true
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||
|
@ -123,7 +123,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
|
||||
updateStatusState(status: status)
|
||||
|
||||
contentLabel.statusID = statusID
|
||||
contentTextView.statusID = statusID
|
||||
|
||||
contentWarningLabel.text = status.spoilerText
|
||||
contentWarningLabel.isHidden = status.spoilerText.isEmpty
|
||||
|
@ -132,7 +132,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
collapsible = !status.spoilerText.isEmpty
|
||||
var shouldCollapse = collapsible
|
||||
if !shouldCollapse,
|
||||
let text = contentLabel.text,
|
||||
let text = contentTextView.text,
|
||||
text.count > 500 {
|
||||
collapsible = true
|
||||
shouldCollapse = true
|
||||
|
@ -203,7 +203,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
func setCollapsed(_ collapsed: Bool, animated: Bool) {
|
||||
self.collapsed = collapsed
|
||||
|
||||
contentLabel.isHidden = collapsed
|
||||
contentTextView.isHidden = collapsed
|
||||
attachmentsView.isHidden = attachmentsView.attachments.count == 0 || collapsed
|
||||
|
||||
let buttonImage = UIImage(systemName: collapsed ? "chevron.down" : "chevron.up")!
|
||||
|
@ -325,7 +325,7 @@ extension BaseStatusTableViewCell: MenuPreviewProvider {
|
|||
let description = attachmentView.attachment.description
|
||||
return (content: { self.delegate?.largeImage(image, description: description, sourceView: attachmentView) }, actions: { [] })
|
||||
}
|
||||
} else if contentLabel.frame.contains(location),
|
||||
}/* else if contentLabel.frame.contains(location),
|
||||
let link = contentLabel.getLink(atPoint: contentLabel.convert(location, from: self)) {
|
||||
return (
|
||||
content: { self.contentLabel.getViewController(forLink: link.url, inRange: link.range) },
|
||||
|
@ -340,7 +340,7 @@ extension BaseStatusTableViewCell: MenuPreviewProvider {
|
|||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}*/
|
||||
return self.getStatusCellPreviewProviders(for: location, sourceViewController: sourceViewController)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,9 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
|||
|
||||
profileAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self)
|
||||
profileAccessibilityElement.accessibilityFrameInContainerSpace = profileDetailContainerView.convert(profileDetailContainerView.frame, to: self)
|
||||
accessibilityElements = [profileAccessibilityElement!, contentWarningLabel!, collapseButton!, contentLabel!, totalFavoritesButton!, totalReblogsButton!, timestampAndClientLabel!, replyButton!, favoriteButton!, reblogButton!, moreButton!]
|
||||
accessibilityElements = [profileAccessibilityElement!, contentWarningLabel!, collapseButton!, contentTextView!, totalFavoritesButton!, totalReblogsButton!, timestampAndClientLabel!, replyButton!, favoriteButton!, reblogButton!, moreButton!]
|
||||
|
||||
contentTextView.defaultFont = .systemFont(ofSize: 20)
|
||||
}
|
||||
|
||||
override func updateUI(statusID: String, state: StatusState) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -75,12 +75,13 @@
|
|||
<action selector="collapseButtonPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="2Jy-L1-lN6"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="TopLeft" horizontalHuggingPriority="251" verticalHuggingPriority="249" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TgY-hs-Klo" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="124.5" width="70" height="55.5"/>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="749" scrollEnabled="NO" delaysContentTouches="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="z0g-HN-gS0" customClass="StatusContentTextView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="124.5" width="343" height="55.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" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IF9-9U-Gk0" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="184" width="343" height="0.0"/>
|
||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
|
@ -183,6 +184,7 @@
|
|||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Cnd-Fj-B7l" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="2hS-RG-81T"/>
|
||||
<constraint firstItem="z0g-HN-gS0" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="4TF-2Z-mdf"/>
|
||||
<constraint firstItem="IF9-9U-Gk0" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="8A8-wi-7sg"/>
|
||||
<constraint firstItem="8r8-O8-Agh" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="bZv-bR-jJ3"/>
|
||||
<constraint firstItem="ejU-sO-Og5" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="biK-oQ-SLy"/>
|
||||
|
@ -204,7 +206,7 @@
|
|||
<outlet property="attachmentsView" destination="IF9-9U-Gk0" id="Oxw-sJ-MJE"/>
|
||||
<outlet property="avatarImageView" destination="mB9-HO-1vf" id="0R0-rt-Osh"/>
|
||||
<outlet property="collapseButton" destination="8r8-O8-Agh" id="0es-Hi-bpt"/>
|
||||
<outlet property="contentLabel" destination="TgY-hs-Klo" id="SEi-B2-VQf"/>
|
||||
<outlet property="contentTextView" destination="z0g-HN-gS0" id="atk-1f-83e"/>
|
||||
<outlet property="contentWarningLabel" destination="cwQ-mR-L1b" id="5sm-PC-FIN"/>
|
||||
<outlet property="displayNameLabel" destination="lZY-2e-17d" id="7og-23-eHy"/>
|
||||
<outlet property="favoriteAndReblogCountStackView" destination="HZv-qj-gi6" id="jC9-cA-dXg"/>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="ve3-Y1-NQH">
|
||||
<rect key="frame" x="0.0" y="28.5" width="343" height="103.5"/>
|
||||
<rect key="frame" x="0.0" y="28.5" width="343" height="165.5"/>
|
||||
<subviews>
|
||||
<imageView contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QMP-j2-HLn">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
|
@ -37,7 +37,7 @@
|
|||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="751" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="gIY-Wp-RSk">
|
||||
<rect key="frame" x="58" y="0.0" width="277" height="103.5"/>
|
||||
<rect key="frame" x="58" y="0.0" width="277" height="165.5"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="3Sm-P0-ySf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="277" height="20.5"/>
|
||||
|
@ -105,12 +105,13 @@
|
|||
<action selector="collapseButtonPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="JaH-xX-UOD"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="TopLeft" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HrJ-t9-KcD" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="83" width="277" height="20.5"/>
|
||||
<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" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
|
@ -125,17 +126,17 @@
|
|||
</constraints>
|
||||
</view>
|
||||
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="136" width="343" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="198" width="343" height="0.0"/>
|
||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" priority="999" constant="200" id="J42-49-2MU"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" distribution="equalSpacing" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="Zlb-yt-NTw">
|
||||
<rect key="frame" x="0.0" y="140" width="343" height="84"/>
|
||||
<rect key="frame" x="0.0" y="202" width="343" height="22"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rKF-yF-KIa">
|
||||
<rect key="frame" x="0.0" y="62" width="21" height="22"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="21" height="22"/>
|
||||
<accessibility key="accessibilityConfiguration" label="Reply"/>
|
||||
<state key="normal" image="arrowshape.turn.up.left.fill" catalog="system"/>
|
||||
<connections>
|
||||
|
@ -143,7 +144,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x0t-TR-jJ4">
|
||||
<rect key="frame" x="107" y="62" width="22" height="22"/>
|
||||
<rect key="frame" x="107" y="0.0" width="22" height="22"/>
|
||||
<accessibility key="accessibilityConfiguration" label="Favorite"/>
|
||||
<state key="normal" image="star.fill" catalog="system"/>
|
||||
<connections>
|
||||
|
@ -151,7 +152,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6tW-z8-Qh9">
|
||||
<rect key="frame" x="215.5" y="62" width="22.5" height="22"/>
|
||||
<rect key="frame" x="215.5" y="0.0" width="22.5" height="22"/>
|
||||
<accessibility key="accessibilityConfiguration" label="Reblog"/>
|
||||
<state key="normal" image="repeat" catalog="system"/>
|
||||
<connections>
|
||||
|
@ -159,7 +160,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="982-J4-NGl">
|
||||
<rect key="frame" x="324" y="62" width="19" height="22"/>
|
||||
<rect key="frame" x="324" y="0.0" width="19" height="22"/>
|
||||
<accessibility key="accessibilityConfiguration" label="More Actions"/>
|
||||
<state key="normal" image="ellipsis" catalog="system"/>
|
||||
<connections>
|
||||
|
@ -189,7 +190,7 @@
|
|||
<outlet property="attachmentsView" destination="nbq-yr-2mA" id="SVm-zl-mPb"/>
|
||||
<outlet property="avatarImageView" destination="QMP-j2-HLn" id="xfS-v8-Gzu"/>
|
||||
<outlet property="collapseButton" destination="O0E-Vf-XYR" id="nWd-gg-st8"/>
|
||||
<outlet property="contentLabel" destination="HrJ-t9-KcD" id="s6V-cx-bBt"/>
|
||||
<outlet property="contentTextView" destination="waJ-f5-LKv" id="hrR-Zg-gLY"/>
|
||||
<outlet property="contentWarningLabel" destination="inI-Og-YiU" id="C7a-eK-qcx"/>
|
||||
<outlet property="displayNameLabel" destination="gll-xe-FSr" id="vVS-WM-Wqx"/>
|
||||
<outlet property="favoriteButton" destination="x0t-TR-jJ4" id="guV-yz-Lm6"/>
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
//
|
||||
// StatusContentLabel.swift
|
||||
// StatusContentTextView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 10/1/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
// Created by Shadowfacts on 1/18/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class StatusContentLabel: ContentLabel {
|
||||
class StatusContentTextView: ContentTextView {
|
||||
|
||||
var statusID: String? {
|
||||
didSet {
|
||||
guard let statusID = statusID else { return }
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Can't set StatusContentLabel text without cached status \(statusID)") }
|
||||
guard let status = MastodonCache.status(for: statusID) else {
|
||||
fatalError("Can't set StatusContentTextView text without cached status for \(statusID)")
|
||||
}
|
||||
setTextFromHtml(status.content)
|
||||
setEmojis(status.emojis)
|
||||
}
|
Loading…
Reference in New Issue