1178 lines
41 KiB
JavaScript
1178 lines
41 KiB
JavaScript
"use strict";
|
|
/**
|
|
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.CardConverter = exports.MODE = void 0;
|
|
const utils_1 = require("../utils.js");
|
|
const types_1 = require("./types.js");
|
|
const card_utils_1 = require("./card-utils.js");
|
|
exports.MODE = { Concise: 0, Detailed: 1 };
|
|
const elementConverters = new Map([
|
|
['plain_text', (c, _elem, prop) => c.convertPlainText(prop)],
|
|
['markdown', (c, _elem, prop) => c.convertMarkdown(prop)],
|
|
['markdown_v1', (c, elem, prop) => c.convertMarkdownV1(elem, prop)],
|
|
['text', (c, _elem, prop) => c.convertPlainText(prop)],
|
|
['div', (c, _elem, prop, id) => c.convertDiv(prop, id)],
|
|
['note', (c, _elem, prop) => c.convertNote(prop)],
|
|
['hr', () => '---'],
|
|
['br', () => '\n'],
|
|
['column_set', (c, _elem, prop, _id, depth) => c.convertColumnSet(prop, depth)],
|
|
['column', (c, _elem, prop, _id, depth) => c.convertColumn(prop, depth)],
|
|
['person', (c, _elem, prop, id) => c.convertPerson(prop, id)],
|
|
['person_v1', (c, _elem, prop, id) => c.convertPersonV1(prop, id)],
|
|
['person_list', (c, _elem, prop) => c.convertPersonList(prop)],
|
|
['avatar', (c, _elem, prop, id) => c.convertAvatar(prop, id)],
|
|
['at', (c, _elem, prop) => c.convertAt(prop)],
|
|
['at_all', () => '@所有人'],
|
|
['button', (c, _elem, prop, id) => c.convertButton(prop, id)],
|
|
['actions', (c, _elem, prop) => c.convertActions(prop)],
|
|
['action', (c, _elem, prop) => c.convertActions(prop)],
|
|
['overflow', (c, _elem, prop) => c.convertOverflow(prop)],
|
|
['select_static', (c, _elem, prop, id) => c.convertSelect(prop, id, false)],
|
|
['multi_select_static', (c, _elem, prop, id) => c.convertSelect(prop, id, true)],
|
|
['select_person', (c, _elem, prop, id) => c.convertSelect(prop, id, false)],
|
|
['multi_select_person', (c, _elem, prop, id) => c.convertSelect(prop, id, true)],
|
|
['select_img', (c, _elem, prop, id) => c.convertSelectImg(prop, id)],
|
|
['input', (c, _elem, prop, id) => c.convertInput(prop, id)],
|
|
['date_picker', (c, _elem, prop, id) => c.convertDatePicker(prop, id, 'date')],
|
|
['picker_time', (c, _elem, prop, id) => c.convertDatePicker(prop, id, 'time')],
|
|
['picker_datetime', (c, _elem, prop, id) => c.convertDatePicker(prop, id, 'datetime')],
|
|
['checker', (c, _elem, prop, id) => c.convertChecker(prop, id)],
|
|
['img', (c, _elem, prop, id) => c.convertImage(prop, id)],
|
|
['image', (c, _elem, prop, id) => c.convertImage(prop, id)],
|
|
['img_combination', (c, _elem, prop) => c.convertImgCombination(prop)],
|
|
['table', (c, _elem, prop) => c.convertTable(prop)],
|
|
['chart', (c, _elem, prop, id) => c.convertChart(prop, id)],
|
|
['audio', (c, _elem, prop, id) => c.convertAudio(prop, id)],
|
|
['video', (c, _elem, prop, id) => c.convertVideo(prop, id)],
|
|
['collapsible_panel', (c, _elem, prop, id) => c.convertCollapsiblePanel(prop, id)],
|
|
['form', (c, _elem, prop, id) => c.convertForm(prop, id)],
|
|
['interactive_container', (c, _elem, prop, id) => c.convertInteractiveContainer(prop, id)],
|
|
['text_tag', (c, _elem, prop) => c.convertTextTag(prop)],
|
|
['number_tag', (c, _elem, prop) => c.convertNumberTag(prop)],
|
|
['link', (c, _elem, prop) => c.convertLink(prop)],
|
|
['emoji', (c, _elem, prop) => c.convertEmoji(prop)],
|
|
['local_datetime', (c, _elem, prop) => c.convertLocalDatetime(prop)],
|
|
['list', (c, _elem, prop) => c.convertList(prop)],
|
|
['blockquote', (c, _elem, prop) => c.convertBlockquote(prop)],
|
|
['code_block', (c, _elem, prop) => c.convertCodeBlock(prop)],
|
|
['code_span', (c, _elem, prop) => c.convertCodeSpan(prop)],
|
|
['heading', (c, _elem, prop) => c.convertHeading(prop)],
|
|
['fallback_text', (c, _elem, prop) => c.convertFallbackText(prop)],
|
|
['repeat', (c, _elem, prop) => c.convertRepeat(prop)],
|
|
['card_header', () => ''],
|
|
['custom_icon', () => ''],
|
|
['standard_icon', () => ''],
|
|
]);
|
|
class CardConverter {
|
|
mode;
|
|
attachment;
|
|
constructor(mode) {
|
|
this.mode = mode;
|
|
}
|
|
convert(input) {
|
|
const card = (0, utils_1.safeParse)(input.json_card);
|
|
if (!card) {
|
|
return { content: '<card>\n[无法解析卡片内容]\n</card>', schema: 0 };
|
|
}
|
|
if (input.json_attachment) {
|
|
this.attachment = (0, utils_1.safeParse)(input.json_attachment);
|
|
}
|
|
let schema = input.card_schema ?? 0;
|
|
if (schema === 0) {
|
|
const s = card.schema;
|
|
schema = typeof s === 'number' ? s : 1;
|
|
}
|
|
const header = card.header;
|
|
const title = header ? this.extractHeaderTitle(header, schema) : '';
|
|
const body = this.extractBody(card, schema);
|
|
const bodyContent = body ? this.convertBody(body, schema) : '';
|
|
let out = title ? `<card title="${(0, card_utils_1.escapeAttr)(title)}">\n` : '<card>\n';
|
|
if (bodyContent)
|
|
out += bodyContent + '\n';
|
|
out += '</card>';
|
|
return { content: out, schema };
|
|
}
|
|
extractBody(card, _schema) {
|
|
if (card.body && typeof card.body === 'object') {
|
|
return card.body;
|
|
}
|
|
return undefined;
|
|
}
|
|
extractHeaderTitle(header, _schema) {
|
|
const prop = header.property;
|
|
if (prop) {
|
|
const titleElem = prop.title;
|
|
if (titleElem)
|
|
return this.extractTextContent(titleElem);
|
|
}
|
|
else {
|
|
const titleElem = header.title;
|
|
if (titleElem)
|
|
return this.extractTextContent(titleElem);
|
|
}
|
|
return '';
|
|
}
|
|
convertBody(body, _schema) {
|
|
let elements;
|
|
const prop = body.property;
|
|
if (prop) {
|
|
const e = prop.elements;
|
|
if (Array.isArray(e) && e.length > 0)
|
|
elements = e;
|
|
}
|
|
if (!elements || elements.length === 0) {
|
|
const e = body.elements;
|
|
if (Array.isArray(e))
|
|
elements = e;
|
|
}
|
|
if (!elements || elements.length === 0)
|
|
return '';
|
|
return this.convertElements(elements, 0);
|
|
}
|
|
convertElements(elements, depth) {
|
|
const results = [];
|
|
for (const elem of elements) {
|
|
if (typeof elem !== 'object' || elem == null)
|
|
continue;
|
|
const result = this.convertElement(elem, depth);
|
|
if (result)
|
|
results.push(result);
|
|
}
|
|
return results.join('\n');
|
|
}
|
|
convertElement(elem, depth) {
|
|
const tag = elem.tag ?? '';
|
|
const id = elem.id ?? '';
|
|
const prop = this.extractProperty(elem);
|
|
const fn = elementConverters.get(tag);
|
|
if (fn)
|
|
return fn(this, elem, prop, id, depth);
|
|
return this.convertUnknown(prop, tag);
|
|
}
|
|
extractProperty(elem) {
|
|
if (elem.property && typeof elem.property === 'object') {
|
|
return elem.property;
|
|
}
|
|
return elem;
|
|
}
|
|
extractTextContent(textElem) {
|
|
if (textElem == null)
|
|
return '';
|
|
if (typeof textElem === 'string')
|
|
return textElem;
|
|
if (typeof textElem === 'object') {
|
|
const m = textElem;
|
|
if (m.property && typeof m.property === 'object') {
|
|
return this.extractTextFromProperty(m.property);
|
|
}
|
|
return this.extractTextFromProperty(m);
|
|
}
|
|
return '';
|
|
}
|
|
extractTextFromProperty(prop) {
|
|
const i18n = prop.i18nContent;
|
|
if (i18n && typeof i18n === 'object') {
|
|
for (const lang of ['zh_cn', 'en_us', 'ja_jp']) {
|
|
const t = i18n[lang];
|
|
if (typeof t === 'string' && t)
|
|
return t;
|
|
}
|
|
}
|
|
if (typeof prop.content === 'string')
|
|
return prop.content;
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements) && elements.length > 0) {
|
|
const texts = [];
|
|
for (const elem of elements) {
|
|
if (typeof elem === 'object' && elem != null) {
|
|
const t = this.extractTextContent(elem);
|
|
if (t)
|
|
texts.push(t);
|
|
}
|
|
}
|
|
return texts.join('');
|
|
}
|
|
if (typeof prop.text === 'string')
|
|
return prop.text;
|
|
return '';
|
|
}
|
|
convertPlainText(prop) {
|
|
const content = prop.content;
|
|
if (!content)
|
|
return '';
|
|
const style = this.extractTextStyle(prop);
|
|
return this.applyTextStyle(content, style);
|
|
}
|
|
convertMarkdown(prop) {
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements) && elements.length > 0) {
|
|
return this.convertMarkdownElements(elements);
|
|
}
|
|
if (typeof prop.content === 'string')
|
|
return prop.content;
|
|
return '';
|
|
}
|
|
convertMarkdownV1(elem, prop) {
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements) && elements.length > 0) {
|
|
return this.convertMarkdownElements(elements);
|
|
}
|
|
const fallback = elem.fallback;
|
|
if (fallback && typeof fallback === 'object') {
|
|
return this.convertElement(fallback, 0);
|
|
}
|
|
if (typeof prop.content === 'string')
|
|
return prop.content;
|
|
return '';
|
|
}
|
|
convertMarkdownElements(elements) {
|
|
const parts = [];
|
|
for (const elem of elements) {
|
|
if (typeof elem !== 'object' || elem == null)
|
|
continue;
|
|
const result = this.convertElement(elem, 0);
|
|
if (result)
|
|
parts.push(result);
|
|
}
|
|
return parts.join('');
|
|
}
|
|
convertDiv(prop, _id) {
|
|
const results = [];
|
|
const textElem = prop.text;
|
|
if (textElem && typeof textElem === 'object') {
|
|
const text = this.convertElement(textElem, 0);
|
|
if (text)
|
|
results.push(text);
|
|
}
|
|
const fields = prop.fields;
|
|
if (Array.isArray(fields) && fields.length > 0) {
|
|
const fieldTexts = [];
|
|
for (const field of fields) {
|
|
if (typeof field !== 'object' || field == null)
|
|
continue;
|
|
const fm = field;
|
|
const te = fm.text;
|
|
if (te && typeof te === 'object') {
|
|
const ft = this.convertElement(te, 0);
|
|
if (ft)
|
|
fieldTexts.push(ft);
|
|
}
|
|
}
|
|
if (fieldTexts.length > 0)
|
|
results.push(fieldTexts.join('\n'));
|
|
}
|
|
const extraElem = prop.extra;
|
|
if (extraElem && typeof extraElem === 'object') {
|
|
const extra = this.convertElement(extraElem, 0);
|
|
if (extra)
|
|
results.push(extra);
|
|
}
|
|
return results.join('\n');
|
|
}
|
|
convertNote(prop) {
|
|
const elements = prop.elements;
|
|
if (!Array.isArray(elements) || elements.length === 0)
|
|
return '';
|
|
const texts = [];
|
|
for (const elem of elements) {
|
|
if (typeof elem !== 'object' || elem == null)
|
|
continue;
|
|
const text = this.convertElement(elem, 0);
|
|
if (text)
|
|
texts.push(text);
|
|
}
|
|
if (texts.length === 0)
|
|
return '';
|
|
return `📝 ${texts.join(' ')}`;
|
|
}
|
|
convertLink(prop) {
|
|
const content = prop.content || '链接';
|
|
let url = '';
|
|
const urlObj = prop.url;
|
|
if (urlObj && typeof urlObj === 'object') {
|
|
url = urlObj.url || '';
|
|
}
|
|
if (url)
|
|
return `[${content}](${url})`;
|
|
return content;
|
|
}
|
|
convertEmoji(prop) {
|
|
const key = prop.key || '';
|
|
return types_1.EMOJI_MAP[key] ?? `:${key}:`;
|
|
}
|
|
convertLocalDatetime(prop) {
|
|
const milliseconds = prop.milliseconds;
|
|
const fallbackText = prop.fallbackText;
|
|
if (milliseconds) {
|
|
const formatted = (0, card_utils_1.formatMillisecondsToISO8601)(milliseconds);
|
|
if (formatted)
|
|
return formatted;
|
|
}
|
|
return fallbackText || '';
|
|
}
|
|
convertList(prop) {
|
|
const items = prop.items;
|
|
if (!Array.isArray(items) || items.length === 0)
|
|
return '';
|
|
const lines = [];
|
|
for (const item of items) {
|
|
if (typeof item !== 'object' || item == null)
|
|
continue;
|
|
const im = item;
|
|
const level = im.level || 0;
|
|
const listType = im.type || '';
|
|
const order = im.order || 0;
|
|
const indent = ' '.repeat(level);
|
|
const marker = listType === 'ol' ? `${Math.floor(order)}.` : '-';
|
|
const elements = im.elements;
|
|
if (Array.isArray(elements)) {
|
|
const content = this.convertMarkdownElements(elements);
|
|
lines.push(`${indent}${marker} ${content}`);
|
|
}
|
|
}
|
|
return lines.join('\n');
|
|
}
|
|
convertBlockquote(prop) {
|
|
let content = '';
|
|
if (typeof prop.content === 'string') {
|
|
content = prop.content;
|
|
}
|
|
else {
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements)) {
|
|
content = this.convertMarkdownElements(elements);
|
|
}
|
|
}
|
|
if (!content)
|
|
return '';
|
|
return content
|
|
.split('\n')
|
|
.map((line) => `> ${line}`)
|
|
.join('\n');
|
|
}
|
|
convertCodeBlock(prop) {
|
|
const language = prop.language || 'plaintext';
|
|
let code = '';
|
|
const contents = prop.contents;
|
|
if (Array.isArray(contents)) {
|
|
for (const line of contents) {
|
|
if (typeof line !== 'object' || line == null)
|
|
continue;
|
|
const lm = line;
|
|
const lineContents = lm.contents;
|
|
if (Array.isArray(lineContents)) {
|
|
for (const c of lineContents) {
|
|
if (typeof c !== 'object' || c == null)
|
|
continue;
|
|
const cm = c;
|
|
if (typeof cm.content === 'string')
|
|
code += cm.content;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return `\`\`\`${language}\n${code}\`\`\``;
|
|
}
|
|
convertCodeSpan(prop) {
|
|
const content = prop.content || '';
|
|
return `\`${content}\``;
|
|
}
|
|
convertHeading(prop) {
|
|
let level = prop.level || 1;
|
|
if (level < 1)
|
|
level = 1;
|
|
if (level > 6)
|
|
level = 6;
|
|
let content = '';
|
|
if (typeof prop.content === 'string') {
|
|
content = prop.content;
|
|
}
|
|
else {
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements)) {
|
|
content = this.convertMarkdownElements(elements);
|
|
}
|
|
}
|
|
return `${'#'.repeat(level)} ${content}`;
|
|
}
|
|
convertFallbackText(prop) {
|
|
const textElem = prop.text;
|
|
if (textElem && typeof textElem === 'object') {
|
|
return this.extractTextContent(textElem);
|
|
}
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements)) {
|
|
return this.convertMarkdownElements(elements);
|
|
}
|
|
return '';
|
|
}
|
|
convertTextTag(prop) {
|
|
const textElem = prop.text;
|
|
let text = '';
|
|
if (textElem && typeof textElem === 'object') {
|
|
text = this.extractTextContent(textElem);
|
|
}
|
|
if (!text)
|
|
return '';
|
|
return `「${text}」`;
|
|
}
|
|
convertNumberTag(prop) {
|
|
const textElem = prop.text;
|
|
let text = '';
|
|
if (textElem && typeof textElem === 'object') {
|
|
text = this.extractTextContent(textElem);
|
|
}
|
|
if (!text)
|
|
return '';
|
|
const urlObj = prop.url;
|
|
if (urlObj && typeof urlObj === 'object') {
|
|
const url = urlObj.url;
|
|
if (url)
|
|
return `[${text}](${url})`;
|
|
}
|
|
return text;
|
|
}
|
|
convertUnknown(prop, tag) {
|
|
if (!prop) {
|
|
if (this.mode === exports.MODE.Detailed)
|
|
return `[未知内容](tag:${tag})`;
|
|
return '[未知内容]';
|
|
}
|
|
const paths = ['content', 'text', 'title', 'label', 'placeholder'];
|
|
for (const path of paths) {
|
|
if (prop[path] != null) {
|
|
const text = this.extractTextContent(prop[path]);
|
|
if (text)
|
|
return text;
|
|
}
|
|
}
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements) && elements.length > 0) {
|
|
return this.convertElements(elements, 0);
|
|
}
|
|
if (this.mode === exports.MODE.Detailed)
|
|
return `[未知内容](tag:${tag})`;
|
|
return '[未知内容]';
|
|
}
|
|
convertColumnSet(prop, depth) {
|
|
const columns = prop.columns;
|
|
if (!Array.isArray(columns) || columns.length === 0)
|
|
return '';
|
|
const results = [];
|
|
for (const col of columns) {
|
|
if (typeof col !== 'object' || col == null)
|
|
continue;
|
|
const result = this.convertElement(col, depth + 1);
|
|
if (result)
|
|
results.push(result);
|
|
}
|
|
return results.join('\n\n');
|
|
}
|
|
convertColumn(prop, depth) {
|
|
const elements = prop.elements;
|
|
if (!Array.isArray(elements) || elements.length === 0)
|
|
return '';
|
|
return this.convertElements(elements, depth);
|
|
}
|
|
convertForm(prop, _id) {
|
|
let out = '<form>\n';
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements)) {
|
|
out += this.convertElements(elements, 0);
|
|
}
|
|
out += '\n</form>';
|
|
return out;
|
|
}
|
|
convertCollapsiblePanel(prop, _id) {
|
|
const expanded = prop.expanded === true;
|
|
let title = '详情';
|
|
const header = prop.header;
|
|
if (header && typeof header === 'object') {
|
|
const titleElem = header.title;
|
|
if (titleElem) {
|
|
const t = this.extractTextContent(titleElem);
|
|
if (t)
|
|
title = t;
|
|
}
|
|
}
|
|
const shouldExpand = expanded || this.mode === exports.MODE.Detailed;
|
|
if (shouldExpand) {
|
|
let out = `▼ ${title}\n`;
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements)) {
|
|
const content = this.convertElements(elements, 1);
|
|
for (const line of content.split('\n')) {
|
|
if (line)
|
|
out += ` ${line}\n`;
|
|
}
|
|
}
|
|
out += '▲';
|
|
return out;
|
|
}
|
|
return `▶ ${title}`;
|
|
}
|
|
convertInteractiveContainer(prop, _id) {
|
|
let url = '';
|
|
const actions = prop.actions;
|
|
if (Array.isArray(actions) && actions.length > 0) {
|
|
const action = actions[0];
|
|
if (action && typeof action === 'object') {
|
|
const actionType = action.type;
|
|
if (actionType === 'open_url') {
|
|
const actionData = action.action;
|
|
if (actionData && typeof actionData === 'object') {
|
|
url = actionData.url || '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let out = '<clickable';
|
|
if (url)
|
|
out += ` url="${(0, card_utils_1.escapeAttr)(url)}"`;
|
|
if (this.mode === exports.MODE.Detailed && _id)
|
|
out += ` id="${_id}"`;
|
|
out += '>\n';
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements)) {
|
|
out += this.convertElements(elements, 0);
|
|
}
|
|
out += '\n</clickable>';
|
|
return out;
|
|
}
|
|
convertRepeat(prop) {
|
|
const elements = prop.elements;
|
|
if (Array.isArray(elements)) {
|
|
return this.convertElements(elements, 0);
|
|
}
|
|
return '';
|
|
}
|
|
convertButton(prop, _id) {
|
|
let buttonText = '';
|
|
const textElem = prop.text;
|
|
if (textElem && typeof textElem === 'object') {
|
|
buttonText = this.extractTextContent(textElem);
|
|
}
|
|
if (!buttonText)
|
|
buttonText = '按钮';
|
|
const disabled = prop.disabled === true;
|
|
if (disabled && this.mode === exports.MODE.Concise) {
|
|
return `[${buttonText} ✗]`;
|
|
}
|
|
const actions = prop.actions;
|
|
if (Array.isArray(actions)) {
|
|
for (const action of actions) {
|
|
if (typeof action !== 'object' || action == null)
|
|
continue;
|
|
const am = action;
|
|
if (am.type === 'open_url') {
|
|
const ad = am.action;
|
|
if (ad && typeof ad === 'object') {
|
|
const url = ad.url;
|
|
if (url)
|
|
return `[${buttonText}](${url})`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (disabled && this.mode === exports.MODE.Detailed) {
|
|
let result = `[${buttonText} ✗]`;
|
|
const tips = prop.disabledTips;
|
|
if (tips && typeof tips === 'object') {
|
|
const tipsText = this.extractTextContent(tips);
|
|
if (tipsText)
|
|
result += `(tips:"${tipsText}")`;
|
|
}
|
|
return result;
|
|
}
|
|
return `[${buttonText}]`;
|
|
}
|
|
convertActions(prop) {
|
|
const actions = prop.actions;
|
|
if (!Array.isArray(actions) || actions.length === 0)
|
|
return '';
|
|
const results = [];
|
|
for (const action of actions) {
|
|
if (typeof action !== 'object' || action == null)
|
|
continue;
|
|
const result = this.convertElement(action, 0);
|
|
if (result)
|
|
results.push(result);
|
|
}
|
|
return results.join(' ');
|
|
}
|
|
convertSelect(prop, _id, isMulti) {
|
|
const options = prop.options || [];
|
|
const selectedValues = new Set();
|
|
if (isMulti) {
|
|
const vals = prop.selectedValues;
|
|
if (Array.isArray(vals)) {
|
|
for (const v of vals) {
|
|
if (typeof v === 'string')
|
|
selectedValues.add(v);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
const initialOption = prop.initialOption;
|
|
if (typeof initialOption === 'string')
|
|
selectedValues.add(initialOption);
|
|
const initialIndex = prop.initialIndex;
|
|
if (typeof initialIndex === 'number' && initialIndex >= 0 && initialIndex < options.length) {
|
|
const opt = options[initialIndex];
|
|
if (opt && typeof opt === 'object') {
|
|
const val = opt.value;
|
|
if (val)
|
|
selectedValues.add(val);
|
|
}
|
|
}
|
|
}
|
|
const optionTexts = [];
|
|
let hasSelected = false;
|
|
for (const opt of options) {
|
|
if (typeof opt !== 'object' || opt == null)
|
|
continue;
|
|
const om = opt;
|
|
let optText = '';
|
|
const textElem = om.text;
|
|
if (textElem && typeof textElem === 'object') {
|
|
optText = this.extractTextContent(textElem);
|
|
}
|
|
if (!optText)
|
|
optText = om.value || '';
|
|
if (!optText)
|
|
continue;
|
|
const value = om.value || '';
|
|
if (selectedValues.has(value)) {
|
|
optText = '✓' + optText;
|
|
hasSelected = true;
|
|
}
|
|
optionTexts.push(optText);
|
|
}
|
|
if (optionTexts.length === 0) {
|
|
let placeholder = '请选择';
|
|
const phElem = prop.placeholder;
|
|
if (phElem && typeof phElem === 'object') {
|
|
const ph = this.extractTextContent(phElem);
|
|
if (ph)
|
|
placeholder = ph;
|
|
}
|
|
optionTexts.push(placeholder + ' ▼');
|
|
}
|
|
else if (!hasSelected) {
|
|
optionTexts[optionTexts.length - 1] += ' ▼';
|
|
}
|
|
let result = `{${optionTexts.join(' / ')}}`;
|
|
if (this.mode === exports.MODE.Detailed) {
|
|
const attrs = [];
|
|
if (isMulti)
|
|
attrs.push('multi');
|
|
if (_id.includes('person') || prop.type === 'person')
|
|
attrs.push('type:person');
|
|
if (attrs.length > 0)
|
|
result += `(${attrs.join(' ')})`;
|
|
}
|
|
return result;
|
|
}
|
|
convertSelectImg(prop, _id) {
|
|
const options = prop.options;
|
|
if (!Array.isArray(options))
|
|
return '';
|
|
const selectedValues = new Set();
|
|
const vals = prop.selectedValues;
|
|
if (Array.isArray(vals)) {
|
|
for (const v of vals) {
|
|
if (typeof v === 'string')
|
|
selectedValues.add(v);
|
|
}
|
|
}
|
|
const optTexts = [];
|
|
for (let i = 0; i < options.length; i++) {
|
|
const opt = options[i];
|
|
if (!opt || typeof opt !== 'object')
|
|
continue;
|
|
const value = opt.value || '';
|
|
let text = `🖼️图${i + 1}`;
|
|
if (selectedValues.has(value))
|
|
text = '✓' + text;
|
|
optTexts.push(text);
|
|
}
|
|
return `{${optTexts.join(' / ')}}`;
|
|
}
|
|
convertInput(prop, _id) {
|
|
let label = '';
|
|
const labelElem = prop.label;
|
|
if (labelElem && typeof labelElem === 'object') {
|
|
label = this.extractTextContent(labelElem);
|
|
}
|
|
const defaultValue = prop.defaultValue || '';
|
|
let placeholder = '';
|
|
const phElem = prop.placeholder;
|
|
if (phElem && typeof phElem === 'object') {
|
|
placeholder = this.extractTextContent(phElem);
|
|
}
|
|
let result;
|
|
if (defaultValue) {
|
|
result = defaultValue + '___';
|
|
}
|
|
else if (placeholder) {
|
|
result = placeholder + '_____';
|
|
}
|
|
else {
|
|
result = '_____';
|
|
}
|
|
if (label)
|
|
result = label + ': ' + result;
|
|
const inputType = prop.inputType;
|
|
if (inputType === 'multiline_text') {
|
|
result = result.replace(/_____/g, '...');
|
|
}
|
|
return result;
|
|
}
|
|
convertDatePicker(prop, _id, pickerType) {
|
|
let emoji;
|
|
let value = '';
|
|
switch (pickerType) {
|
|
case 'date':
|
|
emoji = '📅';
|
|
value = prop.initialDate || '';
|
|
break;
|
|
case 'time':
|
|
emoji = '🕐';
|
|
value = prop.initialTime || '';
|
|
break;
|
|
case 'datetime':
|
|
emoji = '📅';
|
|
value = prop.initialDatetime || '';
|
|
break;
|
|
default:
|
|
emoji = '📅';
|
|
}
|
|
if (value)
|
|
value = (0, card_utils_1.normalizeTimeFormat)(value);
|
|
if (!value) {
|
|
let placeholder = '选择';
|
|
const phElem = prop.placeholder;
|
|
if (phElem && typeof phElem === 'object') {
|
|
const ph = this.extractTextContent(phElem);
|
|
if (ph)
|
|
placeholder = ph;
|
|
}
|
|
value = placeholder;
|
|
}
|
|
return `${emoji} ${value}`;
|
|
}
|
|
convertChecker(prop, _id) {
|
|
const checked = prop.checked === true;
|
|
const checkMark = checked ? '[x]' : '[ ]';
|
|
let text = '';
|
|
const textElem = prop.text;
|
|
if (textElem && typeof textElem === 'object') {
|
|
text = this.extractTextContent(textElem);
|
|
}
|
|
let result = `${checkMark} ${text}`;
|
|
if (this.mode === exports.MODE.Detailed && _id) {
|
|
result += `(id:${_id})`;
|
|
}
|
|
return result;
|
|
}
|
|
convertOverflow(prop) {
|
|
const options = prop.options;
|
|
if (!Array.isArray(options) || options.length === 0)
|
|
return '';
|
|
const optTexts = [];
|
|
for (const opt of options) {
|
|
if (typeof opt !== 'object' || opt == null)
|
|
continue;
|
|
const om = opt;
|
|
const textElem = om.text;
|
|
if (textElem && typeof textElem === 'object') {
|
|
const text = this.extractTextContent(textElem);
|
|
if (text)
|
|
optTexts.push(text);
|
|
}
|
|
}
|
|
return `⋮ ${optTexts.join(', ')}`;
|
|
}
|
|
convertPerson(prop, _id) {
|
|
const userID = prop.userID || '';
|
|
if (!userID)
|
|
return '';
|
|
let personName = '';
|
|
if (this.attachment) {
|
|
const persons = this.attachment.persons;
|
|
if (persons && typeof persons === 'object') {
|
|
const person = persons[userID];
|
|
if (person && typeof person === 'object') {
|
|
const content = person.content;
|
|
if (content)
|
|
personName = content;
|
|
}
|
|
}
|
|
}
|
|
if (!personName) {
|
|
const notation = prop.notation;
|
|
if (notation && typeof notation === 'object') {
|
|
personName = this.extractTextContent(notation);
|
|
}
|
|
}
|
|
if (personName) {
|
|
if (this.mode === exports.MODE.Detailed)
|
|
return `@${personName}(open_id:${userID})`;
|
|
return `@${personName}`;
|
|
}
|
|
if (this.mode === exports.MODE.Detailed)
|
|
return `@用户(open_id:${userID})`;
|
|
return `@${userID}`;
|
|
}
|
|
convertPersonV1(prop, _id) {
|
|
const userID = prop.userID || '';
|
|
if (!userID)
|
|
return '';
|
|
let personName = '';
|
|
if (this.attachment) {
|
|
const persons = this.attachment.persons;
|
|
if (persons && typeof persons === 'object') {
|
|
const person = persons[userID];
|
|
if (person && typeof person === 'object') {
|
|
const content = person.content;
|
|
if (content)
|
|
personName = content;
|
|
}
|
|
}
|
|
}
|
|
if (personName) {
|
|
if (this.mode === exports.MODE.Detailed)
|
|
return `@${personName}(open_id:${userID})`;
|
|
return `@${personName}`;
|
|
}
|
|
if (this.mode === exports.MODE.Detailed)
|
|
return `@用户(open_id:${userID})`;
|
|
return `@${userID}`;
|
|
}
|
|
convertPersonList(prop) {
|
|
const persons = prop.persons;
|
|
if (!Array.isArray(persons) || persons.length === 0)
|
|
return '';
|
|
const names = [];
|
|
for (const person of persons) {
|
|
if (typeof person !== 'object' || person == null)
|
|
continue;
|
|
const pm = person;
|
|
const personID = pm.id || '';
|
|
const name = '用户';
|
|
if (this.mode === exports.MODE.Detailed && personID) {
|
|
names.push(`@${name}(id:${personID})`);
|
|
}
|
|
else {
|
|
names.push(`@${name}`);
|
|
}
|
|
}
|
|
return names.join(', ');
|
|
}
|
|
convertAvatar(prop, _id) {
|
|
const userID = prop.userID || '';
|
|
let result = '👤';
|
|
if (this.mode === exports.MODE.Detailed && userID) {
|
|
result += `(id:${userID})`;
|
|
}
|
|
return result;
|
|
}
|
|
convertAt(prop) {
|
|
const userID = prop.userID || '';
|
|
if (!userID)
|
|
return '';
|
|
let userName = '';
|
|
let actualUserID = '';
|
|
if (this.attachment) {
|
|
const atUsers = this.attachment.at_users;
|
|
if (atUsers && typeof atUsers === 'object') {
|
|
const userInfo = atUsers[userID];
|
|
if (userInfo && typeof userInfo === 'object') {
|
|
const content = userInfo.content;
|
|
if (content)
|
|
userName = content;
|
|
const uid = userInfo.user_id;
|
|
if (uid)
|
|
actualUserID = uid;
|
|
}
|
|
}
|
|
}
|
|
if (userName) {
|
|
if (this.mode === exports.MODE.Detailed) {
|
|
if (actualUserID)
|
|
return `@${userName}(user_id:${actualUserID})`;
|
|
return `@${userName}(open_id:${userID})`;
|
|
}
|
|
return `@${userName}`;
|
|
}
|
|
if (this.mode === exports.MODE.Detailed) {
|
|
if (actualUserID)
|
|
return `@用户(user_id:${actualUserID})`;
|
|
return `@用户(open_id:${userID})`;
|
|
}
|
|
return `@${userID}`;
|
|
}
|
|
convertImage(prop, _id) {
|
|
let alt = '图片';
|
|
const altElem = prop.alt;
|
|
if (altElem && typeof altElem === 'object') {
|
|
const altText = this.extractTextContent(altElem);
|
|
if (altText)
|
|
alt = altText;
|
|
}
|
|
const titleElem = prop.title;
|
|
if (titleElem && typeof titleElem === 'object') {
|
|
const titleText = this.extractTextContent(titleElem);
|
|
if (titleText)
|
|
alt = titleText;
|
|
}
|
|
let result = `🖼️ ${alt}`;
|
|
if (this.mode === exports.MODE.Detailed) {
|
|
const imageID = prop.imageID;
|
|
if (imageID) {
|
|
const token = this.getImageToken(imageID);
|
|
if (token) {
|
|
result += `(img_token:${token})`;
|
|
}
|
|
else {
|
|
result += `(img_key:${imageID})`;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
convertImgCombination(prop) {
|
|
const imgList = prop.imgList;
|
|
if (!Array.isArray(imgList) || imgList.length === 0)
|
|
return '';
|
|
let result = `🖼️ ${imgList.length}张图片`;
|
|
if (this.mode === exports.MODE.Detailed) {
|
|
const keys = [];
|
|
for (const img of imgList) {
|
|
if (typeof img !== 'object' || img == null)
|
|
continue;
|
|
const im = img;
|
|
const imageID = im.imageID;
|
|
if (imageID)
|
|
keys.push(imageID);
|
|
}
|
|
if (keys.length > 0)
|
|
result += `(keys:${keys.join(',')})`;
|
|
}
|
|
return result;
|
|
}
|
|
convertChart(prop, _id) {
|
|
let title = '图表';
|
|
let chartType = '';
|
|
const chartSpec = prop.chartSpec;
|
|
if (chartSpec && typeof chartSpec === 'object') {
|
|
const titleObj = chartSpec.title;
|
|
if (titleObj && typeof titleObj === 'object') {
|
|
const text = titleObj.text;
|
|
if (text)
|
|
title = text;
|
|
}
|
|
const ct = chartSpec.type;
|
|
if (ct) {
|
|
chartType = ct;
|
|
const typeName = types_1.CHART_TYPE_NAMES[chartType];
|
|
if (typeName)
|
|
title = `${title}${typeName}`;
|
|
}
|
|
}
|
|
const summary = this.extractChartSummary(prop, chartType);
|
|
let result = `📊 ${title}`;
|
|
if (summary)
|
|
result += `\n数据摘要: ${summary}`;
|
|
return result;
|
|
}
|
|
extractChartSummary(prop, chartType) {
|
|
const chartSpec = prop.chartSpec;
|
|
if (!chartSpec || typeof chartSpec !== 'object')
|
|
return '';
|
|
const dataObj = chartSpec.data;
|
|
if (!dataObj || typeof dataObj !== 'object')
|
|
return '';
|
|
const values = dataObj.values;
|
|
if (!Array.isArray(values) || values.length === 0)
|
|
return '';
|
|
switch (chartType) {
|
|
case 'line':
|
|
case 'bar':
|
|
case 'area':
|
|
return this.extractLineBarSummary(chartSpec, values);
|
|
case 'pie':
|
|
return this.extractPieSummary(chartSpec, values);
|
|
default:
|
|
return this.extractGenericSummary(values);
|
|
}
|
|
}
|
|
extractLineBarSummary(chartSpec, values) {
|
|
const xField = chartSpec.xField;
|
|
const yField = chartSpec.yField;
|
|
if (!xField || !yField || values.length === 0) {
|
|
return this.extractGenericSummary(values);
|
|
}
|
|
const parts = [];
|
|
for (const v of values) {
|
|
if (typeof v !== 'object' || v == null)
|
|
continue;
|
|
const vm = v;
|
|
parts.push(`${vm[xField]}:${vm[yField]}`);
|
|
}
|
|
return parts.length > 0 ? parts.join(', ') : this.extractGenericSummary(values);
|
|
}
|
|
extractPieSummary(chartSpec, values) {
|
|
const categoryField = chartSpec.categoryField;
|
|
const valueField = chartSpec.valueField;
|
|
if (!categoryField || !valueField || values.length === 0) {
|
|
return this.extractGenericSummary(values);
|
|
}
|
|
const parts = [];
|
|
for (const v of values) {
|
|
if (typeof v !== 'object' || v == null)
|
|
continue;
|
|
const vm = v;
|
|
parts.push(`${vm[categoryField]}:${vm[valueField]}`);
|
|
}
|
|
return parts.length > 0 ? parts.join(', ') : this.extractGenericSummary(values);
|
|
}
|
|
extractGenericSummary(values) {
|
|
return `${values.length}个数据点`;
|
|
}
|
|
convertAudio(prop, _id) {
|
|
let result = '🎵 音频';
|
|
if (this.mode === exports.MODE.Detailed) {
|
|
const fileID = prop.fileID || prop.audioID || '';
|
|
if (fileID)
|
|
result += `(key:${fileID})`;
|
|
}
|
|
return result;
|
|
}
|
|
convertVideo(prop, _id) {
|
|
let result = '🎬 视频';
|
|
if (this.mode === exports.MODE.Detailed) {
|
|
const fileID = prop.fileID || prop.videoID || '';
|
|
if (fileID)
|
|
result += `(key:${fileID})`;
|
|
}
|
|
return result;
|
|
}
|
|
convertTable(prop) {
|
|
const columns = prop.columns;
|
|
if (!Array.isArray(columns) || columns.length === 0)
|
|
return '';
|
|
const rows = prop.rows || [];
|
|
const colNames = [];
|
|
const colKeys = [];
|
|
for (const col of columns) {
|
|
if (typeof col !== 'object' || col == null)
|
|
continue;
|
|
const cm = col;
|
|
let displayName = cm.displayName || '';
|
|
const name = cm.name || '';
|
|
if (!displayName)
|
|
displayName = name;
|
|
colNames.push(displayName);
|
|
colKeys.push(name);
|
|
}
|
|
const lines = [];
|
|
lines.push('| ' + colNames.join(' | ') + ' |');
|
|
lines.push('|' + colNames.map(() => '------|').join(''));
|
|
for (const row of rows) {
|
|
if (typeof row !== 'object' || row == null)
|
|
continue;
|
|
const rm = row;
|
|
const cells = [];
|
|
for (const key of colKeys) {
|
|
let cellValue = '';
|
|
const cellData = rm[key];
|
|
if (cellData && typeof cellData === 'object') {
|
|
if (cellData.data != null) {
|
|
cellValue = this.extractTableCellValue(cellData.data);
|
|
}
|
|
}
|
|
cells.push(cellValue);
|
|
}
|
|
lines.push('| ' + cells.join(' | ') + ' |');
|
|
}
|
|
return lines.join('\n');
|
|
}
|
|
extractTableCellValue(data) {
|
|
if (typeof data === 'string')
|
|
return data;
|
|
if (typeof data === 'number')
|
|
return data.toFixed(2);
|
|
if (Array.isArray(data)) {
|
|
const texts = [];
|
|
for (const item of data) {
|
|
if (typeof item === 'object' && item != null) {
|
|
const im = item;
|
|
if (typeof im.text === 'string')
|
|
texts.push(`「${im.text}」`);
|
|
}
|
|
}
|
|
return texts.join(' ');
|
|
}
|
|
if (typeof data === 'object' && data != null) {
|
|
return this.extractTextContent(data);
|
|
}
|
|
return '';
|
|
}
|
|
extractTextStyle(prop) {
|
|
const style = {
|
|
bold: false,
|
|
italic: false,
|
|
strikethrough: false,
|
|
};
|
|
const textStyle = prop.textStyle;
|
|
if (!textStyle || typeof textStyle !== 'object')
|
|
return style;
|
|
const attrs = textStyle.attributes;
|
|
if (Array.isArray(attrs)) {
|
|
for (const attr of attrs) {
|
|
if (typeof attr !== 'string')
|
|
continue;
|
|
switch (attr) {
|
|
case 'bold':
|
|
style.bold = true;
|
|
break;
|
|
case 'italic':
|
|
style.italic = true;
|
|
break;
|
|
case 'strikethrough':
|
|
style.strikethrough = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return style;
|
|
}
|
|
applyTextStyle(content, style) {
|
|
if (!content)
|
|
return content;
|
|
if (style.strikethrough)
|
|
content = `~~${content}~~`;
|
|
if (style.italic)
|
|
content = `*${content}*`;
|
|
if (style.bold)
|
|
content = `**${content}**`;
|
|
return content;
|
|
}
|
|
getImageToken(imageID) {
|
|
if (!this.attachment)
|
|
return '';
|
|
const images = this.attachment.images;
|
|
if (!images || typeof images !== 'object')
|
|
return '';
|
|
const imageInfo = images[imageID];
|
|
if (!imageInfo || typeof imageInfo !== 'object')
|
|
return '';
|
|
return imageInfo.token || '';
|
|
}
|
|
}
|
|
exports.CardConverter = CardConverter;
|