git » sdk » commit f898447

Shorter context for reaction fallbacks

author Stephen Paul Weber
2026-09-01 19:09:31 UTC
committer Stephen Paul Weber
2026-09-01 20:50:53 UTC
parent 34988414c21c30ebec9ddb29d5f183ba8ecd30a3

Shorter context for reaction fallbacks

People have said that quoting the whole previous long message on a
reaction is too verbose. Here we limit to a single line.

borogove/ChatMessage.hx +7 -3
test/TestChatMessage.hx +72 -0

diff --git a/borogove/ChatMessage.hx b/borogove/ChatMessage.hx
index 548447f..904aa71 100644
--- a/borogove/ChatMessage.hx
+++ b/borogove/ChatMessage.hx
@@ -630,18 +630,22 @@ class ChatMessage {
 			final replyId = replyToM.getReplyId();
 			if (body != null) {
 				final lines = replyToM.text?.split("\n") ?? [];
+				final reaction = EmojiUtil.isEmoji(StringTools.trim(body)) ? StringTools.trim(body.replace("\u{fe0f}", "")) : null;
 				var quoteText = "";
 				for (line in lines) {
 					if (!~/^(?:> ?){3,}/.match(line)) {
 						if (line.charAt(0) == ">") {
-							quoteText += ">" + line + "\n";
+							if (reaction == null) quoteText += ">" + line + "\n";
 						} else {
-							quoteText += "> " + line + "\n";
+							quoteText += "> " + line;
+							if (reaction != null) break;
+							quoteText += "\n";
 						}
 					}
 				}
+				if (reaction != null && lines.length > 1) quoteText += "…";
+				if (reaction != null) quoteText += "\n";
 				if (quoteText != "") quoteText += "\n";
-				final reaction = EmojiUtil.isEmoji(StringTools.trim(body)) ? StringTools.trim(body.replace("\u{fe0f}", "")) : null;
 				body = quoteText + body;
 				if (replyId != null) {
 					final codepoints = StringUtil.codepointArray(quoteText);
diff --git a/test/TestChatMessage.hx b/test/TestChatMessage.hx
index e08794d..321809d 100644
--- a/test/TestChatMessage.hx
+++ b/test/TestChatMessage.hx
@@ -172,4 +172,76 @@ class TestChatMessage extends utest.Test {
 				Assert.fail("Expected SubjectStanza");
 		}
 	}
+
+	public function testReactionFallbackShorterContext() {
+		// Build a parent message with multiple lines
+		final parentBuilder = new borogove.ChatMessageBuilder();
+		parentBuilder.localId = "parent-id";
+		parentBuilder.from = JID.parse("alice@example.com");
+		parentBuilder.to = JID.parse("bob@example.com");
+		parentBuilder.senderId = "alice@example.com";
+		parentBuilder.setBody(borogove.Html.text("line one\nline two\nline three"));
+		final parent = parentBuilder.build();
+
+		// Reply with an emoji (reaction)
+		final reply = parent.reply();
+		reply.from = JID.parse("bob@example.com");
+		reply.to = JID.parse("alice@example.com");
+		reply.senderId = "bob@example.com";
+		reply.localId = "reply-id";
+		reply.setBody(borogove.Html.text("👍"));
+		final replyMsg = reply.build();
+		final stanza = replyMsg.asStanza();
+		final body = stanza.getChildText("body");
+		// Reaction should only quote the first line
+		Assert.equals("> line one…\n\n👍", body);
+	}
+
+	public function testNormalReplyQuotesAllLines() {
+		// Build a parent message with multiple lines
+		final parentBuilder = new borogove.ChatMessageBuilder();
+		parentBuilder.localId = "parent-id";
+		parentBuilder.from = JID.parse("alice@example.com");
+		parentBuilder.to = JID.parse("bob@example.com");
+		parentBuilder.senderId = "alice@example.com";
+		parentBuilder.setBody(borogove.Html.text("line one\nline two\nline three"));
+		final parent = parentBuilder.build();
+
+		// Reply with normal text
+		final reply = parent.reply();
+		reply.from = JID.parse("bob@example.com");
+		reply.to = JID.parse("alice@example.com");
+		reply.senderId = "bob@example.com";
+		reply.localId = "reply-id";
+		reply.setBody(borogove.Html.text("ok sure"));
+		final replyMsg = reply.build();
+		final stanza = replyMsg.asStanza();
+		final body = stanza.getChildText("body");
+		// Normal reply should quote all lines
+		Assert.equals("> line one\n> line two\n> line three\n\nok sure", body);
+	}
+
+	public function testReactionFallbackSkipsNestedQuotes() {
+		// Build a parent message that itself contains a quote and regular text
+		final parentBuilder = new borogove.ChatMessageBuilder();
+		parentBuilder.localId = "parent-id";
+		parentBuilder.from = JID.parse("alice@example.com");
+		parentBuilder.to = JID.parse("bob@example.com");
+		parentBuilder.senderId = "alice@example.com";
+		parentBuilder.setBody(borogove.Html.text("> quoted line\nactual text\nmore text"));
+		final parent = parentBuilder.build();
+
+		// Reply with an emoji (reaction)
+		final reply = parent.reply();
+		reply.from = JID.parse("bob@example.com");
+		reply.to = JID.parse("alice@example.com");
+		reply.senderId = "bob@example.com";
+		reply.localId = "reply-id";
+		reply.setBody(borogove.Html.text("❤️"));
+		final replyMsg = reply.build();
+		final stanza = replyMsg.asStanza();
+		final body = stanza.getChildText("body");
+		// Reaction should skip the already-quoted line and only quote the first non-quoted line
+		Assert.equals("> actual text…\n\n❤️", body);
+	}
 }