Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
416
extensions/line/src/markdown-to-line.ts
Normal file
416
extensions/line/src/markdown-to-line.ts
Normal file
@@ -0,0 +1,416 @@
|
||||
// Line plugin module implements markdown to line behavior.
|
||||
import type { messagingApi } from "@line/bot-sdk";
|
||||
import { stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { uriAction } from "./actions.js";
|
||||
import { createReceiptCard, toFlexMessage, type FlexBubble } from "./flex-templates.js";
|
||||
export { stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
|
||||
|
||||
type FlexMessage = messagingApi.FlexMessage;
|
||||
type FlexComponent = messagingApi.FlexComponent;
|
||||
type FlexText = messagingApi.FlexText;
|
||||
type FlexBox = messagingApi.FlexBox;
|
||||
|
||||
export interface ProcessedLineMessage {
|
||||
/** The processed text with markdown stripped */
|
||||
text: string;
|
||||
/** Flex messages extracted from tables/code blocks */
|
||||
flexMessages: FlexMessage[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex patterns for markdown detection
|
||||
*/
|
||||
const MARKDOWN_TABLE_REGEX = /^\|(.+)\|[\r\n]+\|[-:\s|]+\|[\r\n]+((?:\|.+\|[\r\n]*)+)/gm;
|
||||
const MARKDOWN_CODE_BLOCK_REGEX = /```(\w*)\n([\s\S]*?)```/g;
|
||||
const MARKDOWN_LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
|
||||
|
||||
/**
|
||||
* Detect and extract markdown tables from text
|
||||
*/
|
||||
export function extractMarkdownTables(text: string): {
|
||||
tables: MarkdownTable[];
|
||||
textWithoutTables: string;
|
||||
} {
|
||||
const tables: MarkdownTable[] = [];
|
||||
let textWithoutTables = text;
|
||||
|
||||
// Reset regex state
|
||||
MARKDOWN_TABLE_REGEX.lastIndex = 0;
|
||||
|
||||
let match: RegExpExecArray | null;
|
||||
const matches: { fullMatch: string; table: MarkdownTable }[] = [];
|
||||
|
||||
while ((match = MARKDOWN_TABLE_REGEX.exec(text)) !== null) {
|
||||
const fullMatch = match[0];
|
||||
const headerLine = match[1];
|
||||
const bodyLines = match[2];
|
||||
|
||||
const headers = parseTableRow(headerLine);
|
||||
const rows = bodyLines
|
||||
.trim()
|
||||
.split(/[\r\n]+/)
|
||||
.filter((line) => line.trim())
|
||||
.map(parseTableRow);
|
||||
|
||||
if (headers.length > 0 && rows.length > 0) {
|
||||
matches.push({
|
||||
fullMatch,
|
||||
table: { headers, rows },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove tables from text in reverse order to preserve indices
|
||||
for (let i = matches.length - 1; i >= 0; i--) {
|
||||
const { fullMatch, table } = matches[i];
|
||||
tables.unshift(table);
|
||||
textWithoutTables = textWithoutTables.replace(fullMatch, "");
|
||||
}
|
||||
|
||||
return { tables, textWithoutTables };
|
||||
}
|
||||
|
||||
export interface MarkdownTable {
|
||||
headers: string[];
|
||||
rows: string[][];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single table row (pipe-separated values)
|
||||
*/
|
||||
function parseTableRow(row: string): string[] {
|
||||
return row
|
||||
.split("|")
|
||||
.map((cell) => cell.trim())
|
||||
.filter((cell, index, arr) => {
|
||||
// Filter out empty cells at start/end (from leading/trailing pipes)
|
||||
if (index === 0 && cell === "") {
|
||||
return false;
|
||||
}
|
||||
if (index === arr.length - 1 && cell === "") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a markdown table to a LINE Flex Message bubble
|
||||
*/
|
||||
export function convertTableToFlexBubble(table: MarkdownTable): FlexBubble {
|
||||
const parseCell = (
|
||||
value: string | undefined,
|
||||
): { text: string; bold: boolean; hasMarkup: boolean } => {
|
||||
const raw = value?.trim() ?? "";
|
||||
if (!raw) {
|
||||
return { text: "-", bold: false, hasMarkup: false };
|
||||
}
|
||||
|
||||
let hasMarkup = false;
|
||||
const stripped = raw.replace(/\*\*(.+?)\*\*/g, (_, inner) => {
|
||||
hasMarkup = true;
|
||||
return String(inner);
|
||||
});
|
||||
const text = stripped.trim() || "-";
|
||||
const bold = /^\*\*.+\*\*$/.test(raw);
|
||||
|
||||
return { text, bold, hasMarkup };
|
||||
};
|
||||
|
||||
const headerCells = table.headers.map((header) => parseCell(header));
|
||||
const rowCells = table.rows.map((row) => row.map((cell) => parseCell(cell)));
|
||||
const hasInlineMarkup =
|
||||
headerCells.some((cell) => cell.hasMarkup) ||
|
||||
rowCells.some((row) => row.some((cell) => cell.hasMarkup));
|
||||
|
||||
// For simple 2-column tables, use receipt card format
|
||||
if (table.headers.length === 2 && !hasInlineMarkup) {
|
||||
const items = rowCells.map((row) => ({
|
||||
name: row[0]?.text ?? "-",
|
||||
value: row[1]?.text ?? "-",
|
||||
}));
|
||||
|
||||
return createReceiptCard({
|
||||
title: headerCells.map((cell) => cell.text).join(" / "),
|
||||
items,
|
||||
});
|
||||
}
|
||||
|
||||
// For multi-column tables, create a custom layout
|
||||
const headerRow: FlexComponent = {
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: headerCells.map((cell) => ({
|
||||
type: "text",
|
||||
text: cell.text,
|
||||
weight: "bold",
|
||||
size: "sm",
|
||||
color: "#333333",
|
||||
flex: 1,
|
||||
wrap: true,
|
||||
})) as FlexText[],
|
||||
paddingBottom: "sm",
|
||||
} as FlexBox;
|
||||
|
||||
const dataRows: FlexComponent[] = rowCells.slice(0, 10).map((row, rowIndex) => {
|
||||
const rowContents = table.headers.map((_, colIndex) => {
|
||||
const cell = row[colIndex] ?? { text: "-", bold: false, hasMarkup: false };
|
||||
return {
|
||||
type: "text",
|
||||
text: cell.text,
|
||||
size: "sm",
|
||||
color: "#666666",
|
||||
flex: 1,
|
||||
wrap: true,
|
||||
weight: cell.bold ? "bold" : undefined,
|
||||
};
|
||||
}) as FlexText[];
|
||||
|
||||
return {
|
||||
type: "box",
|
||||
layout: "horizontal",
|
||||
contents: rowContents,
|
||||
margin: rowIndex === 0 ? "md" : "sm",
|
||||
} as FlexBox;
|
||||
});
|
||||
|
||||
return {
|
||||
type: "bubble",
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [headerRow, { type: "separator", margin: "sm" }, ...dataRows],
|
||||
paddingAll: "lg",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect and extract code blocks from text
|
||||
*/
|
||||
export function extractCodeBlocks(text: string): {
|
||||
codeBlocks: CodeBlock[];
|
||||
textWithoutCode: string;
|
||||
} {
|
||||
const codeBlocks: CodeBlock[] = [];
|
||||
let textWithoutCode = text;
|
||||
|
||||
// Reset regex state
|
||||
MARKDOWN_CODE_BLOCK_REGEX.lastIndex = 0;
|
||||
|
||||
let match: RegExpExecArray | null;
|
||||
const matches: { fullMatch: string; block: CodeBlock }[] = [];
|
||||
|
||||
while ((match = MARKDOWN_CODE_BLOCK_REGEX.exec(text)) !== null) {
|
||||
const fullMatch = match[0];
|
||||
const language = match[1] || undefined;
|
||||
const code = match[2];
|
||||
|
||||
matches.push({
|
||||
fullMatch,
|
||||
block: { language, code: code.trim() },
|
||||
});
|
||||
}
|
||||
|
||||
// Remove code blocks in reverse order
|
||||
for (let i = matches.length - 1; i >= 0; i--) {
|
||||
const { fullMatch, block } = matches[i];
|
||||
codeBlocks.unshift(block);
|
||||
textWithoutCode = textWithoutCode.replace(fullMatch, "");
|
||||
}
|
||||
|
||||
return { codeBlocks, textWithoutCode };
|
||||
}
|
||||
|
||||
export interface CodeBlock {
|
||||
language?: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a code block to a LINE Flex Message bubble
|
||||
*/
|
||||
export function convertCodeBlockToFlexBubble(block: CodeBlock): FlexBubble {
|
||||
const titleText = block.language ? `Code (${block.language})` : "Code";
|
||||
|
||||
// Truncate very long code to fit LINE's limits
|
||||
const displayCode =
|
||||
block.code.length > 2000 ? truncateUtf16Safe(block.code, 2000) + "\n..." : block.code;
|
||||
|
||||
return {
|
||||
type: "bubble",
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: titleText,
|
||||
weight: "bold",
|
||||
size: "sm",
|
||||
color: "#666666",
|
||||
} as FlexText,
|
||||
{
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: displayCode,
|
||||
size: "xs",
|
||||
color: "#333333",
|
||||
wrap: true,
|
||||
} as FlexText,
|
||||
],
|
||||
backgroundColor: "#F5F5F5",
|
||||
paddingAll: "md",
|
||||
cornerRadius: "md",
|
||||
margin: "sm",
|
||||
} as FlexBox,
|
||||
],
|
||||
paddingAll: "lg",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract markdown links from text
|
||||
*/
|
||||
export function extractLinks(text: string): { links: MarkdownLink[]; textWithLinks: string } {
|
||||
const links: MarkdownLink[] = [];
|
||||
|
||||
// Reset regex state
|
||||
MARKDOWN_LINK_REGEX.lastIndex = 0;
|
||||
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = MARKDOWN_LINK_REGEX.exec(text)) !== null) {
|
||||
links.push({
|
||||
text: match[1],
|
||||
url: match[2],
|
||||
});
|
||||
}
|
||||
|
||||
// Replace markdown links with just the text (for plain text output)
|
||||
const textWithLinks = text.replace(MARKDOWN_LINK_REGEX, "$1");
|
||||
|
||||
return { links, textWithLinks };
|
||||
}
|
||||
|
||||
export interface MarkdownLink {
|
||||
text: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Flex Message with tappable link buttons
|
||||
*/
|
||||
export function convertLinksToFlexBubble(links: MarkdownLink[]): FlexBubble {
|
||||
const buttons: FlexComponent[] = links.slice(0, 4).map((link, index) => ({
|
||||
type: "button",
|
||||
action: uriAction(link.text, link.url),
|
||||
style: index === 0 ? "primary" : "secondary",
|
||||
margin: index > 0 ? "sm" : undefined,
|
||||
}));
|
||||
|
||||
return {
|
||||
type: "bubble",
|
||||
body: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Links",
|
||||
weight: "bold",
|
||||
size: "md",
|
||||
color: "#333333",
|
||||
} as FlexText,
|
||||
],
|
||||
paddingAll: "lg",
|
||||
paddingBottom: "sm",
|
||||
},
|
||||
footer: {
|
||||
type: "box",
|
||||
layout: "vertical",
|
||||
contents: buttons,
|
||||
paddingAll: "md",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function: Process text for LINE output
|
||||
* - Extracts tables → Flex Messages
|
||||
* - Extracts code blocks → Flex Messages
|
||||
* - Strips remaining markdown
|
||||
* - Returns processed text + Flex Messages
|
||||
*/
|
||||
export function processLineMessage(text: string): ProcessedLineMessage {
|
||||
const flexMessages: FlexMessage[] = [];
|
||||
let processedText = text;
|
||||
|
||||
// 1. Extract and convert tables
|
||||
const { tables, textWithoutTables } = extractMarkdownTables(processedText);
|
||||
processedText = textWithoutTables;
|
||||
|
||||
for (const table of tables) {
|
||||
const bubble = convertTableToFlexBubble(table);
|
||||
flexMessages.push(toFlexMessage("Table", bubble));
|
||||
}
|
||||
|
||||
// 2. Extract and convert code blocks
|
||||
const { codeBlocks, textWithoutCode } = extractCodeBlocks(processedText);
|
||||
processedText = textWithoutCode;
|
||||
|
||||
for (const block of codeBlocks) {
|
||||
const bubble = convertCodeBlockToFlexBubble(block);
|
||||
flexMessages.push(toFlexMessage("Code", bubble));
|
||||
}
|
||||
|
||||
// 3. Handle links - convert [text](url) to plain text for display
|
||||
// (We could also create link buttons, but that can get noisy)
|
||||
const { textWithLinks } = extractLinks(processedText);
|
||||
processedText = textWithLinks;
|
||||
|
||||
// 4. Strip remaining markdown formatting
|
||||
processedText = stripMarkdown(processedText);
|
||||
|
||||
return {
|
||||
text: processedText,
|
||||
flexMessages,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if text contains markdown that needs conversion
|
||||
*/
|
||||
export function hasMarkdownToConvert(text: string): boolean {
|
||||
// Check for tables
|
||||
MARKDOWN_TABLE_REGEX.lastIndex = 0;
|
||||
if (MARKDOWN_TABLE_REGEX.test(text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for code blocks
|
||||
MARKDOWN_CODE_BLOCK_REGEX.lastIndex = 0;
|
||||
if (MARKDOWN_CODE_BLOCK_REGEX.test(text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for other markdown patterns
|
||||
if (/\*\*[^*]+\*\*/.test(text)) {
|
||||
return true;
|
||||
} // bold
|
||||
if (/~~[^~]+~~/.test(text)) {
|
||||
return true;
|
||||
} // strikethrough
|
||||
if (/^#{1,6}\s+/m.test(text)) {
|
||||
return true;
|
||||
} // headers
|
||||
if (/^>\s+/m.test(text)) {
|
||||
return true;
|
||||
} // blockquotes
|
||||
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user