| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-12 02:20:32 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-12 02:20:32 UTC |
| parent | 00f2c43268d9838c869e02b5895ac7eef04af206 |
| borogove/XEP0393.hx | +26 | -7 |
| test/TestChatMessage.hx | +36 | -0 |
| test/TestChatMessageBuilder.hx | +9 | -0 |
diff --git a/borogove/XEP0393.hx b/borogove/XEP0393.hx index b97c7cf..fe2714a 100644 --- a/borogove/XEP0393.hx +++ b/borogove/XEP0393.hx @@ -16,7 +16,7 @@ class XEP0393 { return blocks; } - public static function render(xhtml: Stanza, inPre = false) { + public static function render(xhtml: Stanza, inPre = false, followNewline = true) { if (xhtml.name == "br") { return "\n"; } @@ -26,61 +26,80 @@ class XEP0393 { } final s = new StringBuf(); + var endsWithNewline = true; + + if (!followNewline && ["blockquote", "pre", "div", "p"].contains(xhtml.name)) { + s.add("\n"); + endsWithNewline = true; + } if (xhtml.name == "pre") { final code = xhtml.getChild("code"); var lang = ""; - if (code != null) { + if (code != null && xhtml.children.length == 1) { final className = code.attr.get("class") ?? ""; if (className.startsWith("language-")) { lang = className.substr(9); } } - s.add("\n```" + lang + "\n"); + s.add("```" + lang + "\n"); + endsWithNewline = true; } if (xhtml.name == "b" || xhtml.name == "strong") { s.add("*"); + endsWithNewline = false; } if (xhtml.name == "i" || xhtml.name == "em") { s.add("_"); + endsWithNewline = false; } if (xhtml.name == "s" || xhtml.name == "del") { s.add("~"); + endsWithNewline = false; } if (!inPre && (xhtml.name == "tt" || xhtml.name == "code")) { s.add("`"); + endsWithNewline = false; } for (child in xhtml.children) { - s.add(renderNode(child, xhtml.name == "pre")); + final rendered = renderNode(child, xhtml.name == "pre", endsWithNewline); + s.add(rendered); + endsWithNewline = rendered.endsWith("\n"); } if (xhtml.name == "b" || xhtml.name == "strong") { s.add("*"); + endsWithNewline = false; } if (xhtml.name == "i" || xhtml.name == "em") { s.add("_"); + endsWithNewline = false; } if (xhtml.name == "s" || xhtml.name == "del") { s.add("~"); + endsWithNewline = false; } if (!inPre && (xhtml.name == "tt" || xhtml.name == "code")) { s.add("`"); + endsWithNewline = false; } - if (xhtml.name == "blockquote" || xhtml.name == "p" || xhtml.name == "div" || xhtml.name == "pre") { + if (!endsWithNewline && ["blockquote", "pre", "div", "p"].contains(xhtml.name)) { s.add("\n"); + endsWithNewline = true; } if (xhtml.name == "pre") { s.add("```\n"); + endsWithNewline = true; } if (xhtml.name == "blockquote") { @@ -90,9 +109,9 @@ class XEP0393 { return s.toString(); } - public static function renderNode(xhtml: Node, inPre = false) { + public static function renderNode(xhtml: Node, inPre = false, followNewline = true) { return switch (xhtml) { - case Element(c): render(c, inPre); + case Element(c): render(c, inPre, followNewline); case CData(c): c.content; }; } diff --git a/test/TestChatMessage.hx b/test/TestChatMessage.hx index bd6238f..cf38366 100644 --- a/test/TestChatMessage.hx +++ b/test/TestChatMessage.hx @@ -44,4 +44,40 @@ class TestChatMessage extends utest.Test { Assert.fail("Expected ChatMessageStanza"); } } + + public function testStyledBodyWithCodeBlock() { + final stanza = new Stanza("message"); + stanza.attr.set("id", "test-id-1"); + stanza.attr.set("from", "alice@example.com"); + stanza.attr.set("to", "bob@example.com"); + stanza.attr.set("type", "chat"); + stanza.addChild(new Stanza("body").text("```javascript\nlet hello;\n```")); + + final msg = Message.fromStanza(stanza, JID.parse("bob@example.com")); + switch (msg.parsed) { + case ChatMessageStanza(m): + Assert.equals("<pre><code class=\"language-javascript\">let hello;\n</code></pre>", m.body().toString()); + Assert.equals("```javascript\nlet hello;\n```", m.body().toPlainText()); + default: + Assert.fail("Expected ChatMessageStanza"); + } + } + + public function testStyledBodyWithPreBlock() { + final stanza = new Stanza("message"); + stanza.attr.set("id", "test-id-1"); + stanza.attr.set("from", "alice@example.com"); + stanza.attr.set("to", "bob@example.com"); + stanza.attr.set("type", "chat"); + stanza.addChild(new Stanza("body").text("```\nlet hello;")); + + final msg = Message.fromStanza(stanza, JID.parse("bob@example.com")); + switch (msg.parsed) { + case ChatMessageStanza(m): + Assert.equals("<pre>let hello;\n</pre>", m.body().toString()); + Assert.equals("```\nlet hello;\n```", m.body().toPlainText()); + default: + Assert.fail("Expected ChatMessageStanza"); + } + } } diff --git a/test/TestChatMessageBuilder.hx b/test/TestChatMessageBuilder.hx index 9da21d1..36793f9 100644 --- a/test/TestChatMessageBuilder.hx +++ b/test/TestChatMessageBuilder.hx @@ -26,6 +26,15 @@ class TestChatMessageBuilder extends utest.Test { ); } + public function testConvertHtmlToTextWithParas() { + final msg = new ChatMessageBuilder(); + msg.setBody(Html.fromString("<blockquote>Hello<br>you</blockquote><img alt=':boop:'><br><b>hi</b> <em>hi</em> <s>hey</s> <tt>up</tt><p>a</p><p>b</p><pre>hello<br>you")); + Assert.equals( + "> Hello\n> you\n:boop:\n*hi* _hi_ ~hey~ `up`\na\nb\n```\nhello\nyou\n```", + msg.text + ); + } + public function testConvertHtmlToXHTMLIgnoresBody() { final msg = new ChatMessageBuilder(); msg.setBody(Html.fromString("<body>Hello <div><img src='hai'><br></body>"));