diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index cd4c7bfe6..cf25a0860 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -281,6 +281,7 @@ 30E5628F74AD3C27A061BF25 /* QRCodeLoginScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC1B7CB061C9865B2B91B56 /* QRCodeLoginScreenViewModel.swift */; }; 311868F5E65BA61AFA6CCC2C /* CompoundHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B89D6C760E8CAE29CA28FB1 /* CompoundHook.swift */; }; 3118D9ABFD4BE5A3492FF88A /* ElementCallConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC437C491EA6996513B1CEAB /* ElementCallConfiguration.swift */; }; + 31CCF3159E5CE1C05AE5781B /* HTMLFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A5FBB74981BF8EFFDAE90D /* HTMLFixtures.swift */; }; 31DE6B7FD15E1C6E43885717 /* RoomRole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 578AF9CE60816069536C0953 /* RoomRole.swift */; }; 32B7891D937377A59606EDFC /* UserFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */; }; 32F47002A331817F0E6BD7EB /* RoomMembershipDetailsProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1434D5169F0EE319E226DA7F /* RoomMembershipDetailsProxyProtocol.swift */; }; @@ -1327,6 +1328,7 @@ F382E2FE3AC3719BC1F22D9C /* MapURLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB08D1F7C27A8C24EF81073C /* MapURLs.swift */; }; F38D32C1B0232AAFE6A0822C /* ExtensionLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A8571A8A071FB41778C016 /* ExtensionLogger.swift */; }; F396470968764E2C3EDA92DA /* SpaceListScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB180B67B452302260AF44B /* SpaceListScreenViewModel.swift */; }; + F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A5FBB74981BF8EFFDAE90D /* HTMLFixtures.swift */; }; F3E2D3F7ACDED65A4E5CD8DE /* RoomMembersListScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */; }; F3ECA377FF77E81A4F1FA062 /* TimelineItemSendInfoLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753B4C6C0EDDCBF0708DC384 /* TimelineItemSendInfoLabel.swift */; }; F3F38062C6CA21CF403C5C90 /* AudioConverterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */; }; @@ -1706,6 +1708,7 @@ 276833816F5DEB1CE3B8BE1E /* ReportRoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreen.swift; sourceTree = ""; }; 277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfigurationScreenViewStateTests.swift; sourceTree = ""; }; 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; + 27A5FBB74981BF8EFFDAE90D /* HTMLFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLFixtures.swift; sourceTree = ""; }; 27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = ""; }; 27D0EA07BD545CC9F234DB8D /* UserDetailsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenModels.swift; sourceTree = ""; }; 27DF257F5D968E5DD719583C /* BugReportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportTests.swift; sourceTree = ""; }; @@ -5056,6 +5059,7 @@ F254AD274D6973B840FEAF0B /* AttributedStringBuilderV2.swift */, 1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */, C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */, + 27A5FBB74981BF8EFFDAE90D /* HTMLFixtures.swift */, AB07F03461023BC39C730922 /* PhishingDetector.swift */, A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */, E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */, @@ -7162,6 +7166,7 @@ 89198AE2649DD77673D5793B /* ExtensionLogger.swift in Sources */, A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */, 0DFCEAB8C82B151718F71D49 /* FrequentlyUsedEmoji.swift in Sources */, + 31CCF3159E5CE1C05AE5781B /* HTMLFixtures.swift in Sources */, 59F940FCBE6BC343AECEF75E /* ImageCache.swift in Sources */, EBE13FAB4E29738AC41BD3E5 /* InfoPlistReader.swift in Sources */, 8691186F9B99BCDDB7CACDD8 /* KeychainController.swift in Sources */, @@ -7717,6 +7722,7 @@ D34E328E9E65904358248FDD /* GlobalSearchScreenModels.swift in Sources */, 55D18AA4F4A2257642EBDB94 /* GlobalSearchScreenViewModel.swift in Sources */, E32A18802EB37EEE3EF7B965 /* GlobalSearchScreenViewModelProtocol.swift in Sources */, + F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */, E8C4D9F93F0DCED211D5F187 /* HTMLParserStyle.swift in Sources */, 0C1E537A49ABB386F7554D4A /* HighlightedTimelineItemModifier.swift in Sources */, 964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */, diff --git a/ElementX/Sources/Other/HTMLParsing/HTMLFixtures.swift b/ElementX/Sources/Other/HTMLParsing/HTMLFixtures.swift new file mode 100644 index 000000000..29699fdd0 --- /dev/null +++ b/ElementX/Sources/Other/HTMLParsing/HTMLFixtures.swift @@ -0,0 +1,106 @@ +// +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +enum HTMLFixtures: String, CaseIterable { + case plainText + case headers + case paragraphs + case matrixIdentifiers + case links + case textFormatting + case blockQuotes + case codeBlocks + case unorderedList + case orderedList + + var rawValue: String { + switch self { + case .plainText: + """ + Nothing is as permanent as a temporary solution that works. + Experience is the name everyone gives to their mistakes. + If debugging is the process of removing bugs, then programming must be the process of putting them in. + """ + case .headers: + """ +

