diff --git a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilderV2.swift b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilderV2.swift index 2670d31d9..f162e8ce1 100644 --- a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilderV2.swift +++ b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilderV2.swift @@ -71,7 +71,8 @@ struct AttributedStringBuilderV2: AttributedStringBuilderProtocol { return nil } - let mutableAttributedString = attributedString(from: body, preserveFormatting: false) + var listIndex = 1 + let mutableAttributedString = attributedString(from: body, preserveFormatting: false, listTag: nil, listIndex: &listIndex, indentLevel: 0) detectPhishingAttempts(mutableAttributedString) addLinksAndMentions(mutableAttributedString) addMatrixEntityPermalinkAttributesTo(mutableAttributedString) @@ -81,10 +82,13 @@ struct AttributedStringBuilderV2: AttributedStringBuilderProtocol { return result } - + // MARK: - Private - func attributedString(from element: Element, preserveFormatting: Bool) -> NSMutableAttributedString { + func attributedString(from element: Element, preserveFormatting: Bool, + listTag: String?, + listIndex: inout Int, + indentLevel: Int) -> NSMutableAttributedString { let result = NSMutableAttributedString() for node in element.getChildNodes() { @@ -108,54 +112,55 @@ struct AttributedStringBuilderV2: AttributedStringBuilderProtocol { let tag = childElement.tagName().lowercased() var content = NSMutableAttributedString() + var childIndex = 1 switch tag { case "h1", "h2", "h3", "h4", "h5", "h6": let level = Int(String(tag.dropFirst())) ?? 1 let size: CGFloat = UIFont.systemFontSize + CGFloat(6 - level) * 2 - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.setFontPreservingSymbolicTraits(UIFont.boldSystemFont(ofSize: size)) case "p", "div": - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.append(NSAttributedString(string: "\n")) case "br": content = NSMutableAttributedString(string: "\n") case "b", "strong": - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.setFontPreservingSymbolicTraits(UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)) case "i", "em": - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.setFontPreservingSymbolicTraits(UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)) case "u": - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: NSRange(location: 0, length: content.length)) case "s", "del": - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSRange(location: 0, length: content.length)) case "sup": - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.addAttribute(.baselineOffset, value: 6, range: NSRange(location: 0, length: content.length)) content.setFontPreservingSymbolicTraits(UIFont.systemFont(ofSize: UIFont.systemFontSize * 0.7)) case "sub": - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.addAttribute(.baselineOffset, value: -4, range: NSRange(location: 0, length: content.length)) content.setFontPreservingSymbolicTraits(UIFont.systemFont(ofSize: UIFont.systemFontSize * 0.7)) case "blockquote": - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.addAttribute(.MatrixBlockquote, value: true, range: NSRange(location: 0, length: content.length)) case "code", "pre": let preserveFormatting = preserveFormatting || tag == "pre" - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) content.setFontPreservingSymbolicTraits(UIFont.monospacedSystemFont(ofSize: UIFont.systemFontSize, weight: .regular)) content.addAttribute(.backgroundColor, value: UIColor.compound._bgCodeBlock as Any, range: NSRange(location: 0, length: content.length)) @@ -163,18 +168,43 @@ struct AttributedStringBuilderV2: AttributedStringBuilderProtocol { content = NSMutableAttributedString(string: "\n") case "a": - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) if let href = try? childElement.attr("href") { content.addAttribute(.link, value: href, range: NSRange(location: 0, length: content.length)) } case "span": if (try? childElement.attr(Self.attributeMSC4286)) ?? nil != nil { - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) } + case "ul", "ol": + var listIndex = 1 + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: tag, listIndex: &listIndex, indentLevel: indentLevel + 1) + if listTag == nil { // If not within another list + content.insert(NSAttributedString(string: "\n"), at: 0) + } + + case "li": + var bullet = "" + if listTag == "ol" { + bullet = "\(listIndex). " + listIndex += 1 + } else { + bullet = "• " + } + + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel + 1) + content.insert(NSAttributedString(string: bullet), at: 0) + content.append(NSAttributedString(string: "\n")) + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.headIndent = CGFloat(indentLevel) * 20 + paragraphStyle.firstLineHeadIndent = CGFloat(indentLevel) * 20 + content.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: content.length)) + default: - content = attributedString(from: childElement, preserveFormatting: preserveFormatting) + content = attributedString(from: childElement, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel) } result.append(content) diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift index 31d505a22..379123ac6 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift @@ -213,7 +213,7 @@ struct FormattedBodyText_Previews: PreviewProvider, TestablePreview { This is an unordered list """, diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-en-GB-0.png index b3fc5ddf3..cd65f38dc 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-en-GB-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-en-GB-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0afe442ef495ebf79cfb765870127fd522e5634d9fcaf97ad7f18d2e6d61ff16 -size 1144762 +oid sha256:4ec1eee24450c078e2d930204eb6c4bd5f5a48c1d472926ad25dd061389d7c45 +size 1144610 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-en-GB-1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-en-GB-1.png index dbd82fa08..a4fade271 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-en-GB-1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-en-GB-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eeff85fc90e7c67e1812d308516d642ed5e534675384a9acbd7f1a112bb904f3 -size 1106059 +oid sha256:4d6c10079176b2c9d5d4155e0d917a25191723ce8b0c3c1c23a4b2ceed15f3fb +size 1116696 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-pseudo-0.png index b3fc5ddf3..cd65f38dc 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0afe442ef495ebf79cfb765870127fd522e5634d9fcaf97ad7f18d2e6d61ff16 -size 1144762 +oid sha256:4ec1eee24450c078e2d930204eb6c4bd5f5a48c1d472926ad25dd061389d7c45 +size 1144610 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-pseudo-1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-pseudo-1.png index dbd82fa08..a4fade271 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-pseudo-1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPad-pseudo-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eeff85fc90e7c67e1812d308516d642ed5e534675384a9acbd7f1a112bb904f3 -size 1106059 +oid sha256:4d6c10079176b2c9d5d4155e0d917a25191723ce8b0c3c1c23a4b2ceed15f3fb +size 1116696 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-en-GB-0.png index 4ddb21eb4..957cc5822 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-en-GB-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-en-GB-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c1d75edcce4fd949e15c41cf00a0ee5cb2974a5573ac079e9bbeef230008170 -size 1323823 +oid sha256:aeee2464439a366386e0ededbd5ebde7c330a9a90c26964dc9a63e56563dc7f5 +size 1323827 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-en-GB-1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-en-GB-1.png index fd3ec3979..673ce0f53 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-en-GB-1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-en-GB-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71608e9218a46fa9f22b59e90352d6a463597bb9b2dbff95d063d334085bd8a5 -size 1303346 +oid sha256:e84edbb38495d3c2d323ee5dcd37896c9b286bec6377980625d35e861e5dbd19 +size 1307233 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-pseudo-0.png index 4ddb21eb4..957cc5822 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c1d75edcce4fd949e15c41cf00a0ee5cb2974a5573ac079e9bbeef230008170 -size 1323823 +oid sha256:aeee2464439a366386e0ededbd5ebde7c330a9a90c26964dc9a63e56563dc7f5 +size 1323827 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-pseudo-1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-pseudo-1.png index fd3ec3979..673ce0f53 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-pseudo-1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/formattedBodyText.iPhone-16-pseudo-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71608e9218a46fa9f22b59e90352d6a463597bb9b2dbff95d063d334085bd8a5 -size 1303346 +oid sha256:e84edbb38495d3c2d323ee5dcd37896c9b286bec6377980625d35e861e5dbd19 +size 1307233