MessageText component (#1521)
* MessageText component * swiftformat * fix swift format being a bit crazy * link tapping support * new test + fix for font + dynamic support * fix for blockquote issue. * code improvement * small code improvement and fixed tests not working due to weird swiftformat behaviour * fix boost emoji * updated swiftformat * better testing * UI tests updated * fixing the issue with the cache overriding the size category changes * whitespaces * appropriate color + better info plist parsing * cleaned the code and fixed links * tapping link fixes * list bug fix * ui tests regenerated
This commit is contained in:
@@ -331,6 +331,7 @@
|
||||
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; };
|
||||
764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42EEA67A6796BDC2761619C5 /* PaginationIndicatorRoomTimelineView.swift */; };
|
||||
76BA28216FBAF83B2D86A027 /* InvitesScreenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2A71915C1F075E403F559C /* InvitesScreenCell.swift */; };
|
||||
7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CF1EE0AA78470C674554262 /* PillTextAttachment.swift */; };
|
||||
7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */; };
|
||||
77920AFA8091AC6B9F190C90 /* Signposter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */; };
|
||||
77BB228AEA861E50FFD6A228 /* HomeScreenEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FEA560929DD73FFEF8C3DF /* HomeScreenEmptyStateView.swift */; };
|
||||
@@ -351,6 +352,7 @@
|
||||
7C384A8E54A4B60A14CDE8E5 /* WaitlistScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */; };
|
||||
7C6376192F578E0BA801BFEC /* AnalyticsSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C64A14EE89928207E3B42B /* AnalyticsSettingsScreenModels.swift */; };
|
||||
7CD16990BA843BE9ED639129 /* ImageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFE4453AB0B34C203447162 /* ImageRoomTimelineItem.swift */; };
|
||||
7E2BB42805C59DB57E95610F /* PillView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7773CBFDBD458E0B7E270507 /* PillView.swift */; };
|
||||
7E91BAC17963ED41208F489B /* UserSessionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */; };
|
||||
7ECF12D5DCD69F67BD3E3842 /* RoomTimelineControllerFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FE0CDF1FFA92EA7EE17B0B /* RoomTimelineControllerFactoryProtocol.swift */; };
|
||||
7F02063FB3D1C3E5601471A1 /* WelcomeScreenScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851EF6258DF8B7EF129DC3AC /* WelcomeScreenScreenViewModelTests.swift */; };
|
||||
@@ -608,6 +610,7 @@
|
||||
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; };
|
||||
C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; };
|
||||
C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; };
|
||||
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; };
|
||||
C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81B6170DB690013CEB646F4 /* MapLibreModels.swift */; };
|
||||
C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; };
|
||||
CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */; };
|
||||
@@ -715,6 +718,7 @@
|
||||
ECA636DAF071C611FDC2BB57 /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||
EDC1031A7CFB3406A9DA3175 /* AnalyticsLocationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD6299F4516797E9BBE14C3 /* AnalyticsLocationType.swift */; };
|
||||
EDF8919F15DE0FF00EF99E70 /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */; };
|
||||
EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */; };
|
||||
EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40F48279322E504153AB0D /* MockClientProxy.swift */; };
|
||||
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; };
|
||||
EE8A37E2A1A77DE5CF941632 /* StateRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */; };
|
||||
@@ -908,6 +912,7 @@
|
||||
1ABDE6F66532CBEB0E016F94 /* RoomProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyMock.swift; sourceTree = "<group>"; };
|
||||
1B1EE0908B2BF9212436AD3E /* SessionVerificationScreenStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenStateMachine.swift; sourceTree = "<group>"; };
|
||||
1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillAttachmentViewProvider.swift; sourceTree = "<group>"; };
|
||||
1B564D748B67A156F413CD97 /* NotificationSettingsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenModels.swift; sourceTree = "<group>"; };
|
||||
1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenModels.swift; sourceTree = "<group>"; };
|
||||
1B8E176484A89BAC389D4076 /* RoomMembersListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreen.swift; sourceTree = "<group>"; };
|
||||
@@ -1158,6 +1163,7 @@
|
||||
75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
75910F5A36EA8FF9BAD08D18 /* MigrationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenUITests.swift; sourceTree = "<group>"; };
|
||||
772334731A8BF8E6D90B194D /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
7773CBFDBD458E0B7E270507 /* PillView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillView.swift; sourceTree = "<group>"; };
|
||||
780258F1B9D15E30549FF4BE /* NotificationSettingsEditScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||
@@ -1251,6 +1257,7 @@
|
||||
9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationStackCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||
9C7F7DE62D33C6A26CBFCD72 /* IntegrationTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
9CF1EE0AA78470C674554262 /* PillTextAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachment.swift; sourceTree = "<group>"; };
|
||||
9E685274772980BDEFF6691E /* UNUserNotificationCenter+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNUserNotificationCenter+Settings.swift"; sourceTree = "<group>"; };
|
||||
9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = "<group>"; };
|
||||
9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = "<group>"; };
|
||||
@@ -1445,6 +1452,7 @@
|
||||
E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerAuthorization.swift; sourceTree = "<group>"; };
|
||||
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
|
||||
E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = "<group>"; };
|
||||
E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; };
|
||||
E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarSize.swift; sourceTree = "<group>"; };
|
||||
E26747B3154A5DBC3A7E24A5 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
|
||||
E2DCA495ED42D2463DDAA94D /* TimelineBubbleLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineBubbleLayout.swift; sourceTree = "<group>"; };
|
||||
@@ -3007,6 +3015,17 @@
|
||||
path = Templates;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9C4193C4524B35FD6B94B5A9 /* Pills */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1E0B4A34E69BD2132BEC521 /* MessageText.swift */,
|
||||
1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */,
|
||||
9CF1EE0AA78470C674554262 /* PillTextAttachment.swift */,
|
||||
7773CBFDBD458E0B7E270507 /* PillView.swift */,
|
||||
);
|
||||
path = Pills;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9F4A1E90C924DE7954BA5005 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3342,6 +3361,7 @@
|
||||
8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */,
|
||||
06501F0E978B2D5C92771DC7 /* Logging */,
|
||||
C17C3586C93F3A314C1CC318 /* MapLibre */,
|
||||
9C4193C4524B35FD6B94B5A9 /* Pills */,
|
||||
052CC920F473C10B509F9FC1 /* SwiftUI */,
|
||||
B687E3E8C23415A06A3D5C65 /* UserIndicator */,
|
||||
);
|
||||
@@ -4517,6 +4537,7 @@
|
||||
695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */,
|
||||
F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */,
|
||||
C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */,
|
||||
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */,
|
||||
9DE98D3EC47742A0F9F9EC3C /* MigrationScreen.swift in Sources */,
|
||||
968823C9DBF3062729413EBF /* MigrationScreenCoordinator.swift in Sources */,
|
||||
B46EBC7B96CCB64FF8E110DC /* MigrationScreenModels.swift in Sources */,
|
||||
@@ -4572,6 +4593,9 @@
|
||||
764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */,
|
||||
80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */,
|
||||
962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */,
|
||||
EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */,
|
||||
7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */,
|
||||
7E2BB42805C59DB57E95610F /* PillView.swift in Sources */,
|
||||
9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */,
|
||||
1BA04D05EBC6646958B1BE60 /* PlaceholderScreenCoordinator.swift in Sources */,
|
||||
16CBD087038DE3815CDA512C /* PollMock.swift in Sources */,
|
||||
@@ -5015,6 +5039,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(APP_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -5039,6 +5064,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
||||
PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(APP_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -591,6 +591,15 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
selector: #selector(applicationWillTerminate),
|
||||
name: UIApplication.willTerminateNotification,
|
||||
object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(didChangeContentSizeCategory),
|
||||
name: UIContentSizeCategory.didChangeNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
@objc
|
||||
private func didChangeContentSizeCategory() {
|
||||
AttributedStringBuilder.invalidateCache()
|
||||
}
|
||||
|
||||
@objc
|
||||
|
||||
@@ -30,6 +30,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
// worst singleton ever
|
||||
Self.shared = self
|
||||
NSTextAttachment.registerViewProviderClass(PillAttachmentViewProvider.self, forFileType: InfoPlistReader.main.pillsUTType)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
private let permalinkBaseURL: URL
|
||||
|
||||
private static var cache = LRUCache<String, AttributedString>(countLimit: 1000)
|
||||
|
||||
static func invalidateCache() {
|
||||
cache.removeAllValues()
|
||||
}
|
||||
|
||||
init(permalinkBaseURL: URL) {
|
||||
self.permalinkBaseURL = permalinkBaseURL
|
||||
@@ -64,10 +68,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
return cached
|
||||
}
|
||||
|
||||
// Trick DTCoreText into preserving newlines
|
||||
let adjustedHTMLString = htmlString.replacingOccurrences(of: "\n", with: "<br>")
|
||||
|
||||
guard let data = adjustedHTMLString.data(using: .utf8) else {
|
||||
guard let data = htmlString.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,13 @@ struct InfoPlistReader {
|
||||
static let bundleShortVersion = "CFBundleShortVersionString"
|
||||
static let bundleDisplayName = "CFBundleDisplayName"
|
||||
static let mapLibreAPIKey = "mapLibreAPIKey"
|
||||
static let utExportedTypeDeclarationsKey = "UTExportedTypeDeclarations"
|
||||
static let utTypeIdentifierKey = "UTTypeIdentifier"
|
||||
static let utDescriptionKey = "UTTypeDescription"
|
||||
}
|
||||
|
||||
private enum Values {
|
||||
static let mentionPills = "Mention Pills"
|
||||
}
|
||||
|
||||
/// Info.plist reader on the bundle object that contains the current executable.
|
||||
@@ -42,51 +49,61 @@ struct InfoPlistReader {
|
||||
|
||||
/// App group identifier set in Info.plist of the target
|
||||
var appGroupIdentifier: String {
|
||||
infoPlistStringValue(forKey: Keys.appGroupIdentifier)
|
||||
infoPlistValue(forKey: Keys.appGroupIdentifier)
|
||||
}
|
||||
|
||||
/// Base bundle identifier set in Info.plist of the target
|
||||
var baseBundleIdentifier: String {
|
||||
infoPlistStringValue(forKey: Keys.baseBundleIdentifier)
|
||||
infoPlistValue(forKey: Keys.baseBundleIdentifier)
|
||||
}
|
||||
|
||||
/// Keychain access group identifier set in Info.plist of the target
|
||||
var keychainAccessGroupIdentifier: String {
|
||||
infoPlistStringValue(forKey: Keys.keychainAccessGroupIdentifier)
|
||||
infoPlistValue(forKey: Keys.keychainAccessGroupIdentifier)
|
||||
}
|
||||
|
||||
/// Bundle executable of the target
|
||||
var bundleExecutable: String {
|
||||
infoPlistStringValue(forKey: kCFBundleExecutableKey as String)
|
||||
infoPlistValue(forKey: kCFBundleExecutableKey as String)
|
||||
}
|
||||
|
||||
/// Bundle identifier of the target
|
||||
var bundleIdentifier: String {
|
||||
infoPlistStringValue(forKey: kCFBundleIdentifierKey as String)
|
||||
infoPlistValue(forKey: kCFBundleIdentifierKey as String)
|
||||
}
|
||||
|
||||
/// Bundle short version string of the target
|
||||
var bundleShortVersionString: String {
|
||||
infoPlistStringValue(forKey: Keys.bundleShortVersion)
|
||||
infoPlistValue(forKey: Keys.bundleShortVersion)
|
||||
}
|
||||
|
||||
/// Bundle version of the target
|
||||
var bundleVersion: String {
|
||||
infoPlistStringValue(forKey: kCFBundleVersionKey as String)
|
||||
infoPlistValue(forKey: kCFBundleVersionKey as String)
|
||||
}
|
||||
|
||||
/// Bundle display name of the target
|
||||
var bundleDisplayName: String {
|
||||
infoPlistStringValue(forKey: Keys.bundleDisplayName)
|
||||
infoPlistValue(forKey: Keys.bundleDisplayName)
|
||||
}
|
||||
|
||||
/// Map Libre API Key
|
||||
var mapLibreAPIKey: String {
|
||||
infoPlistStringValue(forKey: Keys.mapLibreAPIKey)
|
||||
infoPlistValue(forKey: Keys.mapLibreAPIKey)
|
||||
}
|
||||
|
||||
private func infoPlistStringValue(forKey key: String) -> String {
|
||||
guard let result = bundle.object(forInfoDictionaryKey: key) as? String else {
|
||||
/// Mention Pills UTType
|
||||
var pillsUTType: String {
|
||||
let exportedTypes: [[String: Any]] = infoPlistValue(forKey: Keys.utExportedTypeDeclarationsKey)
|
||||
guard let mentionPills = exportedTypes.first(where: { $0[Keys.utDescriptionKey] as? String == Values.mentionPills }),
|
||||
let utType = mentionPills[Keys.utTypeIdentifierKey] as? String else {
|
||||
fatalError("Add properly \(Values.mentionPills) exported type into your target's Info.plst")
|
||||
}
|
||||
return utType
|
||||
}
|
||||
|
||||
private func infoPlistValue<T>(forKey key: String) -> T {
|
||||
guard let result = bundle.object(forInfoDictionaryKey: key) as? T else {
|
||||
fatalError("Add \(key) into your target's Info.plst")
|
||||
}
|
||||
return result
|
||||
|
||||
137
ElementX/Sources/Other/Pills/MessageText.swift
Normal file
137
ElementX/Sources/Other/Pills/MessageText.swift
Normal file
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
final class MessageTextView: UITextView {
|
||||
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
|
||||
// Prevent long press to show the magnifying glass
|
||||
if gestureRecognizer is UILongPressGestureRecognizer {
|
||||
gestureRecognizer.isEnabled = false
|
||||
}
|
||||
|
||||
super.addGestureRecognizer(gestureRecognizer)
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageText: UIViewRepresentable {
|
||||
@Environment(\.openURL) private var openURLAction: OpenURLAction
|
||||
let attributedString: AttributedString
|
||||
|
||||
func makeUIView(context: Context) -> MessageTextView {
|
||||
let textView = MessageTextView()
|
||||
textView.isEditable = false
|
||||
textView.isScrollEnabled = false
|
||||
textView.adjustsFontForContentSizeCategory = true
|
||||
|
||||
// Required to allow tapping links
|
||||
// We disable selection at delegate level
|
||||
textView.isSelectable = true
|
||||
textView.isUserInteractionEnabled = true
|
||||
|
||||
// Otherwise links can be dragged and dropped when long pressed
|
||||
textView.textDragInteraction?.isEnabled = false
|
||||
|
||||
textView.contentInset = .zero
|
||||
textView.contentInsetAdjustmentBehavior = .never
|
||||
textView.textContainerInset = .zero
|
||||
textView.textContainer.lineFragmentPadding = 0
|
||||
textView.layoutManager.usesFontLeading = false
|
||||
textView.backgroundColor = .clear
|
||||
textView.attributedText = NSAttributedString(attributedString)
|
||||
textView.delegate = context.coordinator
|
||||
return textView
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: MessageTextView, context: Context) {
|
||||
uiView.attributedText = NSAttributedString(attributedString)
|
||||
context.coordinator.openURLAction = openURLAction
|
||||
}
|
||||
|
||||
func sizeThatFits(_ proposal: ProposedViewSize, uiView: MessageTextView, context: Context) -> CGSize? {
|
||||
uiView.sizeThatFits(CGSize(width: proposal.width ?? UIView.layoutFittingExpandedSize.width, height: UIView.layoutFittingCompressedSize.height))
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(openURLAction: openURLAction)
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, UITextViewDelegate {
|
||||
var openURLAction: OpenURLAction
|
||||
|
||||
init(openURLAction: OpenURLAction) {
|
||||
self.openURLAction = openURLAction
|
||||
}
|
||||
|
||||
func textViewDidChangeSelection(_ textView: UITextView) {
|
||||
textView.selectedTextRange = nil
|
||||
}
|
||||
|
||||
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
||||
if interaction == .invokeDefaultAction {
|
||||
openURLAction.callAsFunction(URL)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct MessageText_Previews: PreviewProvider {
|
||||
private static let defaultFontContainer: AttributeContainer = {
|
||||
var container = AttributeContainer()
|
||||
container.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
return container
|
||||
}()
|
||||
|
||||
private static let attributedString = AttributedString("Hello World! Hello world! Hello world! Hello world! Hello World! Hellooooooooooooooooooooooo Woooooooooooooooooooooorld", attributes: defaultFontContainer)
|
||||
private static let attributedStringWithAttachment = "Hello " + AttributedString(NSAttributedString(attachment: PillTextAttachment(data: Data(), ofType: InfoPlistReader.main.pillsUTType))) + " World!"
|
||||
|
||||
private static let htmlStringWithQuote =
|
||||
"""
|
||||
<blockquote>A blockquote that is long and goes onto multiple lines as the first item in the message</blockquote>
|
||||
<p>Then another line of text here to reply to the blockquote, which is also a multiline component.</p>
|
||||
"""
|
||||
|
||||
private static let htmlStringWithList = "<p>This is a list</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n<li>And number 3</li>\n</ul>\n"
|
||||
|
||||
private static let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL)
|
||||
|
||||
static var previews: some View {
|
||||
MessageText(attributedString: attributedString)
|
||||
.border(Color.purple)
|
||||
.previewDisplayName("Custom Text")
|
||||
// For comparison
|
||||
Text(attributedString)
|
||||
.border(Color.purple)
|
||||
.previewDisplayName("SwiftUI Default Text")
|
||||
MessageText(attributedString: attributedStringWithAttachment)
|
||||
.border(Color.purple)
|
||||
.previewDisplayName("Custom Attachment")
|
||||
if let attributedString = attributedStringBuilder.fromHTML(htmlStringWithQuote) {
|
||||
MessageText(attributedString: attributedString)
|
||||
.border(Color.purple)
|
||||
.previewDisplayName("With block quote")
|
||||
}
|
||||
if let attributedString = attributedStringBuilder.fromHTML(htmlStringWithList) {
|
||||
MessageText(attributedString: attributedString)
|
||||
.border(Color.purple)
|
||||
.previewDisplayName("With list")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
final class PillAttachmentViewProvider: NSTextAttachmentViewProvider {
|
||||
// MARK: - Override
|
||||
|
||||
override func loadView() {
|
||||
super.loadView()
|
||||
|
||||
guard textAttachment is PillTextAttachment else {
|
||||
MXLog.failure("[PillAttachmentViewProvider]: attachment is missing or not of expected class")
|
||||
return
|
||||
}
|
||||
|
||||
let view = PillView()
|
||||
let controller = UIHostingController(rootView: view)
|
||||
self.view = controller.view
|
||||
}
|
||||
}
|
||||
20
ElementX/Sources/Other/Pills/PillTextAttachment.swift
Normal file
20
ElementX/Sources/Other/Pills/PillTextAttachment.swift
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Text attachment for pills display.
|
||||
final class PillTextAttachment: NSTextAttachment { }
|
||||
35
ElementX/Sources/Other/Pills/PillView.swift
Normal file
35
ElementX/Sources/Other/Pills/PillView.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PillView: View {
|
||||
var body: some View {
|
||||
Button(action: { MXLog.info("TEXT ATTACHMENT TEST") }) {
|
||||
HStack {
|
||||
Image(asset: Asset.Images.launchLogo)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PillView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PillView()
|
||||
}
|
||||
}
|
||||
@@ -19,21 +19,31 @@ import SwiftUI
|
||||
struct FormattedBodyText: View {
|
||||
@Environment(\.timelineStyle) private var timelineStyle
|
||||
@Environment(\.layoutDirection) private var layoutDirection
|
||||
|
||||
|
||||
private let attributedString: AttributedString
|
||||
private let additionalWhitespacesCount: Int
|
||||
private let boostEmojiSize: Bool
|
||||
|
||||
|
||||
private let defaultAttributesContainer: AttributeContainer = {
|
||||
var container = AttributeContainer()
|
||||
// Equivalent to compound's bodyLG
|
||||
container.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
container.foregroundColor = UIColor.compound.textPrimary
|
||||
return container
|
||||
}()
|
||||
|
||||
private var attributedComponents: [AttributedStringBuilderComponent] {
|
||||
var adjustedAttributedString = attributedString
|
||||
adjustedAttributedString.append(AttributedString(stringLiteral: additionalWhitespacesSuffix))
|
||||
var adjustedAttributedString = attributedString + AttributedString(additionalWhitespacesSuffix)
|
||||
|
||||
// Required to allow the underlying TextView to use body font when no font is specifie in the AttributedString.
|
||||
adjustedAttributedString.mergeAttributes(defaultAttributesContainer, mergePolicy: .keepCurrent)
|
||||
|
||||
let string = String(attributedString.characters)
|
||||
|
||||
if boostEmojiSize,
|
||||
string.containsOnlyEmoji,
|
||||
let range = adjustedAttributedString.range(of: string) {
|
||||
adjustedAttributedString[range].font = .system(size: 48.0)
|
||||
adjustedAttributedString[range].font = UIFont.systemFont(ofSize: 48.0)
|
||||
}
|
||||
|
||||
return adjustedAttributedString.formattedComponents
|
||||
@@ -52,7 +62,7 @@ struct FormattedBodyText: View {
|
||||
additionalWhitespacesCount: additionalWhitespacesCount,
|
||||
boostEmojiSize: boostEmojiSize)
|
||||
}
|
||||
|
||||
|
||||
// These is needed to create the slightly off inlined timestamp effect
|
||||
private var additionalWhitespacesSuffix: String {
|
||||
.generateBreakableWhitespaceEnd(whitespaceCount: additionalWhitespacesCount, layoutDirection: layoutDirection)
|
||||
@@ -75,8 +85,7 @@ struct FormattedBodyText: View {
|
||||
if component.isBlockquote {
|
||||
// The rendered blockquote with a greedy width. The custom layout prevents the
|
||||
// infinite width from increasing the overall width of the view.
|
||||
Text(component.attributedString.mergingAttributes(blockquoteAttributes))
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
MessageText(attributedString: component.attributedString.mergingAttributes(blockquoteAttributes))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 12.0)
|
||||
@@ -90,10 +99,9 @@ struct FormattedBodyText: View {
|
||||
}
|
||||
.layoutPriority(TimelineBubbleLayout.Priority.visibleQuote)
|
||||
} else {
|
||||
Text(component.attributedString)
|
||||
MessageText(attributedString: component.attributedString)
|
||||
.padding(.horizontal, timelineStyle == .bubbles ? 4 : 0)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.layoutPriority(TimelineBubbleLayout.Priority.regularText)
|
||||
}
|
||||
}
|
||||
@@ -102,7 +110,7 @@ struct FormattedBodyText: View {
|
||||
// which are used for layout calculations but won't be rendered.
|
||||
ForEach(attributedComponents, id: \.self) { component in
|
||||
if component.isBlockquote {
|
||||
Text(component.attributedString.mergingAttributes(blockquoteAttributes))
|
||||
MessageText(attributedString: component.attributedString.mergingAttributes(blockquoteAttributes))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.leading, 12.0)
|
||||
.layoutPriority(TimelineBubbleLayout.Priority.hiddenQuote)
|
||||
@@ -121,23 +129,25 @@ struct FormattedBodyText: View {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.red)
|
||||
.frame(width: 4.0)
|
||||
Text(component.attributedString)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
MessageText(attributedString: component.attributedString)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
} else {
|
||||
Text(component.attributedString)
|
||||
MessageText(attributedString: component.attributedString)
|
||||
.padding(.horizontal, timelineStyle == .bubbles ? 4 : 0)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var blockquoteAttributes: AttributeContainer {
|
||||
var container = AttributeContainer()
|
||||
container.font = .compound.bodyMD
|
||||
// Sadly setting SwiftUI fonts do not work so we would need UIFont equivalents for compound, this one is bodyMD
|
||||
container.font = UIFont.preferredFont(forTextStyle: .subheadline)
|
||||
container.foregroundColor = UIColor.compound.textSecondary
|
||||
// To remove the block style paragraph that the parser adds by default
|
||||
container.paragraphStyle = .default
|
||||
return container
|
||||
}
|
||||
}
|
||||
@@ -177,24 +187,32 @@ struct FormattedBodyText_Previews: PreviewProvider {
|
||||
<code><b>Hello</b> <i>world</i></code>
|
||||
<p>Text</p>
|
||||
<code>Hello world</code>
|
||||
"""
|
||||
""",
|
||||
"<p>This is a list</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n<li>And number 3</li>\n</ul>\n"
|
||||
]
|
||||
|
||||
let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL)
|
||||
|
||||
VStack(alignment: .leading, spacing: 24.0) {
|
||||
ForEach(htmlStrings, id: \.self) { htmlString in
|
||||
if let attributedString = attributedStringBuilder.fromHTML(htmlString) {
|
||||
FormattedBodyText(attributedString: attributedString)
|
||||
.previewBubble()
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 24.0) {
|
||||
ForEach(htmlStrings, id: \.self) { htmlString in
|
||||
if let attributedString = attributedStringBuilder.fromHTML(htmlString) {
|
||||
FormattedBodyText(attributedString: attributedString)
|
||||
.previewBubble()
|
||||
}
|
||||
}
|
||||
FormattedBodyText(attributedString: AttributedString("Some plain text wrapped in an AttributedString."))
|
||||
.previewBubble()
|
||||
FormattedBodyText(text: "Some plain text that's not an attributed component.")
|
||||
.previewBubble()
|
||||
FormattedBodyText(text: "Some plain text that's not an attributed component. This one is really long.")
|
||||
.previewBubble()
|
||||
|
||||
FormattedBodyText(text: "❤️", boostEmojiSize: true)
|
||||
.previewBubble()
|
||||
}
|
||||
FormattedBodyText(text: "Some plain text that's not an attributed component.")
|
||||
.previewBubble()
|
||||
FormattedBodyText(text: "Some plain text that's not an attributed component. This one is really long.")
|
||||
.previewBubble()
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -169,16 +169,6 @@ class TimelineTableViewController: UIViewController {
|
||||
.id(id)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.environmentObject(coordinator.context) // Attempted fix at a crash in TimelineItemContextMenu
|
||||
.onAppear {
|
||||
coordinator.send(viewAction: .itemAppeared(itemID: viewState.identifier))
|
||||
}
|
||||
.onDisappear {
|
||||
coordinator.send(viewAction: .itemDisappeared(itemID: viewState.identifier))
|
||||
}
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
coordinator.send(viewAction: .linkClicked(url: url))
|
||||
return .systemAction
|
||||
})
|
||||
}
|
||||
.margins(.all, self.timelineStyle.rowInsets)
|
||||
.minSize(height: 1)
|
||||
|
||||
@@ -23,7 +23,6 @@ struct RoomTimelineItemView: View {
|
||||
timelineView
|
||||
.animation(.elementDefault, value: viewState.groupStyle)
|
||||
.animation(.elementDefault, value: viewState.type)
|
||||
.environmentObject(context)
|
||||
.environment(\.timelineGroupStyle, viewState.groupStyle)
|
||||
.onAppear {
|
||||
context.send(viewAction: .itemAppeared(itemID: viewState.identifier))
|
||||
|
||||
@@ -10,6 +10,19 @@
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>$(APP_DISPLAY_NAME)</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Mention Pills</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<string>$(PILLS_UT_TYPE_IDENTIFIER)</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -54,6 +67,19 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.text</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Mention Pills</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>$(PILLS_UT_TYPE_IDENTIFIER)</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>appGroupIdentifier</key>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
<key>baseBundleIdentifier</key>
|
||||
|
||||
@@ -81,6 +81,15 @@ targets:
|
||||
io.element.elementx.background.refresh
|
||||
]
|
||||
mapLibreAPIKey: $(MAPLIBRE_API_KEY)
|
||||
UTExportedTypeDeclarations:
|
||||
- UTTypeConformsTo: [public.text]
|
||||
UTTypeDescription: Mention Pills
|
||||
UTTypeIdentifier: $(PILLS_UT_TYPE_IDENTIFIER)
|
||||
CFBundleDocumentTypes:
|
||||
- CFBundleTypeName: Mention Pills
|
||||
CFBundleTypeRole: Viewer
|
||||
LSHandlerRank: Owner
|
||||
LSItemContentTypes: $(PILLS_UT_TYPE_IDENTIFIER)
|
||||
|
||||
|
||||
settings:
|
||||
@@ -90,10 +99,11 @@ targets:
|
||||
MARKETING_VERSION: $(MARKETING_VERSION)
|
||||
CURRENT_PROJECT_VERSION: $(CURRENT_PROJECT_VERSION)
|
||||
DEVELOPMENT_TEAM: $(DEVELOPMENT_TEAM)
|
||||
CODE_SIGN_ENTITLEMENTS: ElementX/SupportingFiles/ElementX.entitlements
|
||||
CODE_SIGN_ENTITLEMENTS: ElementX/SupportingFiles/ElementX.entitlements
|
||||
SWIFT_OBJC_BRIDGING_HEADER: ElementX/SupportingFiles/ElementX-Bridging-Header.h
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME: GeneratedInterface-Swift.h
|
||||
|
||||
PILLS_UT_TYPE_IDENTIFIER: $(BASE_BUNDLE_IDENTIFIER).pills
|
||||
|
||||
preBuildScripts:
|
||||
- name: 🛠 SwiftGen
|
||||
runOnlyWhenInstalling: false
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:be453ffed9fb2bc7d301ad59faa4feba3934cc637c7cac47205bb075e756d4a5
|
||||
size 282665
|
||||
oid sha256:2745dc7f693b1e9a046bbb75796da87a67e605081fe121625289c9cf3d84882e
|
||||
size 283525
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:06b4d1de59e18e7e0cdb58b49b6de0c2b7ec21063c11a823e2f4e238d1640f2d
|
||||
size 296741
|
||||
oid sha256:2bfd39ab7965f208e9bc196f3ce513f0e9cdb8180e1211d0ea13f0149f352eb3
|
||||
size 296688
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9c006fbb509e4114b934197aa1e40e2e906a9eee1e043d74a96528862d2edf43
|
||||
size 301910
|
||||
oid sha256:65a5cfec66c33dccb07af5614be84291e466d434dfcfe4fe56587bd476289215
|
||||
size 302401
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:380e2a9d6e32d16ab8bb572d963cc9357baedf90f1e5ffc6180d30921a40a7e2
|
||||
size 300010
|
||||
oid sha256:16b54633d765113aa067e3d9b528037ac5710b8beda2402135ad091fe4c31420
|
||||
size 299941
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f6a1f2a37c3f3c889bbc7f7cc197ea7ab7a8dbca114e04f4b924f925bfdbc962
|
||||
size 282544
|
||||
oid sha256:563be876ecc1376b4ee1c3e096c7553c1774936a5a8d7012916a9ce0bf9a4481
|
||||
size 283275
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4cba62dbfa6b1df17928f2dc35351f864a29a27cb7f80fb725f3ee1d594e1bc3
|
||||
size 99658
|
||||
oid sha256:2c541db903517fb37926bbc30a6c9acf80c360e24fa7dc24d1d8337b387902b1
|
||||
size 99772
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:20790e520cdf5bd805549a9f9cdeeff46468d29770a8dc2c450a195f8738e676
|
||||
size 122033
|
||||
oid sha256:7a90dc15be8fd08412e3801f1ac3a296e3dd83ffe55b3bedc7a3c7587734ecba
|
||||
size 122254
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d41abd405ee8f5c1981e14b7acd24e0d84027599fe2a5525316d9e67c8859cf8
|
||||
size 295412
|
||||
oid sha256:066a36fe220a4c8a6f5534b59050bbc4a6ea567422b43a216b99cbaf0baf37ad
|
||||
size 295586
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:14e8f122345496eafbb1efadaa7a37fbef2ce9c2037dc20e083dab99f54d324a
|
||||
size 122965
|
||||
oid sha256:c112f5aee448a04024876375a31512fa3d38d69deeef4c13ec8f05adbb239df0
|
||||
size 123121
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e5e0398327a1242ad715e12402222129a25616cfe42714a796866ae63d8ec199
|
||||
size 351436
|
||||
oid sha256:a219698b34b517d5698e8b3f04f4017928e748e36c2ce639cc7da60d30bb20b5
|
||||
size 370454
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5cf469ffe0a81e64c3e88b188f8f54c8835e9912085fea0fbeec6231d246e592
|
||||
oid sha256:e1aab1bb6125e746ba4ed02a7a9eb391d10111b05a33666a4ac28cdf29954cd2
|
||||
size 304213
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1729d0b3d1637ac4d9717f7821421e30afdd784b7dfc798bfac934eed6d9376b
|
||||
size 290271
|
||||
oid sha256:3e4b31eb5561c282690e44789906c89cd0d91dc84597c37ba12cb381badbe76d
|
||||
size 294250
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:42dddaf4b8dd23c8018d9fa7c487ce8346e6adfbe10434889090f7202daf39a9
|
||||
size 307188
|
||||
oid sha256:d5a3c7fe9b3eda6fddba3eced5a2682298bd9432d6221b5f0d84fb724a6b0c55
|
||||
size 307189
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:20792aab7f279c0aaae66dfd1f75b2698deb927cc2f469019c4780b308c3e80f
|
||||
size 351159
|
||||
oid sha256:3d312c1e6f73835ea223d4b06b9cb5c1237e79bd92b6793c636ec15a2dd50bd5
|
||||
size 370023
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:60b8e0d8a2cee990dad950d354f2d4d1c13542f2cdc8ead17f326bab8f3e2789
|
||||
size 155323
|
||||
oid sha256:1a4c2f05711d57cc76b9d35e2a2d246fcdb62ab4e7e0a63278d33b00c2cb32ca
|
||||
size 155301
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4afbe26c0ff3c972a64ba52e4b9aa43b7b01db80a8d4261dab2188ea0ab36ae2
|
||||
size 191745
|
||||
oid sha256:bb933c8b49a86bdbe4969d573529851da622fd3bef6aec2ee4db69769f378a52
|
||||
size 191684
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f5a8447f4e9782dd6e214d23b882585a08f6f08a6e9d12b5e85078f8348df784
|
||||
size 317415
|
||||
oid sha256:fd9f896d830f7788d1cc578c8afb9bc7206a7bb520d15d6c6af2b732dd56cac0
|
||||
size 317304
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:950bdce78d5180aa9917c2c1f14b24888720991c086d9623db70a0a7f9843e20
|
||||
size 188486
|
||||
oid sha256:88d7ab6f8e5b115a112c3e2b2a48eae2caa80b497cb7a12bc4d8023ea0c1dfe1
|
||||
size 188457
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1823d5d7ad95177d49e015b65d81b43a7bfbe8806f8051a8808a73c3f70c3101
|
||||
size 283737
|
||||
oid sha256:cc9cefc9ec2c34f0b87611d0cd7568a8923cd609b88bf1267f64fda987bb0207
|
||||
size 284583
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3c434451e78882347dd741c295cf6fa6c5b842201a6fe87e4a24c13b27dd02e8
|
||||
size 297230
|
||||
oid sha256:167922bcb7911d70f9f850b8a6a8bc9f5a6803a0193e58b0097de7ac681f3a26
|
||||
size 297177
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:55d2c24fc253e862c962bb9dfab9967be096b16b8199da60eb18d195ba06a4b6
|
||||
size 300572
|
||||
oid sha256:45da783c68268176d5bb02a1f232738e4c52fecefdb68cf77a8d5ff9d3d82906
|
||||
size 303044
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7e185789c83db3017fe3070424fb8f2aa27b959d62e6eca6ba2a4dd96b21f4e7
|
||||
size 300452
|
||||
oid sha256:041f3b9095f95431918bf95d72d3481c5bbff5e9d86fd8936b13d3a56bcd54b4
|
||||
size 300383
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4d36119a386b8e94d2ccd199fa2550baa4c8bfd2e18c4cf61e3c673316386d74
|
||||
size 283614
|
||||
oid sha256:7c49cf627a39cf6c9243411a4feef4c4bc03f2875b2b3a1bb13bb3635bb0eeed
|
||||
size 284341
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:253eae727b12ca214f4c0396fecbeeb62b98e5523e40a02e4a6600bbcf47d8a0
|
||||
size 100125
|
||||
oid sha256:20ee74112947b1f4f18b0f6dfdb78d66f70bca461ee5e2b751ddabdfc426494f
|
||||
size 100249
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ecbdb5dd52da1edf021e424e568340576e86a84bda85b9fe50906a01eb452da2
|
||||
size 122527
|
||||
oid sha256:b5a7d75675b674c67ccaf25cb2b1c248706283d6b4c0a528914d140b061b7723
|
||||
size 122748
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0b16a5af8455a73ed43b4f368b4319feb1a2732e7d6f7076fc7edb179e6cd5c2
|
||||
size 295904
|
||||
oid sha256:bd015c400454bf0c52b56c3d0284bb7373f99474f54e4bbe1fca347277f87a9e
|
||||
size 296077
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e411427f77907e303d91905f21239e9f90954ac6a062b10fe74c9b9ddfc08e06
|
||||
size 123931
|
||||
oid sha256:2f59880223ffd2dbf6e95a6c54df957ed49b2a2c142f5aa82dec95382bad42a0
|
||||
size 124085
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:97ca898098ea034b94adcffb6cf3a45907ef2c4fff2774863b37f354d1882354
|
||||
size 344628
|
||||
oid sha256:99391bd22d37d4bf4b61969f7ac7f62655eb531357ad856d4fea25af9c001f30
|
||||
size 364806
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3df719c0282253bd6374dfe5780a8e089929b37f8f9b750c0184fa9be8edcbb7
|
||||
size 304809
|
||||
oid sha256:277fcdcfe4e1b5b92e8ae0f7baca1fab04db4b713aa97df760d365bfec87461b
|
||||
size 305713
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:436486516ce97f7eb691471f779361cfb99c6419d4b668e6abd2fe412909c05d
|
||||
size 290954
|
||||
oid sha256:7dad1a1892fb3a5fc46a332394b60d986e69e5c32914961686d52004a635b20f
|
||||
size 295843
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2c5c89904577779c4fd7b92485d147ea0ccbaa706dee162d34b4afc42ad6c2b3
|
||||
size 307707
|
||||
oid sha256:c3d695404ba0365cded0cd84398c0875ac85383579dc9e1ef1f0fb09e6c78700
|
||||
size 308497
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e812e041b7b9bc8ad82ae0164f618ad8eda9bb322f5c546dc694d4d38941dc65
|
||||
size 344351
|
||||
oid sha256:097d9990ed986039c4211c2dc7b2005048ab15bc75852368a96e8dbf63206e61
|
||||
size 364375
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f1774bd54fac62d61ea258cabd251c396e195dd895380789ca0fd8193c2397d6
|
||||
size 155920
|
||||
oid sha256:44e772d784a093cc6dafd9c0c9e67912649122658df85e40a8194f3e6bbb0203
|
||||
size 156850
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a0f6466bf3bff36d378fcea6bf78d0eb7b79089e693323ac21e4a2f098a146e
|
||||
size 192322
|
||||
oid sha256:683443b121806392facff0ebbe154eca3480a0d012b5b5642c5105fe407e16f9
|
||||
size 193174
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9d88cbe47f93d4cbf7f4a3c66348ea9d6bdd5d9275d6e2939ffc5676c2c321d6
|
||||
size 317927
|
||||
oid sha256:249eec43894e48072d4dc754de55aef34635f904e72bef80bfbe7bff0b111a36
|
||||
size 318662
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2cc222df2fafff98ac955644a6f6e9230eeae0d181e5725352012009e3835536
|
||||
size 189869
|
||||
oid sha256:663dbc6d9b8f0fcae82d14c319001790f47472dc76a2c94555ab325b7493572d
|
||||
size 190743
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:97a19207793f911a796adfbc0b507f3c531a9ac9a83a9d190670c61ec5af3a9b
|
||||
size 275591
|
||||
oid sha256:fdb234339f08999f4bd6f1caa8a414ef8d501dcab14b9ef7ba5f646727f96d18
|
||||
size 276550
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a1f560fd354786f25a61551a0f522555d09ba76522ed6a1347b903ce141ea6f
|
||||
size 268467
|
||||
oid sha256:d09b8a4553a12b63b7708fdf0e9a127365ddd7d399f07c141336efc244a8cc4e
|
||||
size 269426
|
||||
|
||||
@@ -396,17 +396,6 @@ class AttributedStringBuilderTests: XCTestCase {
|
||||
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
|
||||
}
|
||||
|
||||
func testNewLinesArePreserved() {
|
||||
let htmlString = "Bob's\nyour\nuncle\nand\nFanny's\nyour\naunt"
|
||||
|
||||
guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else {
|
||||
XCTFail("Could not build the attributed string")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(String(attributedString.characters), htmlString.replacingOccurrences(of: "\n", with: "\u{2028}"))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func checkLinkIn(attributedString: AttributedString?, expectedLink: String, expectedRuns: Int) {
|
||||
|
||||
Reference in New Issue
Block a user