H1 Header


+

H2 Header


+

H3 Header


+

H4 Header


+
H5 Header

+
H6 Header
+ """ + case .paragraphs: + """ +

This is a paragraph.

And this is another one.

+
And this is a division.
+ New lines are ignored.\n\nLike so.
+ But this line comes after a line break.
+ """ + case .matrixIdentifiers: + """ + We expect various identifiers to be (partially) detected:
+ !room:matrix.org, #room:matrix.org, $event:matrix.org, @user:matrix.org
+ matrix://roomid/room:matrix.org, matrix://r/room:matrix.org, matrix://roomid/room:matrix.org/e/event:matrix.org, matrix://roomid/room:matrix.org/u/user:matrix.org
+ """ + case .links: + """ + Links too:
Matrix rules! 🤘, matrix.org, www.matrix.org, http://matrix.org + """ + case .textFormatting: + """ + Text formatting should work properly.
+ Text formatting does work!.
+ And mixed formatting works too!!1!. +
+ Thumbs if you liked it, sub if you loved it! + """ + case .blockQuotes: + """ +
First blockquote with a link in it
+
Second blockquote with a link in it
+
Third blockquote with a link in it
+ """ + case .codeBlocks: + """ +
A preformatted code blockstruct ContentView: View {
+                var body: some View {
+                    VStack {
+                        Text("Knock, knock!")
+                            .padding()
+                            .background(Color.yellow, in: RoundedRectangle(cornerRadius: 8))
+                        Text("Who's there?")
+                    }
+                    .padding()
+                }
+            }

+ Followed by some plain code blocks
+ Hello, world! + Hello, world! + Hello, world! + """ + case .unorderedList: + """ + This is an unordered list +
    +
  • Jones’ Crumpets
  • +
  • Crumpetorium
  • +
  • Village Bakery
  • +
+ """ + case .orderedList: + """ + This is an ordered list +
    +
  1. Jelly Belly
  2. +
  3. Starburst
  4. +
  5. Skittles
  6. +
+ """ + } + } +} diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift index 379123ac6..14cefde5f 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift @@ -142,90 +142,8 @@ struct FormattedBodyText_Previews: PreviewProvider, TestablePreview { } @ViewBuilder - // swiftlint:disable:next function_body_length static func body(_ attributedStringBuilder: AttributedStringBuilderProtocol) -> some View { - let htmlStrings = [ - """ - Nothing is as permanent as a temporary solution that works. - Experience is the name everyone gives to their mistakes. - If debugging is the process of removing bugs, then programming must be the process of putting them in. - """, - """ -

H1 Header


-

H2 Header


-

H3 Header


-

H4 Header


-
H5 Header

-
H6 Header
- """, - """ -

This is a paragraph.

And this is another one.

-
And this is a division.
- New lines are ignored.\n\nLike so.
- But this line comes after a line break.
- """, - """ - We expect various identifiers to be (partially) detected:
- !room:matrix.org, #room:matrix.org, $event:matrix.org, @user:matrix.org
- matrix://roomid/room:matrix.org, matrix://r/room:matrix.org, matrix://roomid/room:matrix.org/e/event:matrix.org, matrix://roomid/room:matrix.org/u/user:matrix.org
- """, - """ - Links too:
Matrix rules! 🤘, matrix.org, www.matrix.org, http://matrix.org - """, - """ - Text formatting should work properly.
- Text formatting does work!.
- And mixed formatting works too!!1!. -
- Thumbs if you liked it, sub if you loved it! - """, - """ - Text before blockquote
Nothing is as permanent as a temporary solution that works.
Text after blockquote - """, - """ -
First blockquote with a link in it
-
Second blockquote with a link in it
-
Third blockquote with a link in it
- """, - """ -
A blockquote that is long and goes onto multiple lines
-

Then another line of text to reply to the blockquote, also multiline.

-
Then a short blockquote.

Followed by another short sentence.

- """, - """ -
A preformatted code blockstruct ContentView: View {
-                var body: some View {
-                    VStack {
-                        Text("Knock, knock!")
-                            .padding()
-                            .background(Color.yellow, in: RoundedRectangle(cornerRadius: 8))
-                        Text("Who's there?")
-                    }
-                    .padding()
-                }
-            }

- Followed by some plain code blocks
- Hello, world! - Hello, world! - Hello, world! - """, - """ - This is an unordered list -
    -
  • Jones’ Crumpets
  • -
  • Crumpetorium
  • -
  • Village Bakery
  • -
- """, - """ - This is an ordered list -
    -
  1. Jelly Belly
  2. -
  3. Starburst
  4. -
  5. Skittles
  6. -
- """ - ] + let htmlStrings = HTMLFixtures.allCases.map(\.rawValue) ScrollView { VStack(alignment: .leading, spacing: 4.0) {