mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
Preserve the blank lines typed after a list
The newline to break replacement only matched paragraph to paragraph, so a blank line after a list was dropped and the text following it glued to the last item. Only a following paragraph is matched, because without a blank line its text would have continued the last item instead of starting a paragraph of its own.
This commit is contained in:
@@ -52,9 +52,10 @@ nonisolated extension String {
|
||||
}
|
||||
|
||||
nonisolated extension String {
|
||||
/// Drops stray new lines everywhere but paragraphs and lists when other paragraphs follow them
|
||||
func replacingHtmlBreaksOccurrences() -> String {
|
||||
var result = self
|
||||
let pattern = #"</p>(\n+)<p>"#
|
||||
let pattern = #"</(p|ul|ol)>(\n+)(?=<p[ >])"#
|
||||
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
|
||||
return result
|
||||
@@ -63,11 +64,12 @@ nonisolated extension String {
|
||||
|
||||
for match in matches.reversed() {
|
||||
guard let range = Range(match.range, in: self),
|
||||
let innerMatchRange = Range(match.range(at: 1), in: self) else {
|
||||
let tagRange = Range(match.range(at: 1), in: self),
|
||||
let innerMatchRange = Range(match.range(at: 2), in: self) else {
|
||||
continue
|
||||
}
|
||||
let numberOfBreaks = (self[innerMatchRange].components(separatedBy: "\n").count - 1)
|
||||
let replacement = "<br>" + String(repeating: "<br>", count: numberOfBreaks)
|
||||
let replacement = "</\(self[tagRange])>" + String(repeating: "<br>", count: numberOfBreaks)
|
||||
result.replaceSubrange(range, with: replacement)
|
||||
}
|
||||
|
||||
|
||||
@@ -684,6 +684,41 @@ struct AttributedStringBuilderTests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A blank line the user typed after a list must survive, just like one between two paragraphs.
|
||||
@Test
|
||||
func newLineAfterList() throws {
|
||||
let htmlString = "<p>Line 1</p>\n<ul>\n<li>Line 2</li>\n</ul>\n<p>Line 4</p>\n"
|
||||
|
||||
let attributedString = try #require(attributedStringBuilder.fromHTML(htmlString), "Could not build the attributed string")
|
||||
|
||||
#expect(String(attributedString.characters) == "Line 1\n • Line 2\n\nLine 4")
|
||||
}
|
||||
|
||||
/// Without a blank line the trailing text is a lazy continuation of the item, so it stays on its line.
|
||||
@Test
|
||||
func noNewLineAfterList() throws {
|
||||
let htmlString = "<ul><li>Line 1</li></ul><p>Line 2</p>"
|
||||
|
||||
let attributedString = try #require(attributedStringBuilder.fromHTML(htmlString), "Could not build the attributed string")
|
||||
|
||||
#expect(String(attributedString.characters) == " • Line 1\nLine 2")
|
||||
}
|
||||
|
||||
/// A blank line is preserved wherever it can be told apart from mere block separation.
|
||||
@Test
|
||||
func newLinesBetweenBlocks() throws {
|
||||
let expectations = ["<p>a</p>\n<p>b</p>": "a\n\nb",
|
||||
"<p>a</p>\n\n<p>b</p>": "a\n\n\nb",
|
||||
"<ol start=\"2\"><li>a</li></ol>\n<p>b</p>": " 2. a\n\nb",
|
||||
// A list can interrupt a paragraph, so this newline is block separation.
|
||||
"<p>a</p>\n<ul><li>b</li></ul>": "a\n • b"]
|
||||
|
||||
for (htmlString, expectation) in expectations {
|
||||
let attributedString = try #require(attributedStringBuilder.fromHTML(htmlString), "Could not build the attributed string")
|
||||
#expect(String(attributedString.characters) == expectation, "Wrong rendering for \(htmlString)")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func unorderedList() throws {
|
||||
let htmlString = "<ul><li>1</li><li>2</li><li>3</li></ul>"
|
||||
|
||||
@@ -73,13 +73,18 @@ struct StringTests {
|
||||
let input3 = "</p>\n\n\n\n<p>"
|
||||
let input4 = "<p>a</p>\n<p>b</p>"
|
||||
let input5 = "empty"
|
||||
let input6 = "<ul><li>a</li></ul>\n<p>b</p>"
|
||||
let input7 = "</p>\n<ul>"
|
||||
|
||||
let expectedOutput0 = input0
|
||||
let expectedOutput1 = "<br><br>"
|
||||
let expectedOutput2 = "<br><br><br>"
|
||||
let expectedOutput3 = "<br><br><br><br><br>"
|
||||
let expectedOutput4 = "<p>a<br><br>b</p>"
|
||||
let expectedOutput1 = "</p><br><p>"
|
||||
let expectedOutput2 = "</p><br><br><p>"
|
||||
let expectedOutput3 = "</p><br><br><br><br><p>"
|
||||
let expectedOutput4 = "<p>a</p><br><p>b</p>"
|
||||
let expectedOutput5 = input5
|
||||
let expectedOutput6 = "<ul><li>a</li></ul><br><p>b</p>"
|
||||
// A list can interrupt a paragraph, so this newline isn't a blank line the user typed.
|
||||
let expectedOutput7 = input7
|
||||
|
||||
#expect(input0.replacingHtmlBreaksOccurrences() == expectedOutput0)
|
||||
#expect(input1.replacingHtmlBreaksOccurrences() == expectedOutput1)
|
||||
@@ -87,5 +92,7 @@ struct StringTests {
|
||||
#expect(input3.replacingHtmlBreaksOccurrences() == expectedOutput3)
|
||||
#expect(input4.replacingHtmlBreaksOccurrences() == expectedOutput4)
|
||||
#expect(input5.replacingHtmlBreaksOccurrences() == expectedOutput5)
|
||||
#expect(input6.replacingHtmlBreaksOccurrences() == expectedOutput6)
|
||||
#expect(input7.replacingHtmlBreaksOccurrences() == expectedOutput7)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user