@openclaw/line 2026.7.2-beta.7 → 2026.8.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +2 -3
- package/dist/{card-command-ty11OJNt.js → card-command-DALdUSnc.js} +14 -22
- package/dist/{channel-D4i_K4fQ.js → channel-CAdQJRw7.js} +74 -76
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-CuKoy9xw.js +4 -0
- package/dist/{flex-templates-iKJsyCq-.js → flex-templates-BBBlNqpV.js} +108 -81
- package/dist/index.js +1 -1
- package/dist/markdown-to-line-D4I-6lm6.js +499 -0
- package/dist/{monitor-DXEodYFJ.js → monitor-QEh_ZqyR.js} +94 -43
- package/dist/monitor.runtime-72isajCp.js +2 -0
- package/dist/outbound.runtime-CWQMIE7N.js +3 -0
- package/dist/{reply-payload-transform-D5-dHB8b.js → reply-payload-transform-BOPGHVPo.js} +4 -3
- package/dist/runtime-api.js +6 -5
- package/dist/send-CFFr0-rZ.js +366 -0
- package/dist/setup-api.js +1 -1
- package/dist/{setup-surface-DRQ7n0En.js → setup-surface-W0Dbgu58.js} +2 -1
- package/openclaw.plugin.json +7 -0
- package/package.json +9 -5
- package/dist/channel.runtime-BdhK8Xw7.js +0 -4
- package/dist/markdown-to-line-DMDQTMhx.js +0 -799
- package/dist/monitor.runtime-CZquR7jI.js +0 -2
- package/dist/outbound.runtime-BS0IhU_M.js +0 -2
- package/dist/send-receipt-CY2ey8XH.js +0 -107
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { f as messageAction, g as uriAction, h as postbackAction, o as createReceiptCard, p as normalizeLineAction } from "./flex-templates-BBBlNqpV.js";
|
|
2
|
+
import { markdownToIRWithMeta, stripMarkdown, stripMarkdown as stripMarkdown$1 } from "openclaw/plugin-sdk/text-chunking";
|
|
3
|
+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
4
|
+
//#region extensions/line/src/flex-templates/message.ts
|
|
5
|
+
/**
|
|
6
|
+
* Wrap a FlexContainer in a FlexMessage
|
|
7
|
+
*/
|
|
8
|
+
function toFlexMessage(altText, contents) {
|
|
9
|
+
return {
|
|
10
|
+
type: "flex",
|
|
11
|
+
altText,
|
|
12
|
+
contents
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region extensions/line/src/template-messages.ts
|
|
17
|
+
const COMPACT_TEMPLATE_TEXT_LIMIT = 60;
|
|
18
|
+
const TEMPLATE_ALT_TEXT_LIMIT = 1500;
|
|
19
|
+
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
20
|
+
function buildTemplatePayloadAction(action) {
|
|
21
|
+
if (action.type === "uri" && action.uri) return uriAction(action.label, action.uri);
|
|
22
|
+
if (action.type === "postback" && action.data) return postbackAction(action.label, action.data, action.label);
|
|
23
|
+
return messageAction(action.label, action.data ?? action.label);
|
|
24
|
+
}
|
|
25
|
+
function resolveTemplateTextLimit(params) {
|
|
26
|
+
return params.title !== void 0 || params.thumbnailImageUrl !== void 0 ? COMPACT_TEMPLATE_TEXT_LIMIT : params.textOnlyLimit;
|
|
27
|
+
}
|
|
28
|
+
function truncateTemplateText(text, limit) {
|
|
29
|
+
let result = "";
|
|
30
|
+
for (const { segment } of graphemeSegmenter.segment(text)) {
|
|
31
|
+
if (result.length + segment.length > limit) {
|
|
32
|
+
if (!result) for (const codePoint of segment) {
|
|
33
|
+
if (result.length + codePoint.length > limit) break;
|
|
34
|
+
result += codePoint;
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
result += segment;
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function truncateOptionalTemplateText(value, limit) {
|
|
43
|
+
return value === void 0 ? void 0 : truncateTemplateText(value, limit);
|
|
44
|
+
}
|
|
45
|
+
function resolveTemplateAltText(value, fallback) {
|
|
46
|
+
return truncateTemplateText(value ?? fallback, TEMPLATE_ALT_TEXT_LIMIT);
|
|
47
|
+
}
|
|
48
|
+
function normalizeCarouselColumnActions(column) {
|
|
49
|
+
return {
|
|
50
|
+
...column,
|
|
51
|
+
actions: column.actions.map((action) => normalizeLineAction(action)),
|
|
52
|
+
defaultAction: column.defaultAction === void 0 ? void 0 : normalizeLineAction(column.defaultAction)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a confirm template (yes/no style dialog)
|
|
57
|
+
*/
|
|
58
|
+
function createConfirmTemplate(text, confirmAction, cancelAction, altText) {
|
|
59
|
+
const template = {
|
|
60
|
+
type: "confirm",
|
|
61
|
+
text: truncateTemplateText(text, 240),
|
|
62
|
+
actions: [normalizeLineAction(confirmAction), normalizeLineAction(cancelAction)]
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
type: "template",
|
|
66
|
+
altText: resolveTemplateAltText(altText, text),
|
|
67
|
+
template
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create a button template with title, text, and action buttons
|
|
72
|
+
*/
|
|
73
|
+
function createButtonTemplate(title, text, actions, options) {
|
|
74
|
+
const normalizedTitle = title || void 0;
|
|
75
|
+
const textLimit = resolveTemplateTextLimit({
|
|
76
|
+
title: normalizedTitle,
|
|
77
|
+
thumbnailImageUrl: options?.thumbnailImageUrl,
|
|
78
|
+
textOnlyLimit: 160
|
|
79
|
+
});
|
|
80
|
+
const template = {
|
|
81
|
+
type: "buttons",
|
|
82
|
+
...normalizedTitle ? { title: truncateTemplateText(normalizedTitle, 40) } : {},
|
|
83
|
+
text: truncateTemplateText(text, textLimit),
|
|
84
|
+
actions: actions.slice(0, 4).map((action) => normalizeLineAction(action)),
|
|
85
|
+
thumbnailImageUrl: options?.thumbnailImageUrl,
|
|
86
|
+
imageAspectRatio: options?.imageAspectRatio ?? "rectangle",
|
|
87
|
+
imageSize: options?.imageSize ?? "cover",
|
|
88
|
+
imageBackgroundColor: options?.imageBackgroundColor,
|
|
89
|
+
defaultAction: options?.defaultAction === void 0 ? void 0 : normalizeLineAction(options.defaultAction)
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
type: "template",
|
|
93
|
+
altText: resolveTemplateAltText(options?.altText, normalizedTitle ? `${normalizedTitle}: ${text}` : text),
|
|
94
|
+
template
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create a carousel template with multiple columns
|
|
99
|
+
*/
|
|
100
|
+
function createTemplateCarousel(columns, options) {
|
|
101
|
+
const template = {
|
|
102
|
+
type: "carousel",
|
|
103
|
+
columns: columns.slice(0, 10).map(normalizeCarouselColumnActions),
|
|
104
|
+
imageAspectRatio: options?.imageAspectRatio ?? "rectangle",
|
|
105
|
+
imageSize: options?.imageSize ?? "cover"
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
type: "template",
|
|
109
|
+
altText: resolveTemplateAltText(options?.altText, "View carousel"),
|
|
110
|
+
template
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Create a carousel column for use with createTemplateCarousel
|
|
115
|
+
*/
|
|
116
|
+
function createCarouselColumn(params) {
|
|
117
|
+
const textLimit = resolveTemplateTextLimit({
|
|
118
|
+
...params,
|
|
119
|
+
textOnlyLimit: 120
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
title: truncateOptionalTemplateText(params.title, 40),
|
|
123
|
+
text: truncateTemplateText(params.text, textLimit),
|
|
124
|
+
actions: params.actions.slice(0, 3).map((action) => normalizeLineAction(action)),
|
|
125
|
+
thumbnailImageUrl: params.thumbnailImageUrl,
|
|
126
|
+
imageBackgroundColor: params.imageBackgroundColor,
|
|
127
|
+
defaultAction: params.defaultAction === void 0 ? void 0 : normalizeLineAction(params.defaultAction)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Convert a TemplateMessagePayload from ReplyPayload to a LINE TemplateMessage
|
|
132
|
+
*/
|
|
133
|
+
function buildTemplateMessageFromPayload(payload) {
|
|
134
|
+
switch (payload.type) {
|
|
135
|
+
case "confirm": {
|
|
136
|
+
const confirmAction = payload.confirmData.startsWith("http") ? uriAction(payload.confirmLabel, payload.confirmData) : payload.confirmData.includes("=") ? postbackAction(payload.confirmLabel, payload.confirmData, payload.confirmLabel) : messageAction(payload.confirmLabel, payload.confirmData);
|
|
137
|
+
const cancelAction = payload.cancelData.startsWith("http") ? uriAction(payload.cancelLabel, payload.cancelData) : payload.cancelData.includes("=") ? postbackAction(payload.cancelLabel, payload.cancelData, payload.cancelLabel) : messageAction(payload.cancelLabel, payload.cancelData);
|
|
138
|
+
return createConfirmTemplate(payload.text, confirmAction, cancelAction, payload.altText);
|
|
139
|
+
}
|
|
140
|
+
case "buttons": {
|
|
141
|
+
const actions = payload.actions.slice(0, 4).map((action) => buildTemplatePayloadAction(action));
|
|
142
|
+
return createButtonTemplate(payload.title, payload.text, actions, {
|
|
143
|
+
thumbnailImageUrl: payload.thumbnailImageUrl,
|
|
144
|
+
altText: payload.altText
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
case "carousel": return createTemplateCarousel(payload.columns.slice(0, 10).map((col) => {
|
|
148
|
+
const colActions = col.actions.slice(0, 3).map((action) => buildTemplatePayloadAction(action));
|
|
149
|
+
return createCarouselColumn({
|
|
150
|
+
title: col.title,
|
|
151
|
+
text: col.text,
|
|
152
|
+
thumbnailImageUrl: col.thumbnailImageUrl,
|
|
153
|
+
actions: colActions
|
|
154
|
+
});
|
|
155
|
+
}), { altText: payload.altText });
|
|
156
|
+
default: return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region extensions/line/src/markdown-to-line.ts
|
|
161
|
+
const LINE_MARKDOWN_OPTIONS = {
|
|
162
|
+
assistantTranscriptRoleHeaders: true,
|
|
163
|
+
autolink: false,
|
|
164
|
+
blockquotePrefix: "",
|
|
165
|
+
headingStyle: "none",
|
|
166
|
+
horizontalRuleText: "",
|
|
167
|
+
linkify: false,
|
|
168
|
+
preserveSourceBlockSpacing: true
|
|
169
|
+
};
|
|
170
|
+
const TRANSCRIPT_ROLE_PREFIX = "[assistant-authored transcript] ";
|
|
171
|
+
const LINE_FLEX_BUBBLE_MAX_BYTES = 3e4;
|
|
172
|
+
function parseLineMarkdown(text, tableMode = "block") {
|
|
173
|
+
return markdownToIRWithMeta(text, {
|
|
174
|
+
...LINE_MARKDOWN_OPTIONS,
|
|
175
|
+
tableMode
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function toMarkdownTable(table) {
|
|
179
|
+
return {
|
|
180
|
+
headers: table.headers,
|
|
181
|
+
rows: table.rows,
|
|
182
|
+
headerCells: table.headerCells,
|
|
183
|
+
rowCells: table.rowCells
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function codeBlockSpans(ir) {
|
|
187
|
+
return ir.styles.filter((span) => span.style === "code_block");
|
|
188
|
+
}
|
|
189
|
+
function toCodeBlock(ir, span) {
|
|
190
|
+
return {
|
|
191
|
+
...span.language ? { language: span.language } : {},
|
|
192
|
+
code: ir.text.slice(span.start, span.end).trimEnd()
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function rangesOverlap(left, right) {
|
|
196
|
+
return left.start < right.end && right.start < left.end;
|
|
197
|
+
}
|
|
198
|
+
function projectPlainText(ir, omitted = [], additionalInsertions = [], onSegment) {
|
|
199
|
+
const insertions = [...additionalInsertions];
|
|
200
|
+
for (const link of ir.links) {
|
|
201
|
+
if (omitted.some((range) => rangesOverlap(range, link))) continue;
|
|
202
|
+
const href = link.href.trim();
|
|
203
|
+
const label = ir.text.slice(link.start, link.end).trim();
|
|
204
|
+
const comparableHref = href.startsWith("mailto:") ? href.slice(7) : href;
|
|
205
|
+
if (href && label && label !== href && label !== comparableHref) insertions.push({
|
|
206
|
+
position: link.end,
|
|
207
|
+
text: ` (${href})`
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
for (const annotation of ir.annotations ?? []) if (annotation.type === "assistant_transcript_role" && !omitted.some((range) => rangesOverlap(range, annotation))) insertions.push({
|
|
211
|
+
position: annotation.start,
|
|
212
|
+
text: TRANSCRIPT_ROLE_PREFIX
|
|
213
|
+
});
|
|
214
|
+
const inlineCodeSpans = ir.styles.filter((span) => span.style === "code" && !omitted.some((range) => rangesOverlap(range, span)));
|
|
215
|
+
for (const span of inlineCodeSpans) if (stripMarkdown(ir.text.slice(span.start, span.end), { assistantTranscriptRoleHeaders: true }).startsWith(TRANSCRIPT_ROLE_PREFIX)) insertions.push({
|
|
216
|
+
position: span.start,
|
|
217
|
+
text: TRANSCRIPT_ROLE_PREFIX
|
|
218
|
+
});
|
|
219
|
+
insertions.sort((left, right) => left.position - right.position);
|
|
220
|
+
const underlineTags = [...ir.text.matchAll(/<\/?u>/gi)].map((match) => ({
|
|
221
|
+
start: match.index,
|
|
222
|
+
end: match.index + match[0].length
|
|
223
|
+
})).filter((tag) => !inlineCodeSpans.some((span) => rangesOverlap(tag, span)) && !omitted.some((span) => rangesOverlap(tag, span)));
|
|
224
|
+
const removed = [...omitted, ...underlineTags].toSorted((left, right) => left.start - right.start);
|
|
225
|
+
let output = "";
|
|
226
|
+
let segmentStart = 0;
|
|
227
|
+
let cursor = 0;
|
|
228
|
+
let insertionIndex = 0;
|
|
229
|
+
const appendRange = (end) => {
|
|
230
|
+
while (insertionIndex < insertions.length) {
|
|
231
|
+
const insertion = insertions[insertionIndex];
|
|
232
|
+
if (!insertion || insertion.position > end) break;
|
|
233
|
+
if (insertion.position >= cursor) {
|
|
234
|
+
output += ir.text.slice(cursor, insertion.position);
|
|
235
|
+
if ("text" in insertion) output += insertion.text;
|
|
236
|
+
else if (onSegment) {
|
|
237
|
+
const precedingText = output.slice(segmentStart).trim();
|
|
238
|
+
if (precedingText) onSegment({
|
|
239
|
+
type: "text",
|
|
240
|
+
text: precedingText
|
|
241
|
+
});
|
|
242
|
+
onSegment({
|
|
243
|
+
type: "flex",
|
|
244
|
+
message: insertion.message
|
|
245
|
+
});
|
|
246
|
+
segmentStart = output.length;
|
|
247
|
+
}
|
|
248
|
+
cursor = insertion.position;
|
|
249
|
+
}
|
|
250
|
+
insertionIndex += 1;
|
|
251
|
+
}
|
|
252
|
+
output += ir.text.slice(cursor, end);
|
|
253
|
+
cursor = end;
|
|
254
|
+
};
|
|
255
|
+
for (const range of removed) {
|
|
256
|
+
appendRange(range.start);
|
|
257
|
+
cursor = Math.max(cursor, range.end);
|
|
258
|
+
while (insertionIndex < insertions.length && (insertions[insertionIndex]?.position ?? cursor) < cursor) insertionIndex += 1;
|
|
259
|
+
}
|
|
260
|
+
appendRange(ir.text.length);
|
|
261
|
+
if (onSegment) {
|
|
262
|
+
const trailingText = output.slice(segmentStart).trim();
|
|
263
|
+
if (trailingText) onSegment({
|
|
264
|
+
type: "text",
|
|
265
|
+
text: trailingText
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return output.trim();
|
|
269
|
+
}
|
|
270
|
+
function formatOversizedTableAsBullets(table) {
|
|
271
|
+
const markdownCell = (cell) => projectPlainText(cell).replace(/[\\|`*_[\]~<>&]/gu, "\\$&").replace(/\r?\n/gu, " ");
|
|
272
|
+
const markdownRow = (cells) => `| ${cells.map(markdownCell).join(" | ")} |`;
|
|
273
|
+
return projectPlainText(parseLineMarkdown([
|
|
274
|
+
markdownRow(table.headerCells),
|
|
275
|
+
`| ${table.headerCells.map(() => "---").join(" | ")} |`,
|
|
276
|
+
...table.rowCells.map(markdownRow)
|
|
277
|
+
].join("\n"), "bullets").ir);
|
|
278
|
+
}
|
|
279
|
+
function sameSpanStyle(left, right) {
|
|
280
|
+
return left.weight === right.weight && left.style === right.style && left.decoration === right.decoration;
|
|
281
|
+
}
|
|
282
|
+
function renderTableCell(cell, fallback) {
|
|
283
|
+
if (!cell?.text.trim()) return {
|
|
284
|
+
text: fallback,
|
|
285
|
+
hasMarkup: false
|
|
286
|
+
};
|
|
287
|
+
const codeSpans = cell.styles.filter((span) => span.style === "code");
|
|
288
|
+
const tags = [...cell.text.matchAll(/<\/?u>/gi)].map((match) => ({
|
|
289
|
+
start: match.index,
|
|
290
|
+
end: match.index + match[0].length,
|
|
291
|
+
closing: match[0][1] === "/"
|
|
292
|
+
})).filter((tag) => !codeSpans.some((span) => rangesOverlap(tag, span)));
|
|
293
|
+
const boundaries = /* @__PURE__ */ new Set([0, cell.text.length]);
|
|
294
|
+
for (const style of cell.styles) {
|
|
295
|
+
boundaries.add(style.start);
|
|
296
|
+
boundaries.add(style.end);
|
|
297
|
+
}
|
|
298
|
+
for (const link of cell.links) boundaries.add(link.end);
|
|
299
|
+
for (const tag of tags) {
|
|
300
|
+
boundaries.add(tag.start);
|
|
301
|
+
boundaries.add(tag.end);
|
|
302
|
+
}
|
|
303
|
+
const sortedBoundaries = [...boundaries].toSorted((left, right) => left - right);
|
|
304
|
+
const spans = [];
|
|
305
|
+
let underlineDepth = 0;
|
|
306
|
+
let hasMarkup = false;
|
|
307
|
+
const appendSpan = (span) => {
|
|
308
|
+
const previous = spans.at(-1);
|
|
309
|
+
if (previous && sameSpanStyle(previous, span)) previous.text = `${previous.text ?? ""}${span.text ?? ""}`;
|
|
310
|
+
else spans.push(span);
|
|
311
|
+
};
|
|
312
|
+
for (let index = 0; index < sortedBoundaries.length - 1; index += 1) {
|
|
313
|
+
const start = sortedBoundaries[index];
|
|
314
|
+
const end = sortedBoundaries[index + 1];
|
|
315
|
+
if (start === void 0 || end === void 0) continue;
|
|
316
|
+
const tag = tags.find((candidate) => candidate.start === start);
|
|
317
|
+
if (tag) {
|
|
318
|
+
underlineDepth += tag.closing ? -1 : 1;
|
|
319
|
+
hasMarkup = true;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const text = cell.text.slice(start, end);
|
|
323
|
+
if (text) {
|
|
324
|
+
const active = new Set(cell.styles.filter((style) => style.start <= start && style.end >= end).map((style) => style.style));
|
|
325
|
+
const weight = active.has("bold") ? "bold" : void 0;
|
|
326
|
+
const style = active.has("italic") ? "italic" : void 0;
|
|
327
|
+
const decoration = active.has("strikethrough") ? "line-through" : underlineDepth > 0 ? "underline" : void 0;
|
|
328
|
+
hasMarkup ||= weight !== void 0 || style !== void 0 || decoration !== void 0;
|
|
329
|
+
appendSpan({
|
|
330
|
+
type: "span",
|
|
331
|
+
text,
|
|
332
|
+
weight,
|
|
333
|
+
style,
|
|
334
|
+
decoration
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
for (const link of cell.links.filter((candidate) => candidate.end === end)) {
|
|
338
|
+
const href = link.href.trim();
|
|
339
|
+
const label = cell.text.slice(link.start, link.end).trim();
|
|
340
|
+
if (href && label && label !== href) {
|
|
341
|
+
appendSpan({
|
|
342
|
+
type: "span",
|
|
343
|
+
text: ` (${href})`
|
|
344
|
+
});
|
|
345
|
+
hasMarkup = true;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
text: spans.map((span) => span.text ?? "").join("").trim() || fallback,
|
|
351
|
+
...hasMarkup ? { contents: spans } : {},
|
|
352
|
+
hasMarkup
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function plainTableCell(text) {
|
|
356
|
+
return parseLineMarkdown(text, "off").ir;
|
|
357
|
+
}
|
|
358
|
+
/** Convert a markdown table to a LINE Flex Message bubble. */
|
|
359
|
+
function convertTableToFlexBubble(table) {
|
|
360
|
+
const headerCells = (table.headerCells ?? table.headers.map(plainTableCell)).map((cell) => renderTableCell(cell, "-"));
|
|
361
|
+
const rowCells = (table.rowCells ?? table.rows.map((row) => row.map(plainTableCell))).map((row) => row.map((cell) => renderTableCell(cell, "-")));
|
|
362
|
+
const hasInlineMarkup = headerCells.some((cell) => cell.hasMarkup) || rowCells.some((row) => row.some((cell) => cell.hasMarkup));
|
|
363
|
+
if (table.headers.length === 2 && !hasInlineMarkup) return createReceiptCard({
|
|
364
|
+
title: headerCells.map((cell) => cell.text).join(" / "),
|
|
365
|
+
items: rowCells.map((row) => ({
|
|
366
|
+
name: row[0]?.text ?? "-",
|
|
367
|
+
value: row[1]?.text ?? "-"
|
|
368
|
+
}))
|
|
369
|
+
});
|
|
370
|
+
return {
|
|
371
|
+
type: "bubble",
|
|
372
|
+
body: {
|
|
373
|
+
type: "box",
|
|
374
|
+
layout: "vertical",
|
|
375
|
+
contents: [
|
|
376
|
+
{
|
|
377
|
+
type: "box",
|
|
378
|
+
layout: "horizontal",
|
|
379
|
+
contents: headerCells.map((cell) => ({
|
|
380
|
+
type: "text",
|
|
381
|
+
text: cell.text,
|
|
382
|
+
contents: cell.contents,
|
|
383
|
+
weight: "bold",
|
|
384
|
+
size: "sm",
|
|
385
|
+
color: "#333333",
|
|
386
|
+
flex: 1,
|
|
387
|
+
wrap: true
|
|
388
|
+
})),
|
|
389
|
+
paddingBottom: "sm"
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
type: "separator",
|
|
393
|
+
margin: "sm"
|
|
394
|
+
},
|
|
395
|
+
...rowCells.slice(0, 10).map((row, rowIndex) => ({
|
|
396
|
+
type: "box",
|
|
397
|
+
layout: "horizontal",
|
|
398
|
+
contents: table.headers.map((_, colIndex) => {
|
|
399
|
+
const cell = row[colIndex] ?? {
|
|
400
|
+
text: "-",
|
|
401
|
+
hasMarkup: false
|
|
402
|
+
};
|
|
403
|
+
return {
|
|
404
|
+
type: "text",
|
|
405
|
+
text: cell.text,
|
|
406
|
+
contents: cell.contents,
|
|
407
|
+
size: "sm",
|
|
408
|
+
color: "#666666",
|
|
409
|
+
flex: 1,
|
|
410
|
+
wrap: true
|
|
411
|
+
};
|
|
412
|
+
}),
|
|
413
|
+
margin: rowIndex === 0 ? "md" : "sm"
|
|
414
|
+
}))
|
|
415
|
+
],
|
|
416
|
+
paddingAll: "lg"
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/** Convert a code block to a LINE Flex Message bubble. */
|
|
421
|
+
function convertCodeBlockToFlexBubble(block) {
|
|
422
|
+
const titleText = block.language ? `Code (${block.language})` : "Code";
|
|
423
|
+
const displayCode = block.code.length > 2e3 ? truncateUtf16Safe(block.code, 2e3) + "\n..." : block.code;
|
|
424
|
+
return {
|
|
425
|
+
type: "bubble",
|
|
426
|
+
body: {
|
|
427
|
+
type: "box",
|
|
428
|
+
layout: "vertical",
|
|
429
|
+
contents: [{
|
|
430
|
+
type: "text",
|
|
431
|
+
text: titleText,
|
|
432
|
+
weight: "bold",
|
|
433
|
+
size: "sm",
|
|
434
|
+
color: "#666666"
|
|
435
|
+
}, {
|
|
436
|
+
type: "box",
|
|
437
|
+
layout: "vertical",
|
|
438
|
+
contents: [{
|
|
439
|
+
type: "text",
|
|
440
|
+
text: displayCode,
|
|
441
|
+
size: "xs",
|
|
442
|
+
color: "#333333",
|
|
443
|
+
wrap: true
|
|
444
|
+
}],
|
|
445
|
+
backgroundColor: "#F5F5F5",
|
|
446
|
+
paddingAll: "md",
|
|
447
|
+
cornerRadius: "md",
|
|
448
|
+
margin: "sm"
|
|
449
|
+
}],
|
|
450
|
+
paddingAll: "lg"
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/** Parse once, route existing block surfaces to Flex, and project the remainder as plain text. */
|
|
455
|
+
function processLineMessage(text) {
|
|
456
|
+
const { ir, tables } = parseLineMarkdown(text);
|
|
457
|
+
const codeSpans = codeBlockSpans(ir);
|
|
458
|
+
const flexMessages = [];
|
|
459
|
+
const plainTextInsertions = [];
|
|
460
|
+
let hasOversizedTable = false;
|
|
461
|
+
for (const table of tables) {
|
|
462
|
+
const bubble = table.rowCells.length > 10 && (table.headers.length !== 2 || table.rowCells.length > 12 || [table.headerCells, ...table.rowCells].some((cells) => cells.some((cell) => renderTableCell(cell, "-").hasMarkup))) ? void 0 : convertTableToFlexBubble(toMarkdownTable(table));
|
|
463
|
+
if (!bubble || Buffer.byteLength(JSON.stringify(bubble), "utf8") > LINE_FLEX_BUBBLE_MAX_BYTES) {
|
|
464
|
+
hasOversizedTable = true;
|
|
465
|
+
plainTextInsertions.push({
|
|
466
|
+
position: table.placeholderOffset,
|
|
467
|
+
text: `\n\n${formatOversizedTableAsBullets(table)}\n\n`
|
|
468
|
+
});
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const message = toFlexMessage("Table", bubble);
|
|
472
|
+
flexMessages.push(message);
|
|
473
|
+
plainTextInsertions.push({
|
|
474
|
+
position: table.placeholderOffset,
|
|
475
|
+
message
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
for (const span of codeSpans) {
|
|
479
|
+
const message = toFlexMessage("Code", convertCodeBlockToFlexBubble(toCodeBlock(ir, span)));
|
|
480
|
+
flexMessages.push(message);
|
|
481
|
+
plainTextInsertions.push({
|
|
482
|
+
position: span.start,
|
|
483
|
+
message
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
const segments = [];
|
|
487
|
+
return {
|
|
488
|
+
text: projectPlainText(ir, codeSpans, plainTextInsertions, hasOversizedTable ? (segment) => segments.push(segment) : void 0),
|
|
489
|
+
flexMessages,
|
|
490
|
+
...hasOversizedTable ? { segments } : {}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
/** Check if text contains markdown that needs conversion. */
|
|
494
|
+
function hasMarkdownToConvert(text) {
|
|
495
|
+
const { ir, tables } = parseLineMarkdown(text);
|
|
496
|
+
return tables.length > 0 || ir.styles.length > 0 || ir.links.length > 0 || /<\/?u>/i.test(ir.text) || ir.text !== text.trimEnd();
|
|
497
|
+
}
|
|
498
|
+
//#endregion
|
|
499
|
+
export { stripMarkdown$1 as a, createCarouselColumn as c, toFlexMessage as d, processLineMessage as i, createConfirmTemplate as l, convertTableToFlexBubble as n, buildTemplateMessageFromPayload as o, hasMarkdownToConvert as r, createButtonTemplate as s, convertCodeBlockToFlexBubble as t, createTemplateCarousel as u };
|