mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-22 23:41:07 +00:00
* fix(whatsapp): convert Markdown bold/strikethrough to WhatsApp formatting * refactor: Move `escapeRegExp` utility function to `utils.js`. --------- Co-authored-by: Luna AI <luna@coredirection.ai>
78 lines
2.4 KiB
TypeScript
78 lines
2.4 KiB
TypeScript
import { escapeRegExp } from "../utils.js";
|
|
/**
|
|
* Convert standard Markdown formatting to WhatsApp-compatible markup.
|
|
*
|
|
* WhatsApp uses its own formatting syntax:
|
|
* bold: *text*
|
|
* italic: _text_
|
|
* strikethrough: ~text~
|
|
* monospace: ```text```
|
|
*
|
|
* Standard Markdown uses:
|
|
* bold: **text** or __text__
|
|
* italic: *text* or _text_
|
|
* strikethrough: ~~text~~
|
|
* code: `text` (inline) or ```text``` (block)
|
|
*
|
|
* The conversion preserves fenced code blocks and inline code,
|
|
* then converts bold and strikethrough markers.
|
|
*/
|
|
|
|
/** Placeholder tokens used during conversion to protect code spans. */
|
|
const FENCE_PLACEHOLDER = "\x00FENCE";
|
|
const INLINE_CODE_PLACEHOLDER = "\x00CODE";
|
|
|
|
/**
|
|
* Convert standard Markdown bold/italic/strikethrough to WhatsApp formatting.
|
|
*
|
|
* Order of operations matters:
|
|
* 1. Protect fenced code blocks (```...```) — already WhatsApp-compatible
|
|
* 2. Protect inline code (`...`) — leave as-is
|
|
* 3. Convert **bold** → *bold* and __bold__ → *bold*
|
|
* 4. Convert ~~strike~~ → ~strike~
|
|
* 5. Restore protected spans
|
|
*
|
|
* Italic *text* and _text_ are left alone since WhatsApp uses _text_ for italic
|
|
* and single * is already WhatsApp bold — no conversion needed for single markers.
|
|
*/
|
|
export function markdownToWhatsApp(text: string): string {
|
|
if (!text) {
|
|
return text;
|
|
}
|
|
|
|
// 1. Extract and protect fenced code blocks
|
|
const fences: string[] = [];
|
|
let result = text.replace(/```[\s\S]*?```/g, (match) => {
|
|
fences.push(match);
|
|
return `${FENCE_PLACEHOLDER}${fences.length - 1}`;
|
|
});
|
|
|
|
// 2. Extract and protect inline code
|
|
const inlineCodes: string[] = [];
|
|
result = result.replace(/`[^`\n]+`/g, (match) => {
|
|
inlineCodes.push(match);
|
|
return `${INLINE_CODE_PLACEHOLDER}${inlineCodes.length - 1}`;
|
|
});
|
|
|
|
// 3. Convert **bold** → *bold* and __bold__ → *bold*
|
|
result = result.replace(/\*\*(.+?)\*\*/g, "*$1*");
|
|
result = result.replace(/__(.+?)__/g, "*$1*");
|
|
|
|
// 4. Convert ~~strikethrough~~ → ~strikethrough~
|
|
result = result.replace(/~~(.+?)~~/g, "~$1~");
|
|
|
|
// 5. Restore inline code
|
|
result = result.replace(
|
|
new RegExp(`${escapeRegExp(INLINE_CODE_PLACEHOLDER)}(\\d+)`, "g"),
|
|
(_, idx) => inlineCodes[Number(idx)] ?? "",
|
|
);
|
|
|
|
// 6. Restore fenced code blocks
|
|
result = result.replace(
|
|
new RegExp(`${escapeRegExp(FENCE_PLACEHOLDER)}(\\d+)`, "g"),
|
|
(_, idx) => fences[Number(idx)] ?? "",
|
|
);
|
|
|
|
return result;
|
|
}
|