Skip to content

X and Twitch: paragraphs inside a block quote are joined with no space #1161

Open

Description

@abhiramshaji-cyber

Bug Description

Hey, hope you're doing well! It's me again from #1147. Thanks so much for the quick fix in #1156, that was really fast and it works great on Discord, Slack and Google Chat.

While trying a few more adapters I think I noticed a small sibling of that issue: on X and Twitch, a block quote with more than one paragraph comes out with the paragraphs glued together, like First.Second.

For context, XFormatConverter and TwitchFormatConverter turn the markdown a bot posts into plain text for those platforms. Their nodeToText has no branch for blockquote, so the quote falls through to BaseFormatConverter.defaultNodeToText, which joins the child blocks with an empty string.

XFormatConverter.nodeToText falling through to defaultNodeToText

XFormatConverter.nodeToText on main has no blockquote branch, so line 74 passes the quote to BaseFormatConverter.defaultNodeToText, which joins the children with "" at line 857.

flowchart LR
  A["> First.<br>><br>> Second."] --> B["X / Twitch nodeToText"]
  B -- "no blockquote branch" --> C["defaultNodeToText<br>children.join('')"]
  C --> D["First.Second."]
Loading

Steps to Reproduce

  1. Create new XFormatConverter() (or new TwitchFormatConverter()).
  2. Call fromMarkdown("> First.\n>\n> Second.").
  3. The quote has two paragraphs, so the parser gives a blockquote node with two paragraph children.
  4. nodeToText has no blockquote branch, so it calls defaultNodeToText, which runs children.map(nodeConverter).join("").
  5. The output is First.Second. on both X and Twitch.

The same happens with a list inside a quote: "> Intro:\n>\n> - one\n> - two" gives Intro:• one\n• two on X and Intro:• one • two on Twitch.

Expected Behavior

The paragraphs stay apart, the same way they do without a quote ("First.\n\nSecond." gives First.\n\nSecond. on X and First. Second. on Twitch).

  • X: First.\n\nSecond. (or with a > prefix on each line, if you'd like X to show the quote)
  • Twitch: First. Second., since Twitch flattens everything to one line

Actual Behavior

  • X: First.Second. and Intro:• one\n• two
  • Twitch: First.Second. and Intro:• one • two

Code Sample

import { XFormatConverter } from "@chat-adapter/x";
import { TwitchFormatConverter } from "@chat-adapter/twitch";

const md = "> First.\n>\n> Second.";

console.log(JSON.stringify(new XFormatConverter().fromMarkdown(md)));
// "First.Second."

console.log(JSON.stringify(new TwitchFormatConverter().fromMarkdown(md)));
// "First.Second."

Chat SDK Version

4.45.0 (main at 990411e)

Platform Adapter

Other (X, Twitch)

Additional Context

The X converter, where blockquote reaches the fallback:

protected nodeToText(node: Content): string {
if (isTextNode(node)) {
return getNodeValue(node);
}
if (isLinkNode(node)) {
const label = getNodeChildren(node)
.map((child) => this.nodeToText(child))
.join("");
return label && label !== node.url ? `${label} (${node.url})` : node.url;
}
if (isCodeNode(node)) {
return getNodeValue(node);
}
if (isListNode(node)) {
return this.renderList(node, 0, (child) => this.nodeToText(child), "•");
}
if (isTableNode(node)) {
return tableToAscii(node);
}
if (node.type === "break") {
return "\n";
}
if (node.type === "thematicBreak") {
return "---";
}
return this.defaultNodeToText(node, (child) => this.nodeToText(child));
}

The Twitch converter, same spot:

protected nodeToText(node: Content): string {
if (isTextNode(node)) {
return getNodeValue(node);
}
if (isLinkNode(node)) {
const label = getNodeChildren(node)
.map((child) => this.nodeToText(child))
.join("");
return label && label !== node.url ? `${label} (${node.url})` : node.url;
}
if (isCodeNode(node)) {
return getNodeValue(node);
}
if (isListNode(node)) {
return this.renderList(node, 0, (child) => this.nodeToText(child), "•");
}
if (isTableNode(node)) {
const [headerRow, ...bodyRows] = getNodeChildren(node);
return tableToTwitchText(
headerRow ? this.rowToCells(headerRow) : [],
bodyRows.map((row) => this.rowToCells(row))
);
}
if (node.type === "break") {
return "\n";
}
if (node.type === "thematicBreak") {
return "";
}
return this.defaultNodeToText(node, (child) => this.nodeToText(child));
}

The fallback that joins child blocks with "":

protected defaultNodeToText(
node: Content,
nodeConverter: (node: Content) => string
): string {
const children = getNodeChildren(node);
if (children.length > 0) {
return children.map(nodeConverter).join("");
}
return getNodeValue(node);
}

From what I could see, Gmail, Messenger, Twilio, WhatsApp and Instagram go through stringify or toPlainText, so I think only X and Twitch are affected.

I wasn't sure which fix you'd prefer, so I wanted to ask first:

Happy to put up a PR for whichever you like. Thanks again for all your work on this!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions