mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(mattermost): preserve markdown formatting and native tables (#18655)
Merged via squash.
Prepared head SHA: d30fff1776
Co-authored-by: echo931 <259437483+echo931@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
This commit is contained in:
82
extensions/mattermost/src/mattermost/monitor-helpers.test.ts
Normal file
82
extensions/mattermost/src/mattermost/monitor-helpers.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeMention } from "./monitor-helpers.js";
|
||||
|
||||
describe("normalizeMention", () => {
|
||||
it("returns trimmed text when no mention provided", () => {
|
||||
expect(normalizeMention(" hello world ", undefined)).toBe("hello world");
|
||||
});
|
||||
|
||||
it("strips bot mention from text", () => {
|
||||
expect(normalizeMention("@echobot hello", "echobot")).toBe("hello");
|
||||
});
|
||||
|
||||
it("strips mention case-insensitively", () => {
|
||||
expect(normalizeMention("@EchoBot hello", "echobot")).toBe("hello");
|
||||
});
|
||||
|
||||
it("preserves newlines in multi-line messages", () => {
|
||||
const input = "@echobot\nline1\nline2\nline3";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toBe("line1\nline2\nline3");
|
||||
});
|
||||
|
||||
it("preserves Markdown headings", () => {
|
||||
const input = "@echobot\n# Heading\n\nSome text";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toContain("# Heading");
|
||||
expect(result).toContain("\n");
|
||||
});
|
||||
|
||||
it("preserves Markdown blockquotes", () => {
|
||||
const input = "@echobot\n> quoted line\n> second line";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toContain("> quoted line");
|
||||
expect(result).toContain("> second line");
|
||||
});
|
||||
|
||||
it("preserves Markdown lists", () => {
|
||||
const input = "@echobot\n- item A\n- item B\n - sub B1";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toContain("- item A");
|
||||
expect(result).toContain("- item B");
|
||||
});
|
||||
|
||||
it("preserves task lists", () => {
|
||||
const input = "@echobot\n- [ ] todo\n- [x] done";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toContain("- [ ] todo");
|
||||
expect(result).toContain("- [x] done");
|
||||
});
|
||||
|
||||
it("handles mention in middle of text", () => {
|
||||
const input = "hey @echobot check this\nout";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toBe("hey check this\nout");
|
||||
});
|
||||
|
||||
it("preserves leading indentation for nested lists", () => {
|
||||
const input = "@echobot\n- item\n - nested\n - deep";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toContain(" - nested");
|
||||
expect(result).toContain(" - deep");
|
||||
});
|
||||
|
||||
it("preserves first-line indentation for nested list items", () => {
|
||||
const input = "@echobot\n - nested\n - deep";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toBe(" - nested\n - deep");
|
||||
});
|
||||
|
||||
it("preserves indented code blocks", () => {
|
||||
const input = "@echobot\ntext\n code line 1\n code line 2";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toContain(" code line 1");
|
||||
expect(result).toContain(" code line 2");
|
||||
});
|
||||
|
||||
it("preserves first-line indentation for indented code blocks", () => {
|
||||
const input = "@echobot\n code line 1\n code line 2";
|
||||
const result = normalizeMention(input, "echobot");
|
||||
expect(result).toBe(" code line 1\n code line 2");
|
||||
});
|
||||
});
|
||||
@@ -70,3 +70,38 @@ export function resolveThreadSessionKeys(params: {
|
||||
normalizeThreadId: (threadId) => threadId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip bot mention from message text while preserving newlines and
|
||||
* block-level Markdown formatting (headings, lists, blockquotes).
|
||||
*/
|
||||
export function normalizeMention(text: string, mention: string | undefined): string {
|
||||
if (!mention) {
|
||||
return text.trim();
|
||||
}
|
||||
const escaped = mention.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const hasMentionRe = new RegExp(`@${escaped}\\b`, "i");
|
||||
const leadingMentionRe = new RegExp(`^([\\t ]*)@${escaped}\\b[\\t ]*`, "i");
|
||||
const trailingMentionRe = new RegExp(`[\\t ]*@${escaped}\\b[\\t ]*$`, "i");
|
||||
const normalizedLines = text.split("\n").map((line) => {
|
||||
const hadMention = hasMentionRe.test(line);
|
||||
const normalizedLine = line
|
||||
.replace(leadingMentionRe, "$1")
|
||||
.replace(trailingMentionRe, "")
|
||||
.replace(new RegExp(`@${escaped}\\b`, "gi"), "")
|
||||
.replace(/(\S)[ \t]{2,}/g, "$1 ");
|
||||
return {
|
||||
text: normalizedLine,
|
||||
mentionOnlyBlank: hadMention && normalizedLine.trim() === "",
|
||||
};
|
||||
});
|
||||
|
||||
while (normalizedLines[0]?.mentionOnlyBlank) {
|
||||
normalizedLines.shift();
|
||||
}
|
||||
while (normalizedLines.at(-1)?.text.trim() === "") {
|
||||
normalizedLines.pop();
|
||||
}
|
||||
|
||||
return normalizedLines.map((line) => line.text).join("\n");
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ import {
|
||||
import {
|
||||
createDedupeCache,
|
||||
formatInboundFromLabel,
|
||||
normalizeMention,
|
||||
resolveThreadSessionKeys,
|
||||
} from "./monitor-helpers.js";
|
||||
import { resolveOncharPrefixes, stripOncharPrefix } from "./monitor-onchar.js";
|
||||
@@ -143,15 +144,6 @@ function resolveRuntime(opts: MonitorMattermostOpts): RuntimeEnv {
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeMention(text: string, mention: string | undefined): string {
|
||||
if (!mention) {
|
||||
return text.trim();
|
||||
}
|
||||
const escaped = mention.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const re = new RegExp(`@${escaped}\\b`, "gi");
|
||||
return text.replace(re, " ").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function isSystemPost(post: MattermostPost): boolean {
|
||||
const type = post.type?.trim();
|
||||
return Boolean(type);
|
||||
|
||||
Reference in New Issue
Block a user