@open-feishu-card/adapter 0.2.0 → 0.3.0

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/index.js CHANGED
@@ -1,10 +1,1816 @@
1
- /**
2
- * @open-feishu-card/adapter
3
- *
4
- * 公开 DSL(JSON Schema 2.0)↔ 飞书内部 RAW JSON 的双向适配器。
5
- * - dslToRaw: 公开 DSL → 渲染器认得的 RAW 形态
6
- * - rawToDsl: RAW envelope → 公开 DSL,未知/私有组件自动降级 (Fallback)
7
- */
8
- export { dslToRaw } from './dsl-to-raw.js';
9
- export { rawToDsl, degradeUnknown } from './raw-to-dsl.js';
10
- //# sourceMappingURL=index.js.map
1
+ // src/emoji-data.ts
2
+ var EMOJI_KEY_TO_NAME = {
3
+ "AINailedIt": "ai_nailed_it_zh_v2",
4
+ "ANGRY": "angry",
5
+ "APPLAUSE": "applaud_v2",
6
+ "ATTENTION": "attention",
7
+ "AWESOME": "awesome",
8
+ "AWESOMEN": "awesomen_v2",
9
+ "Alarm": "alarm",
10
+ "Ambitious": "bepatientandthinklongterm",
11
+ "BEAR": "bear",
12
+ "BEER": "beer",
13
+ "BETRAYED": "betrayed",
14
+ "BIGKISS": "bigkiss",
15
+ "BLACKFACE": "blackface",
16
+ "BLUBBER": "blubber",
17
+ "BLUSH": "grin",
18
+ "BOMB": "bomb",
19
+ "BULL": "bull",
20
+ "Basketball": "basketball",
21
+ "BeAtTheForefront": "beattheforefront_v1",
22
+ "BeamingFace": "beamingface_v1",
23
+ "BubbleTea": "bubbletea",
24
+ "CAKE": "cake_v2",
25
+ "CALF": "calf",
26
+ "CANDIEDHAWS": "candiedhaws",
27
+ "CHUCKLE": "chuckle",
28
+ "CLAP": "clap",
29
+ "CLEAVER": "cleaver_v3",
30
+ "COMFORT": "comfort",
31
+ "CRAZY": "crazy",
32
+ "CRY": "cry",
33
+ "CUCUMBER": "cucumber_v3",
34
+ "CheckMark": "checkmark",
35
+ "ClownFace": "clownface_v1",
36
+ "Coffee": "coffee_v2",
37
+ "ColdSweat": "coldsweat_v1",
38
+ "CrossMark": "crossmark",
39
+ "CustomerSuccess": "customersuccess_v2",
40
+ "DETERGENT": "detergent",
41
+ "DIZZY": "dizzy",
42
+ "DONE": "done",
43
+ "DONNOTGO": "donnotgo",
44
+ "DROOL": "drool",
45
+ "DROWSY": "drowsy",
46
+ "DULL": "dull",
47
+ "DULLSTARE": "dullstare",
48
+ "DarkApplaud": "darkapplaud_v2",
49
+ "DarkClick": "darkclick_v2",
50
+ "DarkFightOn": "darkfighton_v2",
51
+ "DarkFingerHeart": "darkfingerheart_v2",
52
+ "DarkFistBump": "darkfistbump_v2",
53
+ "DarkHighFive": "darkhighfive_v2",
54
+ "DarkOKHand": "darkokhand",
55
+ "DarkSalute": "darksalute_v2",
56
+ "DarkShake": "darkshake_v2",
57
+ "DarkThanks": "darkthanks_v2",
58
+ "DarkThumbsDown": "darkthumbsdown_v2",
59
+ "DarkThumbsup": "darkthumbsup_v2",
60
+ "DarkWavingHand": "darkwavinghand_v1",
61
+ "DarkYouAreTheBest": "darkyouarethebest_v1",
62
+ "Delighted": "delighted_v1",
63
+ "Drumstick": "drumstick",
64
+ "EATING": "eating",
65
+ "EMBARRASSED": "embarrassed",
66
+ "ENOUGH": "enough",
67
+ "ERROR": "errr",
68
+ "EYESCLOSED": "eyesclosed",
69
+ "EatingFood": "eatingfood_v2",
70
+ "FACEPALM": "facepalm",
71
+ "FINGERHEART": "fingerheart_v2",
72
+ "FIRECRACKER": "firecracker_v3",
73
+ "FIREWORKS": "fireworks_v2",
74
+ "FISTBUMP": "fistbump_v2",
75
+ "FOLLOWME": "followme",
76
+ "FORTUNE": "fortune",
77
+ "FROWN": "frown",
78
+ "Fire": "fire",
79
+ "FullMoonFace": "fullmoonface_v1",
80
+ "GIFT": "gift_v2",
81
+ "GLANCE": "glance",
82
+ "GOODJOB": "goodjob",
83
+ "GeneralBusinessTrip": "generalbusinesstrip_v2",
84
+ "GeneralDoNotDisturb": "generaldonotdisturb_v3",
85
+ "GeneralMoonRest": "generalmoonrest_v2",
86
+ "GeneralSun": "generalsun",
87
+ "GeneralTravellingCar": "generaltravellingcar",
88
+ "GeneralVacation": "onleavestatus",
89
+ "GeneralWorkFromHome": "generalworkfromhome",
90
+ "Get": "get_v3",
91
+ "GoGoGo": "gogogo_v4",
92
+ "HAMMER": "hammer_v1",
93
+ "HAUGHTY": "irritated_v2",
94
+ "HEADSET": "headset_v3",
95
+ "HEART": "heart",
96
+ "HEARTBROKEN": "heartbroken",
97
+ "HIGHFIVE": "highfive_v2",
98
+ "HUG": "hug",
99
+ "HUSKY": "husky",
100
+ "HappyDragon": "happydragon_v3",
101
+ "Hundred": "hundred",
102
+ "INNOCENTSMILE": "innocentsmile",
103
+ "JIAYI": "plusone",
104
+ "JOYFUL": "joyful",
105
+ "JubilantRabbit": "jubilantrabbit_v1",
106
+ "KISS": "kiss",
107
+ "LAUGH": "laugh",
108
+ "LGTM": "lgtm_v3",
109
+ "LIPS": "lips_v1",
110
+ "LOL": "lol",
111
+ "LOOKDOWN": "lookdown",
112
+ "LOVE": "love",
113
+ "LUCK": "luck",
114
+ "Lemon": "lemon_v2",
115
+ "LightApplaud": "lightapplaud_v2",
116
+ "LightClick": "lightclick_v2",
117
+ "LightFightOn": "lightfighton_v2",
118
+ "LightFingerHeart": "lightfingerheart_v2",
119
+ "LightFistBump": "lightfistbump_v2",
120
+ "LightHighFive": "lighthighfive_v2",
121
+ "LightOKHand": "lightokhand",
122
+ "LightSalute": "lightsalute_v2",
123
+ "LightShake": "lightshake_v2",
124
+ "LightThanks": "lightthanks_v2",
125
+ "LightThumbsDown": "lightthumbsdown_v2",
126
+ "LightThumbsup": "lightthumbsup_v2",
127
+ "LightWavingHand": "lightwavinghand_v1",
128
+ "LightYouAreTheBest": "lightyouarethebest_v1",
129
+ "Loudspeaker": "loudspeaker",
130
+ "MONEY": "money",
131
+ "MUSCLE": "fighton_v2",
132
+ "MeMeMe": "mememe",
133
+ "MediumApplaud": "mediumapplaud_v2",
134
+ "MediumClick": "mediumclick_v2",
135
+ "MediumDarkApplaud": "mediumdarkapplaud_v2",
136
+ "MediumDarkClick": "mediumdarkclick_v2",
137
+ "MediumDarkFightOn": "mediumdarkfighton_v2",
138
+ "MediumDarkFingerHeart": "mediumdarkfingerheart_v2",
139
+ "MediumDarkFistBump": "mediumdarkfistbump_v2",
140
+ "MediumDarkHighFive": "mediumdarkhighfive_v2",
141
+ "MediumDarkSalute": "mediumdarksalute_v2",
142
+ "MediumDarkShake": "mediumdarkshake_v2",
143
+ "MediumDarkThanks": "mediumdarkthanks_v2",
144
+ "MediumDarkThumbsDown": "mediumdarkthumbsdown_v2",
145
+ "MediumDarkThumbsup": "mediumdarkthumbsup_v2",
146
+ "MediumFightOn": "mediumfighton_v2",
147
+ "MediumFingerHeart": "mediumfingerheart_v2",
148
+ "MediumFistBump": "mediumfistbump_v2",
149
+ "MediumHighFive": "mediumhighfive_v2",
150
+ "MediumLightApplaud": "mediumlightapplaud_v2",
151
+ "MediumLightClick": "mediumlightclick_v2",
152
+ "MediumLightFightOn": "mediumlightfighton_v2",
153
+ "MediumLightFingerHeart": "mediumlightfingerheart_v2",
154
+ "MediumLightFistBump": "mediumlightfistbump_v2",
155
+ "MediumLightHighFive": "mediumlighthighfive_v2",
156
+ "MediumLightSalute": "mediumlightsalute_v2",
157
+ "MediumLightShake": "mediumlightshake_v2",
158
+ "MediumLightThanks": "mediumlightthanks_v2",
159
+ "MediumLightThumbsDown": "mediumlightthumbsdown_v2",
160
+ "MediumLightThumbsup": "mediumlightthumbsup_v2",
161
+ "MediumOKHand": "mediumokhand",
162
+ "MediumSalute": "mediumsalute_v2",
163
+ "MediumShake": "mediumshake_v2",
164
+ "MediumThanks": "mediumthanks_v2",
165
+ "MediumThumbsDown": "mediumthumbsdown_v2",
166
+ "MediumThumbsup": "mediumthumbsup_v2",
167
+ "MediumWavingHand": "mediumwavinghand_v1",
168
+ "MediumYouAreTheBest": "mediumyouarethebest_v1",
169
+ "MediumdarkOKHand": "mediumdarkokhand",
170
+ "MediumdarkWavingHand": "mediumdarkwavinghand_v1",
171
+ "MediumdarkYouAreTheBest": "mediumdarkyouarethebest_v1",
172
+ "MediumlightOKHand": "mediumlightokhand",
173
+ "MediumlightWavingHand": "mediumlightwavinghand_v1",
174
+ "MediumlightYouAreTheBest": "mediumlightyouarethebest_v1",
175
+ "MinusOne": "minusone_v3",
176
+ "MoonRabbit": "moonrabbit_v2",
177
+ "Mooncake": "mooncake_v2",
178
+ "Movie": "movie_v1",
179
+ "Music": "music_v2",
180
+ "NOSEPICK": "nosepick",
181
+ "No": "no_v3",
182
+ "OBSESSED": "hearts_v2",
183
+ "OK": "ok_v2",
184
+ "OKHand": "okhand",
185
+ "OKR": "okr",
186
+ "OnIt": "onit_v1",
187
+ "OneSecond": "onesecond",
188
+ "PARTY": "party",
189
+ "PETRIFIED": "petrified",
190
+ "POOP": "poop",
191
+ "PRAISE": "praise",
192
+ "PROUD": "proud",
193
+ "PUKE": "puke",
194
+ "Partying": "partying_v1",
195
+ "Pepper": "pepper",
196
+ "Pin": "pin",
197
+ "Pumpkin": "pumpkin_v1",
198
+ "PursueUltimate": "pursueultimate_v2",
199
+ "RAINBOWPUKE": "rainbowpuke_v2",
200
+ "REDPACKET": "redpacket",
201
+ "ROSE": "rose",
202
+ "Responsible": "bereliableandtakeownership",
203
+ "RoarForYou": "roarforyou",
204
+ "SALUTE": "salute_v2",
205
+ "SCOWL": "scowl",
206
+ "SHAKE": "shake_v2",
207
+ "SHHH": "shhh",
208
+ "SHOCKED": "shocked",
209
+ "SHOWOFF": "showoff",
210
+ "SHY": "shy",
211
+ "SICK": "sick",
212
+ "SILENT": "silent",
213
+ "SKULL": "skull",
214
+ "SLAP": "slap",
215
+ "SLEEP": "sleep",
216
+ "SLIGHT": "slight",
217
+ "SMART": "smart",
218
+ "SMILE": "smile",
219
+ "SMIRK": "smirk",
220
+ "SMOOCH": "smooch",
221
+ "SMUG": "smug",
222
+ "SOB": "sob",
223
+ "SPEECHLESS": "speechless",
224
+ "SPITBLOOD": "spitblood",
225
+ "STRIVE": "strive",
226
+ "SWEAT": "sweat",
227
+ "SaluteFace": "saluteface_v1",
228
+ "Shrug": "shrug_v1",
229
+ "SiSiASYouWish": "sisiasyouwish_v1",
230
+ "Sigh": "sigh",
231
+ "Snowman": "snowman",
232
+ "Soccer": "soccer",
233
+ "StatusBus": "statusbus",
234
+ "StatusEnjoyLife": "statusenjoylife",
235
+ "StatusInFlight": "statusinflight",
236
+ "StatusReading": "statusreading",
237
+ "Status_PrivateMessage": "status_privatemessage",
238
+ "StickyRiceBalls": "stickyriceballs",
239
+ "TEARS": "tears",
240
+ "TEASE": "tease",
241
+ "TERROR": "terror",
242
+ "THANKS": "thanks_v2",
243
+ "THINKING": "thinking",
244
+ "THUMBSUP": "thumbsup_v2",
245
+ "TOASTED": "toasted",
246
+ "TONGUE": "tongue",
247
+ "TRICK": "trick",
248
+ "TV": "tv_v1",
249
+ "ThanksFace": "thanksface_v1",
250
+ "ThanksHoldingBoard": "thanksholdingboard_v1",
251
+ "ThumbsDown": "thumbsdown_v2",
252
+ "Trophy": "trophy",
253
+ "Typing": "typing",
254
+ "UPPERLEFT": "click_v2",
255
+ "VRHeadset": "vrheadset_v1",
256
+ "WAIL": "wail",
257
+ "WAVE": "wave",
258
+ "WELLDONE": "welldone",
259
+ "WHAT": "what",
260
+ "WHIMPER": "whimper",
261
+ "WINK": "wink",
262
+ "WITTY": "witty",
263
+ "WOW": "wow",
264
+ "WRONGED": "wronged",
265
+ "WavingHand": "wavinghand_v1",
266
+ "XBLUSH": "blush",
267
+ "XmasHat": "xmashat",
268
+ "XmasTree": "xmastree",
269
+ "YAWN": "yawn",
270
+ "YEAH": "yeah",
271
+ "Yes": "yes_v2",
272
+ "YouAreTheBest": "youarethebest"
273
+ };
274
+
275
+ // src/md-parse.ts
276
+ function styleAttrs(s) {
277
+ const attributes = [];
278
+ if (s.bold) attributes.push("bold");
279
+ if (s.italic) attributes.push("italic");
280
+ if (s.strike) attributes.push("strikethrough");
281
+ const hasColor = s.color && s.color !== "default";
282
+ if (attributes.length === 0 && !hasColor) return void 0;
283
+ const ts = {};
284
+ if (attributes.length) ts.attributes = attributes;
285
+ if (hasColor) ts.color = s.color;
286
+ return ts;
287
+ }
288
+ function makeText(content, s) {
289
+ const property = { content };
290
+ const ts = styleAttrs(s);
291
+ if (ts) property.textStyle = ts;
292
+ return { tag: "plain_text", property };
293
+ }
294
+ function parseInline(text) {
295
+ const out = [];
296
+ const style = { bold: false, italic: false, strike: false };
297
+ let buf = "";
298
+ const flush = () => {
299
+ if (buf) {
300
+ out.push(makeText(buf, style));
301
+ buf = "";
302
+ }
303
+ };
304
+ let i = 0;
305
+ const n = text.length;
306
+ const startsWith = (s) => text.startsWith(s, i);
307
+ while (i < n) {
308
+ const c = text[i];
309
+ if (c === "\n") {
310
+ flush();
311
+ out.push({ tag: "br", property: {} });
312
+ i += 1;
313
+ continue;
314
+ }
315
+ if (c === "\\" && i + 1 < n) {
316
+ buf += text[i + 1];
317
+ i += 2;
318
+ continue;
319
+ }
320
+ if (c === ":") {
321
+ const mm = /^:([A-Za-z0-9_]+):/.exec(text.slice(i));
322
+ if (mm && EMOJI_KEY_TO_NAME[mm[1]]) {
323
+ flush();
324
+ out.push({ tag: "emoji", property: { key: mm[1] } });
325
+ i += mm[0].length;
326
+ continue;
327
+ }
328
+ }
329
+ if (startsWith("**")) {
330
+ flush();
331
+ style.bold = !style.bold;
332
+ i += 2;
333
+ continue;
334
+ }
335
+ if (startsWith("~~")) {
336
+ flush();
337
+ style.strike = !style.strike;
338
+ i += 2;
339
+ continue;
340
+ }
341
+ if (c === "*" || c === "_") {
342
+ const prev = text[i - 1];
343
+ const next = text[i + 1];
344
+ const boundaryOk = c === "*" || (!isWord(prev) || !isWord(next));
345
+ if (boundaryOk) {
346
+ flush();
347
+ style.italic = !style.italic;
348
+ i += 1;
349
+ continue;
350
+ }
351
+ }
352
+ if (c === "`") {
353
+ const end = text.indexOf("`", i + 1);
354
+ if (end !== -1) {
355
+ flush();
356
+ out.push({ tag: "code_span", property: { content: text.slice(i + 1, end) } });
357
+ i = end + 1;
358
+ continue;
359
+ }
360
+ }
361
+ if (c === "[") {
362
+ const link = matchLink(text, i);
363
+ if (link) {
364
+ flush();
365
+ const property = { content: link.text, url: link.url };
366
+ const ts = styleAttrs(style);
367
+ if (ts) property.textStyle = ts;
368
+ out.push({ tag: "link", property });
369
+ i = link.end;
370
+ continue;
371
+ }
372
+ }
373
+ if (startsWith("<font")) {
374
+ const m = /^<font\s+color=['"]?([^'">]+)['"]?\s*>/.exec(text.slice(i));
375
+ if (m) {
376
+ flush();
377
+ style.color = m[1];
378
+ i += m[0].length;
379
+ continue;
380
+ }
381
+ }
382
+ if (startsWith("</font>")) {
383
+ flush();
384
+ style.color = void 0;
385
+ i += "</font>".length;
386
+ continue;
387
+ }
388
+ if (startsWith("<at")) {
389
+ const m = /^<at\s+id=['"]?([^'">\s]+)['"]?\s*>\s*<\/at>/.exec(text.slice(i));
390
+ if (m) {
391
+ flush();
392
+ if (m[1] === "all") out.push({ tag: "at_all", property: {} });
393
+ else out.push({ tag: "at", property: { userID: m[1] } });
394
+ i += m[0].length;
395
+ continue;
396
+ }
397
+ }
398
+ if (startsWith("<text_tag")) {
399
+ const m = /^<text_tag(?:\s+color=['"]?([^'">]+)['"]?)?\s*>([\s\S]*?)<\/text_tag>/.exec(text.slice(i));
400
+ if (m) {
401
+ flush();
402
+ const property = {
403
+ text: { tag: "plain_text", property: { content: m[2] } }
404
+ };
405
+ if (m[1]) property.color = m[1];
406
+ out.push({ tag: "text_tag", property });
407
+ i += m[0].length;
408
+ continue;
409
+ }
410
+ }
411
+ if (startsWith("<person")) {
412
+ const m = /^<person\b([^>]*?)(?:\/>|>\s*<\/person>)/.exec(text.slice(i));
413
+ if (m) {
414
+ flush();
415
+ const attrs = parseHtmlAttrs(m[1]);
416
+ const property = {
417
+ userID: attrs.id || attrs.user_id || "",
418
+ // 标记为 markdown 内联:渲染时跟正文字号,而非独立 person 的 medium 默认
419
+ avatarFollowFontSize: true
420
+ };
421
+ if (attrs.show_name != null) property.showName = attrs.show_name !== "false";
422
+ if (attrs.show_avatar != null) property.showAvatar = attrs.show_avatar !== "false";
423
+ if (attrs.size) property.size = attrs.size;
424
+ if (attrs.style === "capsule") property.pillShaped = true;
425
+ out.push({ tag: "person_v1", property });
426
+ i += m[0].length;
427
+ continue;
428
+ }
429
+ }
430
+ if (startsWith("<audio")) {
431
+ const m = /^<audio\b([^>]*?)(?:\/>|>\s*<\/audio>|>)/.exec(text.slice(i));
432
+ if (m) {
433
+ flush();
434
+ out.push({ tag: "markdown_audio", property: audioAttrsToProperty(parseHtmlAttrs(m[1])) });
435
+ i += m[0].length;
436
+ continue;
437
+ }
438
+ }
439
+ buf += c;
440
+ i += 1;
441
+ }
442
+ flush();
443
+ return out;
444
+ }
445
+ function parseHtmlAttrs(s) {
446
+ const out = {};
447
+ const re = /([A-Za-z_][\w-]*)\s*=\s*(?:'([^']*)'|"([^"]*)"|([^\s'">=]+))/g;
448
+ let m;
449
+ while (m = re.exec(s)) {
450
+ out[m[1]] = m[2] ?? m[3] ?? m[4] ?? "";
451
+ }
452
+ return out;
453
+ }
454
+ function attrBool(v) {
455
+ if (v == null) return void 0;
456
+ return v !== "false" && v !== "0";
457
+ }
458
+ function audioAttrsToProperty(attrs) {
459
+ const property = {};
460
+ const fileID = attrs.file_key || attrs.fileID || attrs.file_id;
461
+ if (fileID) property.fileID = fileID;
462
+ const audioID = attrs.audio_id || attrs.audioID;
463
+ if (audioID) property.audioID = audioID;
464
+ const showTime = attrBool(attrs.show_time ?? attrs.showTime);
465
+ if (showTime != null) property.showTime = showTime;
466
+ const showProgress = attrBool(attrs.show_progress_bar ?? attrs.showProgressBar);
467
+ if (showProgress != null) property.showProgressBar = showProgress;
468
+ const disabled = attrBool(attrs.disabled);
469
+ if (disabled != null) property.disabled = disabled;
470
+ const bg = attrs.background_color || attrs.backgroundColor;
471
+ if (bg) property.backgroundColor = bg;
472
+ const fill = attrs.fill_color || attrs.fillColor;
473
+ if (fill) property.fillColor = fill;
474
+ const fallback = attrs.fallback_url || attrs.fallbackUrl;
475
+ if (fallback) property.url = { url: fallback };
476
+ if (attrs.style === "speak" || attrs.style === "normal") {
477
+ property.audioStyle = attrs.style;
478
+ }
479
+ return property;
480
+ }
481
+ function isWord(ch) {
482
+ return !!ch && /[\w一-龥]/.test(ch);
483
+ }
484
+ function matchLink(text, start) {
485
+ let depth = 0;
486
+ let j = start;
487
+ for (; j < text.length; j++) {
488
+ if (text[j] === "[") depth++;
489
+ else if (text[j] === "]") {
490
+ depth--;
491
+ if (depth === 0) break;
492
+ }
493
+ }
494
+ if (j >= text.length || text[j + 1] !== "(") return null;
495
+ const label = text.slice(start + 1, j);
496
+ const close = text.indexOf(")", j + 2);
497
+ if (close === -1) return null;
498
+ const url = text.slice(j + 2, close);
499
+ return { text: label, url, end: close + 1 };
500
+ }
501
+ var RE_HEADING = /^(#{1,6})\s+(.*)$/;
502
+ var RE_HR = /^\s*([-*_])\1{2,}\s*$/;
503
+ var RE_LIST = /^(\s*)([-*+]|\d+\.)\s+(.*)$/;
504
+ var RE_FENCE = /^\s*```(.*)$/;
505
+ function parseMarkdown(content) {
506
+ if (!content) return [];
507
+ const lines = String(content).replace(/\r\n?/g, "\n").split("\n");
508
+ const out = [];
509
+ let para = [];
510
+ const flushPara = () => {
511
+ if (para.length === 0) return;
512
+ const seg = parseInline(para.join("\n"));
513
+ out.push(...seg);
514
+ para = [];
515
+ };
516
+ for (let i = 0; i < lines.length; i++) {
517
+ const line = lines[i];
518
+ if (line.trim() === "") {
519
+ if (para.length) {
520
+ flushPara();
521
+ out.push({ tag: "br", property: {} });
522
+ }
523
+ continue;
524
+ }
525
+ const fence = RE_FENCE.exec(line);
526
+ if (fence) {
527
+ flushPara();
528
+ const language = fence[1].trim();
529
+ const body = [];
530
+ i++;
531
+ for (; i < lines.length; i++) {
532
+ if (/^\s*```/.test(lines[i])) break;
533
+ body.push(lines[i]);
534
+ }
535
+ out.push({
536
+ tag: "code_block",
537
+ property: {
538
+ language,
539
+ contents: body.map((l) => ({ contents: [{ content: l }] }))
540
+ }
541
+ });
542
+ continue;
543
+ }
544
+ const heading = RE_HEADING.exec(line);
545
+ if (heading) {
546
+ flushPara();
547
+ out.push({
548
+ tag: "heading",
549
+ property: { level: heading[1].length, elements: parseInline(heading[2]) }
550
+ });
551
+ continue;
552
+ }
553
+ if (RE_HR.test(line)) {
554
+ flushPara();
555
+ out.push({ tag: "hr", property: {} });
556
+ continue;
557
+ }
558
+ if (/^\s*>\s?/.test(line)) {
559
+ flushPara();
560
+ const quote = [];
561
+ for (; i < lines.length; i++) {
562
+ if (!/^\s*>\s?/.test(lines[i])) {
563
+ i--;
564
+ break;
565
+ }
566
+ quote.push(lines[i].replace(/^\s*>\s?/, ""));
567
+ }
568
+ out.push({ tag: "blockquote", property: { elements: parseInline(quote.join("\n")) } });
569
+ continue;
570
+ }
571
+ if (RE_LIST.test(line)) {
572
+ flushPara();
573
+ const items = [];
574
+ let counter = 0;
575
+ let lastOrdered;
576
+ for (; i < lines.length; i++) {
577
+ const m = RE_LIST.exec(lines[i]);
578
+ if (!m) {
579
+ i--;
580
+ break;
581
+ }
582
+ const indent = m[1].replace(/\t/g, " ").length;
583
+ const level = Math.floor(indent / 2);
584
+ const ordered = /\d+\./.test(m[2]);
585
+ if (ordered !== lastOrdered) counter = 0;
586
+ lastOrdered = ordered;
587
+ const item = {
588
+ type: ordered ? "ol" : "ul",
589
+ level,
590
+ elements: parseInline(m[3])
591
+ };
592
+ if (ordered) {
593
+ counter += 1;
594
+ item.order = counter;
595
+ }
596
+ items.push(item);
597
+ }
598
+ out.push({ tag: "list", property: { items } });
599
+ continue;
600
+ }
601
+ para.push(line);
602
+ }
603
+ flushPara();
604
+ return out;
605
+ }
606
+
607
+ // src/dsl-to-raw.ts
608
+ var KEY_RENAME = {
609
+ text_align: "textAlign",
610
+ text_style: "textStyle",
611
+ background_style: "backgroundStyle",
612
+ background_color: "backgroundColor",
613
+ user_id: "userID",
614
+ show_avatar: "showAvatar",
615
+ show_name: "showName",
616
+ has_border: "hasBorder",
617
+ hover_tips: "hoverTips",
618
+ pill_shaped: "pillShaped",
619
+ tag_style: "tagStyle",
620
+ scale_type: "scaleType",
621
+ drop_invalid_user_id: "dropInvalidUserID",
622
+ // video / audio
623
+ file_key: "fileID",
624
+ audio_id: "audioID",
625
+ show_progress_bar: "showProgressBar",
626
+ show_time: "showTime",
627
+ enable_download: "enableDownload",
628
+ fill_color: "fillColor"
629
+ };
630
+ var TEXT_LEAF_KEYS = /* @__PURE__ */ new Set(["title", "subtitle", "alt", "hover_tips", "placeholder"]);
631
+ function dslToRaw(dsl) {
632
+ const root = isRecord(dsl) ? dsl : {};
633
+ const card = isRecord(root.card) ? root.card : isRecord(root.json_card) ? root.json_card : root;
634
+ const out = { schema: typeof card.schema === "string" ? card.schema : "2.0" };
635
+ if (isRecord(card.header)) out.header = convertHeader(card.header);
636
+ if (isRecord(card.body)) out.body = convertBody(card.body);
637
+ return out;
638
+ }
639
+ function convertHeader(header) {
640
+ const property = {};
641
+ for (const [k, v] of Object.entries(header)) {
642
+ if (k === "tag") continue;
643
+ if (TEXT_LEAF_KEYS.has(k)) {
644
+ property[k] = wrapTextLeaf(v);
645
+ continue;
646
+ }
647
+ if ((k === "icon" || k === "ud_icon") && isRecord(v)) {
648
+ property[k === "ud_icon" ? "udIcon" : "icon"] = convertEl(v);
649
+ continue;
650
+ }
651
+ if (k === "text_tag_list" && Array.isArray(v)) {
652
+ property.text_tag_list = v.map(convertNode);
653
+ continue;
654
+ }
655
+ property[renameKey(k)] = coerceValue(k, v);
656
+ }
657
+ return { tag: "card_header", property };
658
+ }
659
+ function convertBody(body) {
660
+ const property = buildProperty(body, "body");
661
+ const elements = Array.isArray(body.elements) ? body.elements.map(convertNode) : [];
662
+ property.elements = elements.filter(Boolean);
663
+ return { tag: "body", property };
664
+ }
665
+ function convertNode(node) {
666
+ if (isRecord(node) && typeof node.tag === "string") return convertEl(node);
667
+ return node;
668
+ }
669
+ function convertEl(el) {
670
+ const tag = String(el.tag);
671
+ if (tag === "person") {
672
+ const property = buildProperty(el, "person_v1");
673
+ if (property.style === "capsule") {
674
+ property.pillShaped = true;
675
+ delete property.style;
676
+ }
677
+ return { tag: "person_v1", property };
678
+ }
679
+ if (tag === "markdown" || tag === "lark_md") {
680
+ const property = buildProperty(el, "markdown");
681
+ if (typeof el.content === "string") property.elements = parseMarkdown(el.content);
682
+ delete property.content;
683
+ return { tag: "markdown", property };
684
+ }
685
+ if (tag === "table") {
686
+ return { tag, property: convertTableProperty(el) };
687
+ }
688
+ if (tag === "audio" || tag === "video") {
689
+ const property = buildProperty(el, tag);
690
+ if (tag === "audio" && el.width != null) {
691
+ delete property.width;
692
+ if (el.width === "fill" || el.width === "default") {
693
+ property.widthValue = { type: "builtin_width", value: el.width };
694
+ } else if (typeof el.width === "string") {
695
+ const px = pxValue(el.width);
696
+ if (px != null) property.widthValue = { type: "pixels", value: px };
697
+ }
698
+ }
699
+ if (tag === "video") {
700
+ const cover = el.image_key ?? el.cover_image_key ?? el.coverImageID;
701
+ if (typeof cover === "string" && cover) property.coverImageID = cover;
702
+ delete property.image_key;
703
+ delete property.cover_image_key;
704
+ }
705
+ return { tag, property };
706
+ }
707
+ return { tag, property: buildProperty(el, tag) };
708
+ }
709
+ function convertTableProperty(el) {
710
+ const property = buildProperty(el, "table");
711
+ const columns = Array.isArray(property.columns) ? property.columns : [];
712
+ const mdColNames = /* @__PURE__ */ new Set();
713
+ for (const col of columns) {
714
+ if (!isRecord(col)) continue;
715
+ const dt = col.data_type ?? col.dataType;
716
+ if (dt === "lark_md" || dt === "markdown") {
717
+ const name = typeof col.name === "string" ? col.name : "";
718
+ if (name) mdColNames.add(name);
719
+ }
720
+ }
721
+ if (!mdColNames.size || !Array.isArray(property.rows)) return property;
722
+ property.rows = property.rows.map((row) => {
723
+ if (!isRecord(row)) return row;
724
+ const out = { ...row };
725
+ for (const name of mdColNames) {
726
+ const cell = out[name];
727
+ if (typeof cell === "string") {
728
+ out[name] = {
729
+ data: {
730
+ tag: "markdown",
731
+ property: { elements: parseMarkdown(cell), originTag: "lark_md" }
732
+ }
733
+ };
734
+ }
735
+ }
736
+ return out;
737
+ });
738
+ return property;
739
+ }
740
+ function buildProperty(el, tag) {
741
+ const property = {};
742
+ for (const [k, v] of Object.entries(el)) {
743
+ if (k === "tag" || k === "id") continue;
744
+ if (tag === "column" && k === "width" && typeof v === "string") {
745
+ const px = pxValue(v);
746
+ if (px != null) {
747
+ property.widthValue = { type: "pixels", value: px };
748
+ continue;
749
+ }
750
+ }
751
+ if (TEXT_LEAF_KEYS.has(k)) {
752
+ property[renameKey(k)] = wrapTextLeaf(v);
753
+ continue;
754
+ }
755
+ if (k === "text") {
756
+ if (tag === "checker" && isRecord(v) && (v.tag === "markdown" || v.tag === "lark_md")) {
757
+ property.text = convertEl(v);
758
+ } else {
759
+ property.text = wrapTextLeaf(v);
760
+ }
761
+ continue;
762
+ }
763
+ if ((k === "icon" || k === "ud_icon") && isRecord(v)) {
764
+ property[k === "ud_icon" ? "udIcon" : "icon"] = convertEl(v);
765
+ continue;
766
+ }
767
+ if (k === "behaviors" && Array.isArray(v)) {
768
+ property.actions = behaviorsToActions(v);
769
+ continue;
770
+ }
771
+ if (k === "multi_url") {
772
+ property.url = normalizeUrl(v);
773
+ continue;
774
+ }
775
+ if (k === "button_area" && isRecord(v)) {
776
+ const area = { ...v };
777
+ if (Array.isArray(v.buttons)) area.buttons = v.buttons.map(convertNode);
778
+ property.button_area = area;
779
+ continue;
780
+ }
781
+ property[renameKey(k)] = coerceValue(k, v);
782
+ }
783
+ return property;
784
+ }
785
+ function coerceValue(key, value) {
786
+ if ((key === "padding" || key === "margin") && typeof value === "string") {
787
+ return parseBox(value);
788
+ }
789
+ if (Array.isArray(value)) return value.map(convertNode);
790
+ if (key === "url") return normalizeUrl(value);
791
+ return value;
792
+ }
793
+ function renameKey(k) {
794
+ return KEY_RENAME[k] || k;
795
+ }
796
+ function wrapTextLeaf(v) {
797
+ if (typeof v === "string") return { tag: "plain_text", property: { content: v } };
798
+ if (isRecord(v)) {
799
+ const property = { content: typeof v.content === "string" ? v.content : "" };
800
+ if (typeof v.text_align === "string") property.textAlign = v.text_align;
801
+ if (typeof v.text_size === "string") property.text_size = v.text_size;
802
+ if (typeof v.text_color === "string") property.text_color = v.text_color;
803
+ return { tag: typeof v.tag === "string" ? v.tag : "plain_text", property };
804
+ }
805
+ return { tag: "plain_text", property: { content: "" } };
806
+ }
807
+ function behaviorsToActions(behaviors) {
808
+ const out = [];
809
+ for (const b of behaviors) {
810
+ if (!isRecord(b)) continue;
811
+ const type = typeof b.type === "string" ? b.type : "open_url";
812
+ out.push({ type, action: { url: normalizeUrl(b.default_url) } });
813
+ }
814
+ return out;
815
+ }
816
+ function normalizeUrl(v) {
817
+ if (typeof v === "string") return v;
818
+ if (isRecord(v)) return v;
819
+ return v;
820
+ }
821
+ function parseBox(s) {
822
+ const nums = s.trim().split(/\s+/).map((t2) => pxValue(t2) ?? 0);
823
+ let t = 0, r = 0, b = 0, l = 0;
824
+ if (nums.length === 1) {
825
+ t = r = b = l = nums[0];
826
+ } else if (nums.length === 2) {
827
+ t = b = nums[0];
828
+ r = l = nums[1];
829
+ } else if (nums.length === 3) {
830
+ t = nums[0];
831
+ r = l = nums[1];
832
+ b = nums[2];
833
+ } else if (nums.length >= 4) {
834
+ [t, r, b, l] = nums;
835
+ }
836
+ const px = (value) => ({ type: "pixels", value });
837
+ return { top: px(t), right: px(r), bottom: px(b), left: px(l) };
838
+ }
839
+ function pxValue(s) {
840
+ const m = /^(-?\d+(?:\.\d+)?)px?$/.exec(s.trim());
841
+ if (m) return Number(m[1]);
842
+ const n = Number(s);
843
+ return Number.isFinite(n) ? n : null;
844
+ }
845
+ function isRecord(v) {
846
+ return typeof v === "object" && v !== null && !Array.isArray(v);
847
+ }
848
+
849
+ // src/raw-to-dsl.ts
850
+ function isRawCardEnvelope(value) {
851
+ if (typeof value !== "object" || value === null) return false;
852
+ const v = value;
853
+ return typeof v.json_card === "string" && typeof v.card_schema === "number";
854
+ }
855
+ function rawCardToDsl(envelope) {
856
+ if (!isRawCardEnvelope(envelope)) return envelope;
857
+ const env = envelope;
858
+ let inner;
859
+ try {
860
+ inner = JSON.parse(env.json_card ?? "");
861
+ } catch {
862
+ return envelope;
863
+ }
864
+ const ctx = {
865
+ images: env.json_attachment?.images ?? {},
866
+ atUsers: env.json_attachment?.at_users ?? {}
867
+ };
868
+ return convertRoot(inner, ctx);
869
+ }
870
+ var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
871
+ "new_body",
872
+ "source",
873
+ // Editor line-count mirror on markdown/plain_text. Schema 2.0 has no `lines`
874
+ // field; Feishu rejects it with code 200621 ("unknown property, property:
875
+ // lines"). Raw emits it on every text node (lines:0), so drop it everywhere.
876
+ "lines"
877
+ ]);
878
+ function isI18nMirror(key) {
879
+ return key.startsWith("i18n");
880
+ }
881
+ var IMG_RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
882
+ "compact_width",
883
+ "custom_width",
884
+ "mode"
885
+ ]);
886
+ var VIDEO_RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
887
+ // duration lives on im.file.create upload, not on the video component.
888
+ "duration",
889
+ "duration_ms",
890
+ // editor instance id — public audio has audio_id, video does NOT have video_id.
891
+ "video_id"
892
+ ]);
893
+ var VIDEO_PUBLIC_FIELDS = /* @__PURE__ */ new Set([
894
+ "element_id",
895
+ "margin",
896
+ "file_key",
897
+ "i18n_file_key",
898
+ "enable_download",
899
+ "show_time",
900
+ "cover",
901
+ "hover_tips",
902
+ "fallback"
903
+ ]);
904
+ var AUDIO_PUBLIC_FIELDS = /* @__PURE__ */ new Set([
905
+ "element_id",
906
+ "margin",
907
+ "file_key",
908
+ "i18n_file_key",
909
+ "audio_id",
910
+ "disabled",
911
+ "disabled_tips",
912
+ "show_progress_bar",
913
+ "show_time",
914
+ "time_display",
915
+ "time_position",
916
+ "border",
917
+ "style",
918
+ "padding",
919
+ "background_style",
920
+ "fill_color",
921
+ "width",
922
+ "fallback"
923
+ ]);
924
+ var TAG_DEFAULT_DROPS = {
925
+ column: {
926
+ disabled: false
927
+ },
928
+ column_set: {
929
+ disabled: false,
930
+ horizontal_spacing: "default"
931
+ },
932
+ button: {
933
+ disabled: false
934
+ },
935
+ interactive_container: {
936
+ disabled: false,
937
+ required: false,
938
+ has_border: false,
939
+ height: "auto"
940
+ }
941
+ };
942
+ var VALUE_ECHO_TARGETS = {
943
+ horizontal_spacing_value: "horizontal_spacing",
944
+ vertical_spacing_value: "vertical_spacing",
945
+ width_value: "width",
946
+ height_value: "height",
947
+ corner_radius_value: "corner_radius",
948
+ size_value: "size"
949
+ };
950
+ function convertRoot(root, ctx) {
951
+ if (!isRecord2(root)) return root;
952
+ const out = {};
953
+ out.schema = typeof root.schema === "string" ? root.schema : "2.0";
954
+ out.config = convertConfig(isRecord2(root.config) ? root.config : {});
955
+ if (isRecord2(root.body)) out.body = convertBody2(root.body, ctx);
956
+ if (isRecord2(root.header)) out.header = convertHeader2(root.header, ctx);
957
+ return out;
958
+ }
959
+ function convertConfig(_node) {
960
+ return { update_multi: true };
961
+ }
962
+ function convertHeader2(node, ctx) {
963
+ const property = isRecord2(node.property) ? node.property : {};
964
+ const out = convertProperty(property, "header", ctx);
965
+ return out;
966
+ }
967
+ function convertBody2(node, ctx) {
968
+ const property = isRecord2(node.property) ? node.property : node;
969
+ return convertProperty(property, "body", ctx);
970
+ }
971
+ function unwrap(node, ctx) {
972
+ const tag = typeof node.tag === "string" ? node.tag : void 0;
973
+ const property = isRecord2(node.property) ? node.property : {};
974
+ if (tag === "markdown") return rebuildMarkdown(property, ctx);
975
+ if (tag === "plain_text") {
976
+ const content = typeof property.content === "string" ? property.content : "";
977
+ return { tag: "plain_text", content };
978
+ }
979
+ if (tag === "standard_icon") {
980
+ const out = { tag: "standard_icon" };
981
+ if (typeof property.token === "string") out.token = property.token;
982
+ if (typeof property.color === "string") out.color = property.color;
983
+ const widthPx = isPixelScalar(property.widthValue) ? pixelOf(property.widthValue) : void 0;
984
+ const heightPx = isPixelScalar(property.heightValue) ? pixelOf(property.heightValue) : void 0;
985
+ if (widthPx !== void 0 && heightPx !== void 0) {
986
+ out.size = `${widthPx}px ${heightPx}px`;
987
+ } else if (isRecord2(property.size) && hasSide(property.size)) out.size = formatBox(property.size);
988
+ else if (isPixelScalar(property.size)) out.size = `${pixelOf(property.size)}px`;
989
+ else if (typeof property.size === "string") out.size = property.size;
990
+ return out;
991
+ }
992
+ if (tag === "ud_icon") {
993
+ const token = typeof property.token === "string" ? property.token : void 0;
994
+ const out = { tag: "standard_icon" };
995
+ if (token) out.token = token;
996
+ return out;
997
+ }
998
+ if (tag === "chart") {
999
+ const out = { tag: "chart" };
1000
+ if (typeof property.colorTheme === "string") out.color_theme = property.colorTheme;
1001
+ if (typeof property.preview === "boolean") out.preview = property.preview;
1002
+ if (property.chartSpec !== void 0) out.chart_spec = property.chartSpec;
1003
+ return out;
1004
+ }
1005
+ if (tag === "table") {
1006
+ const out = { tag: "table" };
1007
+ const rawColumns = Array.isArray(property.columns) ? property.columns : [];
1008
+ out.columns = rawColumns.map((col) => {
1009
+ if (!isRecord2(col)) return {};
1010
+ const c = {};
1011
+ if (typeof col.name === "string") c.name = col.name;
1012
+ const displayName = typeof col.displayName === "string" ? col.displayName : typeof col.display_name === "string" ? col.display_name : void 0;
1013
+ if (displayName !== void 0) c.display_name = displayName;
1014
+ const dataType = typeof col.dataType === "string" ? col.dataType : typeof col.data_type === "string" ? col.data_type : void 0;
1015
+ if (dataType !== void 0) c.data_type = dataType;
1016
+ const hAlign = typeof col.horizontalAlign === "string" ? col.horizontalAlign : typeof col.horizontal_align === "string" ? col.horizontal_align : void 0;
1017
+ if (hAlign !== void 0) c.horizontal_align = hAlign;
1018
+ const wv = col.widthValue;
1019
+ if (isRecord2(wv)) {
1020
+ if (wv.type === "pixels" && typeof wv.value === "number") c.width = `${wv.value}px`;
1021
+ else if (typeof wv.value === "string") c.width = wv.value;
1022
+ } else if (typeof col.width === "string" || typeof col.width === "number") {
1023
+ c.width = col.width;
1024
+ }
1025
+ if (isRecord2(col.format)) c.format = col.format;
1026
+ return c;
1027
+ });
1028
+ const rawRows = Array.isArray(property.rows) ? property.rows : [];
1029
+ out.rows = rawRows.map((row) => {
1030
+ if (!isRecord2(row)) return {};
1031
+ const r = {};
1032
+ for (const [k, cell] of Object.entries(row)) {
1033
+ let value = isRecord2(cell) && "data" in cell ? cell.data : cell;
1034
+ if (isRecord2(value) && value.tag === "markdown" && isRecord2(value.property)) {
1035
+ const elements = Array.isArray(value.property.elements) ? value.property.elements : [];
1036
+ value = renderChildren(elements, ctx);
1037
+ }
1038
+ r[k] = value;
1039
+ }
1040
+ return r;
1041
+ });
1042
+ if (isRecord2(property.headerStyle)) {
1043
+ const hs = {};
1044
+ for (const [k, v] of Object.entries(property.headerStyle)) {
1045
+ const sk = snake(k);
1046
+ if (sk === "platform_text_size") continue;
1047
+ if (sk === "lines_value") {
1048
+ if (isRecord2(v) && typeof v.value === "number") hs.lines = v.value;
1049
+ continue;
1050
+ }
1051
+ hs[sk] = v;
1052
+ }
1053
+ out.header_style = hs;
1054
+ }
1055
+ const rhv = property.rowHeightValue;
1056
+ if (isRecord2(rhv) && typeof rhv.value === "string") out.row_height = rhv.value;
1057
+ else if (typeof property.rowHeight === "string") out.row_height = property.rowHeight;
1058
+ if (typeof property.pageSize === "number") out.page_size = property.pageSize;
1059
+ if (typeof property.freezeFirstColumn === "boolean") out.freeze_first_column = property.freezeFirstColumn;
1060
+ return out;
1061
+ }
1062
+ if (tag === "input") {
1063
+ const allow = /* @__PURE__ */ new Set([
1064
+ "name",
1065
+ "placeholder",
1066
+ "default_value",
1067
+ "value",
1068
+ "label",
1069
+ "label_position",
1070
+ "margin",
1071
+ "max_length",
1072
+ "input_type",
1073
+ "width"
1074
+ ]);
1075
+ const out = { tag: "input" };
1076
+ const converted2 = convertProperty(property, "input", ctx);
1077
+ for (const [k, v] of Object.entries(converted2)) {
1078
+ if (allow.has(k)) out[k] = v;
1079
+ }
1080
+ if (converted2.required === true) out.required = true;
1081
+ return out;
1082
+ }
1083
+ if (tag === "person" || tag === "person_v1") {
1084
+ const out = { tag: "person" };
1085
+ if (typeof property.userID === "string") out.user_id = property.userID;
1086
+ else if (typeof property.user_id === "string") out.user_id = property.user_id;
1087
+ if (typeof property.size === "string") out.size = property.size;
1088
+ if (typeof property.showAvatar === "boolean") out.show_avatar = property.showAvatar;
1089
+ if (typeof property.showName === "boolean") out.show_name = property.showName;
1090
+ if (typeof property.style === "string") out.style = property.style;
1091
+ if (isRecord2(property.margin) && hasSide(property.margin)) out.margin = formatBox(property.margin);
1092
+ else if (typeof property.margin === "string") out.margin = property.margin;
1093
+ return out;
1094
+ }
1095
+ if (tag === "person_list") {
1096
+ const out = { tag: "person_list" };
1097
+ if (typeof property.size === "string") out.size = property.size;
1098
+ if (typeof property.showAvatar === "boolean") out.show_avatar = property.showAvatar;
1099
+ if (typeof property.showName === "boolean") out.show_name = property.showName;
1100
+ if (typeof property.lines === "number") out.lines = property.lines;
1101
+ if (typeof property.dropInvalidUserID === "boolean") out.drop_invalid_user_id = property.dropInvalidUserID;
1102
+ else if (typeof property.drop_invalid_user_id === "boolean") out.drop_invalid_user_id = property.drop_invalid_user_id;
1103
+ if (isRecord2(property.margin) && hasSide(property.margin)) out.margin = formatBox(property.margin);
1104
+ else if (typeof property.margin === "string") out.margin = property.margin;
1105
+ const rawPersons = Array.isArray(property.persons) ? property.persons : [];
1106
+ out.persons = rawPersons.map((p) => {
1107
+ if (!isRecord2(p)) return void 0;
1108
+ const id = typeof p.id === "string" ? p.id : typeof p.userID === "string" ? p.userID : void 0;
1109
+ return id ? { id } : void 0;
1110
+ }).filter((p) => p !== void 0);
1111
+ const iconSrc = isRecord2(property.icon) ? property.icon : isRecord2(property.udIcon) ? property.udIcon : void 0;
1112
+ if (iconSrc) {
1113
+ const ip = isRecord2(iconSrc.property) ? iconSrc.property : iconSrc;
1114
+ const icon = {};
1115
+ const iconTag = typeof iconSrc.tag === "string" ? iconSrc.tag : typeof ip.tag === "string" ? ip.tag : "standard_icon";
1116
+ icon.tag = iconTag === "ud_icon" ? "standard_icon" : iconTag;
1117
+ if (typeof ip.token === "string") icon.token = ip.token;
1118
+ if (typeof ip.color === "string") icon.color = ip.color;
1119
+ if (typeof ip.imageID === "string") icon.img_key = ip.imageID;
1120
+ else if (typeof ip.img_key === "string") icon.img_key = ip.img_key;
1121
+ if (icon.token !== void 0 || icon.img_key !== void 0) out.icon = icon;
1122
+ }
1123
+ return out;
1124
+ }
1125
+ if (tag === "div" && isRecord2(property.text) && property.fields === void 0) {
1126
+ const text = property.text;
1127
+ const textTag = typeof text.tag === "string" ? text.tag : "";
1128
+ if ((textTag === "markdown" || textTag === "lark_md") && isRecord2(text.property)) {
1129
+ return unwrap(text, ctx);
1130
+ }
1131
+ if (textTag === "plain_text") {
1132
+ const out = { tag: "div" };
1133
+ const widthVal = property.widthValue;
1134
+ if (isRecord2(widthVal) && typeof widthVal.value === "string" && typeof widthVal.type === "string" && widthVal.type.startsWith("builtin_")) {
1135
+ out.width = widthVal.value;
1136
+ } else if (typeof property.width === "string") {
1137
+ out.width = property.width;
1138
+ }
1139
+ const tp = isRecord2(text.property) ? text.property : {};
1140
+ const content = typeof tp.content === "string" ? tp.content : "";
1141
+ const textAlign = typeof tp.textAlign === "string" ? tp.textAlign : "left";
1142
+ out.text = { tag: "plain_text", content, text_align: textAlign };
1143
+ if (isRecord2(property.icon)) {
1144
+ out.icon = unwrap(property.icon, ctx);
1145
+ }
1146
+ return out;
1147
+ }
1148
+ }
1149
+ const converted = convertProperty(property, tag, ctx);
1150
+ return tag ? { tag, ...converted } : converted;
1151
+ }
1152
+ var DROP = /* @__PURE__ */ Symbol("drop");
1153
+ function convertProperty(property, ownerTag, ctx) {
1154
+ const out = {};
1155
+ const siblings = {
1156
+ buttonActionType: ownerTag === "button" && typeof property.actionType === "string" ? property.actionType : void 0
1157
+ };
1158
+ for (const [rawKey, rawValue] of Object.entries(property)) {
1159
+ const result = convertField(rawKey, rawValue, ownerTag, ctx, siblings);
1160
+ if (result === DROP) continue;
1161
+ const [outKey, outValue] = result;
1162
+ if (outValue === DROP) continue;
1163
+ out[outKey] = outValue;
1164
+ }
1165
+ for (const [echoKey, enumKey] of Object.entries(VALUE_ECHO_TARGETS)) {
1166
+ if (!(echoKey in out)) continue;
1167
+ const echoVal = out[echoKey];
1168
+ const enumVal = out[enumKey];
1169
+ delete out[echoKey];
1170
+ if (!isRecord2(echoVal)) continue;
1171
+ const echoType = typeof echoVal.type === "string" ? echoVal.type : "";
1172
+ const echoValue = echoVal.value;
1173
+ if (echoType.includes("pixels") && typeof echoValue === "number") {
1174
+ if (enumVal === void 0 || enumVal === "default" || enumVal === "auto" || enumVal === "weighted") {
1175
+ out[enumKey] = `${echoValue}px`;
1176
+ }
1177
+ continue;
1178
+ }
1179
+ if (echoType === "aspect_ratio" && isRecord2(echoValue)) {
1180
+ const w = echoValue.width;
1181
+ const h = echoValue.height;
1182
+ if (typeof w === "number" && typeof h === "number") {
1183
+ out["size"] = `${w}:${h}`;
1184
+ }
1185
+ continue;
1186
+ }
1187
+ if (echoType === "width_height_pixels" && isRecord2(echoValue)) {
1188
+ const w = echoValue.width;
1189
+ const h = echoValue.height;
1190
+ if (typeof w === "number" && typeof h === "number") {
1191
+ out["size"] = `${w}px ${h}px`;
1192
+ }
1193
+ continue;
1194
+ }
1195
+ if (echoType === "builtin_image_size") continue;
1196
+ if (echoType.startsWith("builtin_") && typeof echoValue === "string") {
1197
+ if (enumVal === void 0) out[enumKey] = echoValue;
1198
+ continue;
1199
+ }
1200
+ }
1201
+ if (ownerTag && TAG_DEFAULT_DROPS[ownerTag]) {
1202
+ const defaults = TAG_DEFAULT_DROPS[ownerTag];
1203
+ for (const [k, defVal] of Object.entries(defaults)) {
1204
+ if (out[k] === defVal) delete out[k];
1205
+ }
1206
+ }
1207
+ if (ownerTag === "video") {
1208
+ for (const k of Object.keys(out)) {
1209
+ if (!VIDEO_PUBLIC_FIELDS.has(k)) delete out[k];
1210
+ }
1211
+ }
1212
+ if (ownerTag === "audio") {
1213
+ for (const k of Object.keys(out)) {
1214
+ if (!AUDIO_PUBLIC_FIELDS.has(k)) delete out[k];
1215
+ }
1216
+ }
1217
+ return out;
1218
+ }
1219
+ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1220
+ if (rawKey === "originTag" || rawKey === "markdownElements") return DROP;
1221
+ const key = snake(rawKey);
1222
+ if (RAW_ONLY_FIELDS.has(key)) return DROP;
1223
+ if (isI18nMirror(key)) return DROP;
1224
+ if (key === "actions" && Array.isArray(rawValue) && rawValue.length === 0) {
1225
+ return DROP;
1226
+ }
1227
+ if ((key === "padding" || key === "margin") && isRecord2(rawValue) && hasSide(rawValue)) {
1228
+ return [key, formatBox(rawValue)];
1229
+ }
1230
+ if (key in VALUE_ECHO_TARGETS) {
1231
+ return [key, rawValue];
1232
+ }
1233
+ if (isPixelScalar(rawValue)) {
1234
+ return [key, `${pixelOf(rawValue)}px`];
1235
+ }
1236
+ if (ownerTag === "img" && key === "image_id") {
1237
+ const id = typeof rawValue === "string" || typeof rawValue === "number" ? String(rawValue) : void 0;
1238
+ const origin = id !== void 0 ? ctx.images[id]?.origin_key : void 0;
1239
+ if (origin) return ["img_key", origin];
1240
+ return [key, rawValue];
1241
+ }
1242
+ if (ownerTag === "img" && IMG_RAW_ONLY_FIELDS.has(key)) return DROP;
1243
+ if (ownerTag === "video" && key === "cover_image_id") {
1244
+ const imgKey = resolveAttachmentImgKey(rawValue, ctx);
1245
+ if (!imgKey) return DROP;
1246
+ return ["cover", { img_key: imgKey }];
1247
+ }
1248
+ if (ownerTag === "video" && key === "cover") {
1249
+ const cover = normalizeVideoCover(rawValue, ctx);
1250
+ return cover ? ["cover", cover] : DROP;
1251
+ }
1252
+ if ((ownerTag === "video" || ownerTag === "audio") && key === "file_id") {
1253
+ if (typeof rawValue === "string" && rawValue) return ["file_key", rawValue];
1254
+ return DROP;
1255
+ }
1256
+ if (ownerTag === "video" && VIDEO_RAW_ONLY_FIELDS.has(key)) return DROP;
1257
+ if ((key === "alt" || key === "hover_tips") && isRecord2(rawValue) && rawValue.tag === "plain_text") {
1258
+ const p = isRecord2(rawValue.property) ? rawValue.property : {};
1259
+ return [key, { tag: "plain_text", content: typeof p.content === "string" ? p.content : "" }];
1260
+ }
1261
+ if (key === "text" && isRecord2(rawValue) && rawValue.tag === "plain_text") {
1262
+ const p = isRecord2(rawValue.property) ? rawValue.property : {};
1263
+ return [key, { tag: "plain_text", content: typeof p.content === "string" ? p.content : "" }];
1264
+ }
1265
+ if (ownerTag === "header" && key === "ud_icon" && isRecord2(rawValue) && rawValue.tag === "ud_icon") {
1266
+ const p = isRecord2(rawValue.property) ? rawValue.property : {};
1267
+ const token = typeof p.token === "string" ? p.token : void 0;
1268
+ if (!token) return DROP;
1269
+ return ["icon", { tag: "standard_icon", token }];
1270
+ }
1271
+ if (ownerTag === "collapsible_panel" && key === "header" && isRecord2(rawValue)) {
1272
+ const cleaned = {};
1273
+ for (const [k, v] of Object.entries(rawValue)) {
1274
+ const sk = snake(k);
1275
+ if (sk === "position" || sk === "width" || sk === "vertical_align") continue;
1276
+ const sub = convertField(k, v, "collapsible_panel_header", ctx, siblings);
1277
+ if (sub === DROP) continue;
1278
+ const [subKey, subVal] = sub;
1279
+ if (subVal === DROP) continue;
1280
+ cleaned[subKey] = subVal;
1281
+ }
1282
+ return [key, cleaned];
1283
+ }
1284
+ if (ownerTag === "button" && key === "action_type") return DROP;
1285
+ if (ownerTag === "button" && key === "actions" && Array.isArray(rawValue)) {
1286
+ const openUrls = [];
1287
+ for (const a of rawValue) {
1288
+ if (!isRecord2(a)) continue;
1289
+ const inner = isRecord2(a.action) ? a.action : {};
1290
+ const t = typeof a.type === "string" ? a.type : void 0;
1291
+ if (t === "open_url" && typeof inner.url === "string") openUrls.push(inner.url);
1292
+ }
1293
+ if (openUrls.length === 0) return DROP;
1294
+ if (siblings.buttonActionType === "link") {
1295
+ return ["multi_url", { url: openUrls[0] }];
1296
+ }
1297
+ return [
1298
+ "behaviors",
1299
+ openUrls.map((url) => ({ type: "open_url", default_url: url }))
1300
+ ];
1301
+ }
1302
+ if (ownerTag === "interactive_container" && key === "actions" && Array.isArray(rawValue)) {
1303
+ const behaviors = rawValue.map((a) => {
1304
+ if (!isRecord2(a)) return void 0;
1305
+ const inner = isRecord2(a.action) ? a.action : {};
1306
+ const t = typeof a.type === "string" ? a.type : void 0;
1307
+ if (t === "open_url" && typeof inner.url === "string") {
1308
+ return { type: "open_url", default_url: inner.url };
1309
+ }
1310
+ if (t === "callback") {
1311
+ return { type: "callback", value: inner.value ?? {} };
1312
+ }
1313
+ return void 0;
1314
+ }).filter((b) => b !== void 0);
1315
+ if (behaviors.length === 0) return DROP;
1316
+ return ["behaviors", behaviors];
1317
+ }
1318
+ if (key === "border" && isRecord2(rawValue)) {
1319
+ const b = rawValue;
1320
+ const out = {};
1321
+ if (typeof b.borderColor === "string") out.color = b.borderColor;
1322
+ else if (typeof b.color === "string") out.color = b.color;
1323
+ if (isPixelScalar(b.cornerRadius)) out.corner_radius = `${pixelOf(b.cornerRadius)}px`;
1324
+ else if (typeof b.cornerRadius === "string") out.corner_radius = b.cornerRadius;
1325
+ else if (b.cornerRadius !== void 0) out.corner_radius = b.cornerRadius;
1326
+ for (const [bk, bv] of Object.entries(b)) {
1327
+ if (bk === "borderColor" || bk === "cornerRadius" || bk === "color") continue;
1328
+ out[snake(bk)] = convertGeneric(bv, ctx);
1329
+ }
1330
+ return [key, out];
1331
+ }
1332
+ if (key === "url" && isRecord2(rawValue)) {
1333
+ const v = rawValue;
1334
+ const main = typeof v.url === "string" ? v.url : "";
1335
+ const others = ["androidURL", "iosURL", "pcURL", "harmonyURL"].map((k) => v[k]).filter((x) => typeof x === "string" && x.length > 0);
1336
+ if (others.length === 0) return [key, main];
1337
+ return [
1338
+ key,
1339
+ {
1340
+ url: main,
1341
+ android_url: typeof v.androidURL === "string" ? v.androidURL : "",
1342
+ ios_url: typeof v.iosURL === "string" ? v.iosURL : "",
1343
+ pc_url: typeof v.pcURL === "string" ? v.pcURL : ""
1344
+ }
1345
+ ];
1346
+ }
1347
+ return [key, convertGeneric(rawValue, ctx)];
1348
+ }
1349
+ function renderInlinePerson(prop) {
1350
+ const id = typeof prop.userID === "string" ? prop.userID : typeof prop.user_id === "string" ? prop.user_id : typeof prop.id === "string" ? prop.id : "";
1351
+ if (!id) return "";
1352
+ const parts = [`id='${id}'`];
1353
+ if (typeof prop.showName === "boolean") parts.push(`show_name=${prop.showName}`);
1354
+ else if (typeof prop.show_name === "boolean") parts.push(`show_name=${prop.show_name}`);
1355
+ if (typeof prop.showAvatar === "boolean") parts.push(`show_avatar=${prop.showAvatar}`);
1356
+ else if (typeof prop.show_avatar === "boolean") parts.push(`show_avatar=${prop.show_avatar}`);
1357
+ if (typeof prop.style === "string") parts.push(`style='${prop.style}'`);
1358
+ return `<person ${parts.join(" ")}></person>`;
1359
+ }
1360
+ function renderInlineAudio(prop) {
1361
+ const fileKey = typeof prop.fileID === "string" ? prop.fileID : typeof prop.file_key === "string" ? prop.file_key : typeof prop.file_id === "string" ? prop.file_id : "";
1362
+ if (!fileKey) return "";
1363
+ const attrs = [`file_key='${fileKey}'`];
1364
+ const audioId = typeof prop.audioID === "string" ? prop.audioID : typeof prop.audio_id === "string" ? prop.audio_id : void 0;
1365
+ if (audioId) attrs.push(`audio_id='${audioId}'`);
1366
+ if (prop.showTime === true || prop.show_time === true) attrs.push("show_time=true");
1367
+ const style = typeof prop.style === "string" ? prop.style : void 0;
1368
+ if (style) attrs.push(`style='${style}'`);
1369
+ const bg = typeof prop.backgroundColor === "string" ? prop.backgroundColor : typeof prop.background_color === "string" ? prop.background_color : void 0;
1370
+ if (bg) attrs.push(`background_color='${bg}'`);
1371
+ const fill = typeof prop.fillColor === "string" ? prop.fillColor : typeof prop.fill_color === "string" ? prop.fill_color : void 0;
1372
+ if (fill) attrs.push(`fill_color='${fill}'`);
1373
+ const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1374
+ if (urlObj) {
1375
+ if (typeof urlObj.url === "string" && urlObj.url) attrs.push(`fallback_url='${urlObj.url}'`);
1376
+ if (typeof urlObj.pcURL === "string" && urlObj.pcURL) attrs.push(`fallback_pc_url='${urlObj.pcURL}'`);
1377
+ else if (typeof urlObj.pc_url === "string" && urlObj.pc_url) attrs.push(`fallback_pc_url='${urlObj.pc_url}'`);
1378
+ if (typeof urlObj.iosURL === "string" && urlObj.iosURL) attrs.push(`fallback_ios_url='${urlObj.iosURL}'`);
1379
+ else if (typeof urlObj.ios_url === "string" && urlObj.ios_url) attrs.push(`fallback_ios_url='${urlObj.ios_url}'`);
1380
+ if (typeof urlObj.androidURL === "string" && urlObj.androidURL) attrs.push(`fallback_android_url='${urlObj.androidURL}'`);
1381
+ else if (typeof urlObj.android_url === "string" && urlObj.android_url) attrs.push(`fallback_android_url='${urlObj.android_url}'`);
1382
+ if (typeof urlObj.harmonyURL === "string" && urlObj.harmonyURL) attrs.push(`fallback_harmony_url='${urlObj.harmonyURL}'`);
1383
+ else if (typeof urlObj.harmony_url === "string" && urlObj.harmony_url) attrs.push(`fallback_harmony_url='${urlObj.harmony_url}'`);
1384
+ } else if (typeof prop.fallback_url === "string" && prop.fallback_url) {
1385
+ attrs.push(`fallback_url='${prop.fallback_url}'`);
1386
+ }
1387
+ const fb = isRecord2(prop.fallbackText) ? prop.fallbackText : isRecord2(prop.fallback_text) ? prop.fallback_text : void 0;
1388
+ if (fb) {
1389
+ const fbProp = isRecord2(fb.property) ? fb.property : fb;
1390
+ const fbContent = typeof fbProp.content === "string" ? fbProp.content : typeof fb.content === "string" ? fb.content : "";
1391
+ if (fbContent) attrs.push(`fallback_text='${fbContent}'`);
1392
+ }
1393
+ return `<audio ${attrs.join(" ")}></audio>`;
1394
+ }
1395
+ function resolveAtUserId(prop, ctx) {
1396
+ if (typeof prop.userID === "string" && prop.userID) return prop.userID;
1397
+ if (typeof prop.userId === "string" && prop.userId) return prop.userId;
1398
+ if (typeof prop.user_id === "string" && prop.user_id) return prop.user_id;
1399
+ const key = typeof prop.atUserKey === "string" ? prop.atUserKey : typeof prop.at_user_key === "string" ? prop.at_user_key : void 0;
1400
+ if (!key || !ctx.atUsers[key]) return void 0;
1401
+ if (/^(ou_|on_)/.test(key)) return key;
1402
+ return ctx.atUsers[key].user_id;
1403
+ }
1404
+ function rebuildMarkdown(property, ctx) {
1405
+ const out = { tag: "markdown" };
1406
+ const elements = Array.isArray(property.elements) ? property.elements : [];
1407
+ const content = renderChildren(elements, ctx);
1408
+ if (content) out.content = content;
1409
+ for (const [rawKey, rawValue] of Object.entries(property)) {
1410
+ if (rawKey === "elements" || rawKey === "markdownElements" || rawKey === "originTag") continue;
1411
+ if (rawKey === "udIcon") continue;
1412
+ if (rawKey === "textStyle") {
1413
+ const ts = isRecord2(rawValue) ? rawValue : {};
1414
+ const size = typeof ts.size === "string" ? ts.size : void 0;
1415
+ if (size) out.text_size = size;
1416
+ continue;
1417
+ }
1418
+ const result = convertField(rawKey, rawValue, "markdown", ctx);
1419
+ if (result === DROP) continue;
1420
+ const [outKey, outValue] = result;
1421
+ if (outValue === DROP) continue;
1422
+ out[outKey] = outValue;
1423
+ }
1424
+ return out;
1425
+ }
1426
+ function wrapEmphasis(text, bold, color) {
1427
+ if (!bold && !color) return text;
1428
+ const lead = text.match(/^\s*/)[0];
1429
+ const trail = text.match(/\s*$/)[0];
1430
+ const core = text.slice(lead.length, text.length - trail.length);
1431
+ if (core.length === 0) return text;
1432
+ let wrapped = core;
1433
+ if (bold) wrapped = `**${wrapped}**`;
1434
+ if (color) wrapped = `<font color='${color}'>${wrapped}</font>`;
1435
+ return lead + wrapped + trail;
1436
+ }
1437
+ function renderChildren(children, ctx) {
1438
+ let out = "";
1439
+ let buffer = [];
1440
+ function flushBuffer() {
1441
+ if (buffer.length === 0) return;
1442
+ let group = [];
1443
+ function emitGroup() {
1444
+ if (group.length === 0) return;
1445
+ const inner = group.map((s) => s.content).join("");
1446
+ const { bold, color } = group[0];
1447
+ out += wrapEmphasis(inner, bold, color);
1448
+ group = [];
1449
+ }
1450
+ for (const seg of buffer) {
1451
+ if (group.length === 0) {
1452
+ group.push(seg);
1453
+ continue;
1454
+ }
1455
+ const head = group[0];
1456
+ if (head.bold === seg.bold && head.color === seg.color) group.push(seg);
1457
+ else {
1458
+ emitGroup();
1459
+ group.push(seg);
1460
+ }
1461
+ }
1462
+ emitGroup();
1463
+ buffer = [];
1464
+ }
1465
+ for (const child of children) {
1466
+ if (!isRecord2(child)) continue;
1467
+ const inline = childToInlineSegment(child, ctx);
1468
+ if (inline) {
1469
+ buffer.push(inline);
1470
+ continue;
1471
+ }
1472
+ flushBuffer();
1473
+ out += childToMarkdown(child, ctx);
1474
+ }
1475
+ flushBuffer();
1476
+ return out;
1477
+ }
1478
+ function childToInlineSegment(child, ctx) {
1479
+ const tag = child.tag;
1480
+ const prop = isRecord2(child.property) ? child.property : {};
1481
+ const ts = isRecord2(prop.textStyle) ? prop.textStyle : void 0;
1482
+ const bold = !!(ts && Array.isArray(ts.attributes) && ts.attributes.includes("bold"));
1483
+ const rawColor = ts && typeof ts.color === "string" ? ts.color : void 0;
1484
+ const color = rawColor === "default" ? void 0 : rawColor;
1485
+ switch (tag) {
1486
+ case "plain_text":
1487
+ return {
1488
+ content: typeof prop.content === "string" ? prop.content : "",
1489
+ bold,
1490
+ color
1491
+ };
1492
+ case "code_span":
1493
+ return {
1494
+ content: `\`${typeof prop.content === "string" ? prop.content : ""}\``,
1495
+ bold,
1496
+ color
1497
+ };
1498
+ case "link": {
1499
+ const text = typeof prop.content === "string" ? prop.content : "";
1500
+ const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1501
+ const url = urlObj && typeof urlObj.url === "string" ? urlObj.url : "";
1502
+ return { content: `[${text}](${url})`, bold, color };
1503
+ }
1504
+ case "at_all":
1505
+ return { content: "<at id=all></at>", bold, color };
1506
+ case "at": {
1507
+ const userId = resolveAtUserId(prop, ctx);
1508
+ return { content: userId ? `<at id=${userId}></at>` : "<at></at>", bold, color };
1509
+ }
1510
+ case "text_tag": {
1511
+ const tagColor = typeof prop.color === "string" ? prop.color : void 0;
1512
+ const inner = isRecord2(prop.text) ? prop.text : void 0;
1513
+ const innerProp = inner && isRecord2(inner.property) ? inner.property : void 0;
1514
+ const innerContent = innerProp && typeof innerProp.content === "string" ? innerProp.content : "";
1515
+ const rendered = tagColor ? `<text_tag color='${tagColor}'>${innerContent}</text_tag>` : `<text_tag>${innerContent}</text_tag>`;
1516
+ return { content: rendered, bold: false };
1517
+ }
1518
+ case "emoji": {
1519
+ const key = typeof prop.key === "string" ? prop.key : typeof prop.emojiKey === "string" ? prop.emojiKey : "";
1520
+ return key ? { content: `:${key}:`, bold: false } : void 0;
1521
+ }
1522
+ case "person":
1523
+ case "person_v1": {
1524
+ const rendered = renderInlinePerson(prop);
1525
+ return rendered ? { content: rendered, bold: false } : void 0;
1526
+ }
1527
+ case "markdown_audio": {
1528
+ const rendered = renderInlineAudio(prop);
1529
+ return rendered ? { content: rendered, bold: false } : void 0;
1530
+ }
1531
+ default:
1532
+ return void 0;
1533
+ }
1534
+ }
1535
+ function childToMarkdown(child, ctx) {
1536
+ const tag = child.tag;
1537
+ const prop = isRecord2(child.property) ? child.property : {};
1538
+ switch (tag) {
1539
+ case "plain_text": {
1540
+ const content = typeof prop.content === "string" ? prop.content : "";
1541
+ const ts = isRecord2(prop.textStyle) ? prop.textStyle : void 0;
1542
+ const color = ts && typeof ts.color === "string" ? ts.color : void 0;
1543
+ const attrs = ts && Array.isArray(ts.attributes) ? ts.attributes : [];
1544
+ return wrapEmphasis(content, attrs.includes("bold"), color);
1545
+ }
1546
+ case "br":
1547
+ return "\n";
1548
+ case "emoji": {
1549
+ const key = typeof prop.key === "string" ? prop.key : typeof prop.emojiKey === "string" ? prop.emojiKey : "";
1550
+ return key ? `:${key}:` : "";
1551
+ }
1552
+ case "at_all":
1553
+ return "<at id=all></at>";
1554
+ case "at": {
1555
+ const userId = resolveAtUserId(prop, ctx);
1556
+ return userId ? `<at id=${userId}></at>` : "<at></at>";
1557
+ }
1558
+ case "person":
1559
+ case "person_v1":
1560
+ return renderInlinePerson(prop);
1561
+ case "markdown_audio":
1562
+ return renderInlineAudio(prop);
1563
+ case "link": {
1564
+ const text = typeof prop.content === "string" ? prop.content : "";
1565
+ const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1566
+ const url = urlObj && typeof urlObj.url === "string" ? urlObj.url : "";
1567
+ return `[${text}](${url})`;
1568
+ }
1569
+ case "code_span": {
1570
+ const text = typeof prop.content === "string" ? prop.content : "";
1571
+ return `\`${text}\``;
1572
+ }
1573
+ case "text_tag": {
1574
+ const color = typeof prop.color === "string" ? prop.color : void 0;
1575
+ const inner = isRecord2(prop.text) ? prop.text : void 0;
1576
+ const innerProp = inner && isRecord2(inner.property) ? inner.property : void 0;
1577
+ const innerContent = innerProp && typeof innerProp.content === "string" ? innerProp.content : "";
1578
+ if (color) return `<text_tag color='${color}'>${innerContent}</text_tag>`;
1579
+ return `<text_tag>${innerContent}</text_tag>`;
1580
+ }
1581
+ case "code_block": {
1582
+ const lang = typeof prop.language === "string" ? prop.language : "";
1583
+ const blocks = Array.isArray(prop.contents) ? prop.contents : [];
1584
+ const lines = blocks.map((blk) => {
1585
+ if (!isRecord2(blk)) return "";
1586
+ const inner = Array.isArray(blk.contents) ? blk.contents : [];
1587
+ let line = "";
1588
+ for (const seg of inner) {
1589
+ if (isRecord2(seg) && typeof seg.content === "string") line += seg.content;
1590
+ }
1591
+ return line;
1592
+ });
1593
+ const body = lines.join("\n").replace(/\n+$/, "");
1594
+ return `
1595
+ \`\`\`${lang}
1596
+ ${body}
1597
+ \`\`\`
1598
+ `;
1599
+ }
1600
+ case "list": {
1601
+ const items = Array.isArray(prop.items) ? prop.items : [];
1602
+ const lines = [];
1603
+ const levelCounters = [];
1604
+ for (const item of items) {
1605
+ if (!isRecord2(item)) continue;
1606
+ const level = typeof item.level === "number" ? item.level : 0;
1607
+ const type = typeof item.type === "string" ? item.type : "ul";
1608
+ const indent = " ".repeat(level);
1609
+ const inner = Array.isArray(item.elements) ? item.elements : [];
1610
+ let text = "";
1611
+ for (const seg of inner) {
1612
+ if (!isRecord2(seg)) continue;
1613
+ text += childToMarkdown(seg, ctx);
1614
+ }
1615
+ if (type === "ol") {
1616
+ let n;
1617
+ if (typeof item.order === "number" && Number.isFinite(item.order) && item.order > 0) {
1618
+ n = Math.floor(item.order);
1619
+ levelCounters.length = level + 1;
1620
+ levelCounters[level] = n;
1621
+ } else {
1622
+ levelCounters.length = level + 1;
1623
+ levelCounters[level] = (levelCounters[level] ?? 0) + 1;
1624
+ n = levelCounters[level];
1625
+ }
1626
+ lines.push(`${indent}${n}. ${text}`);
1627
+ } else {
1628
+ lines.push(`${indent}- ${text}`);
1629
+ }
1630
+ }
1631
+ return `
1632
+ ${lines.join("\n")}
1633
+
1634
+ `;
1635
+ }
1636
+ case "heading": {
1637
+ const level = typeof prop.level === "number" ? prop.level : 1;
1638
+ const inner = Array.isArray(prop.elements) ? prop.elements : [];
1639
+ let body = "";
1640
+ for (const seg of inner) {
1641
+ if (isRecord2(seg)) body += childToMarkdown(seg, ctx);
1642
+ }
1643
+ return `
1644
+
1645
+ ${"#".repeat(level)} ${body}
1646
+
1647
+ `;
1648
+ }
1649
+ case "hr": {
1650
+ return "\n\n---\n\n";
1651
+ }
1652
+ case "table": {
1653
+ const columns = Array.isArray(prop.columns) ? prop.columns : [];
1654
+ const rows = Array.isArray(prop.rows) ? prop.rows : [];
1655
+ if (columns.length === 0) return "";
1656
+ const headers = columns.map(
1657
+ (col) => isRecord2(col) && typeof col.displayName === "string" ? col.displayName : ""
1658
+ );
1659
+ const headerLine = `| ${headers.join(" | ")} |`;
1660
+ const separator = `| ${columns.map(() => "--------").join(" | ")} |`;
1661
+ const bodyLines = rows.map((row) => {
1662
+ if (!isRecord2(row)) return `| ${columns.map(() => "").join(" | ")} |`;
1663
+ const cells = columns.map((col) => {
1664
+ const name = isRecord2(col) && typeof col.name === "string" ? col.name : "";
1665
+ const cell = isRecord2(row[name]) ? row[name] : void 0;
1666
+ const data = cell && isRecord2(cell.data) ? cell.data : void 0;
1667
+ if (!data || data.tag !== "markdown") return "";
1668
+ const cellProp = isRecord2(data.property) ? data.property : {};
1669
+ const inner = Array.isArray(cellProp.elements) ? cellProp.elements : [];
1670
+ const text = renderChildren(inner, ctx);
1671
+ return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
1672
+ });
1673
+ return `| ${cells.join(" | ")} |`;
1674
+ });
1675
+ return `
1676
+
1677
+ ${headerLine}
1678
+ ${separator}
1679
+ ${bodyLines.join("\n")}
1680
+
1681
+ `;
1682
+ }
1683
+ case "blockquote": {
1684
+ const inner = Array.isArray(prop.elements) ? prop.elements : [];
1685
+ let body = "";
1686
+ for (const seg of inner) {
1687
+ if (isRecord2(seg)) body += childToMarkdown(seg, ctx);
1688
+ }
1689
+ return `
1690
+
1691
+ > ${body.replace(/\n/g, "\n> ")}
1692
+
1693
+ `;
1694
+ }
1695
+ default:
1696
+ return typeof prop.content === "string" ? prop.content : "";
1697
+ }
1698
+ }
1699
+ function convertGeneric(value, ctx) {
1700
+ if (Array.isArray(value)) {
1701
+ return value.map((item) => {
1702
+ if (isRecord2(item) && typeof item.tag === "string" && isRecord2(item.property)) {
1703
+ return unwrap(item, ctx);
1704
+ }
1705
+ return convertGeneric(item, ctx);
1706
+ });
1707
+ }
1708
+ if (isRecord2(value)) {
1709
+ if (typeof value.tag === "string" && isRecord2(value.property)) {
1710
+ return unwrap(value, ctx);
1711
+ }
1712
+ if (hasSide(value)) {
1713
+ return formatBox(value);
1714
+ }
1715
+ if (isPixelScalar(value)) {
1716
+ return `${pixelOf(value)}px`;
1717
+ }
1718
+ const out = {};
1719
+ for (const [k, v] of Object.entries(value)) {
1720
+ out[snake(k)] = convertGeneric(v, ctx);
1721
+ }
1722
+ return out;
1723
+ }
1724
+ return value;
1725
+ }
1726
+ function isRecord2(v) {
1727
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1728
+ }
1729
+ function resolveAttachmentImgKey(rawValue, ctx) {
1730
+ if (typeof rawValue !== "string" && typeof rawValue !== "number") return void 0;
1731
+ const id = String(rawValue);
1732
+ const origin = ctx.images[id]?.origin_key;
1733
+ if (typeof origin === "string" && origin) return origin;
1734
+ if (id.startsWith("img_")) return id;
1735
+ return void 0;
1736
+ }
1737
+ function normalizeVideoCover(rawValue, ctx) {
1738
+ if (typeof rawValue === "string" || typeof rawValue === "number") {
1739
+ const imgKey2 = resolveAttachmentImgKey(rawValue, ctx);
1740
+ return imgKey2 ? { img_key: imgKey2 } : void 0;
1741
+ }
1742
+ if (!isRecord2(rawValue)) return void 0;
1743
+ if (typeof rawValue.img_key === "string" && rawValue.img_key) {
1744
+ return { img_key: rawValue.img_key };
1745
+ }
1746
+ const candidate = rawValue.imageID ?? rawValue.image_id ?? rawValue.cover_image_id ?? rawValue.coverImageId;
1747
+ const imgKey = resolveAttachmentImgKey(candidate, ctx);
1748
+ return imgKey ? { img_key: imgKey } : void 0;
1749
+ }
1750
+ function hasSide(v) {
1751
+ return ("top" in v || "bottom" in v || "left" in v || "right" in v) && Object.values(v).some((side) => isRecord2(side) && "type" in side && "value" in side);
1752
+ }
1753
+ function formatBox(v) {
1754
+ return `${pixelOf(v.top)}px ${pixelOf(v.right)}px ${pixelOf(v.bottom)}px ${pixelOf(v.left)}px`;
1755
+ }
1756
+ function isPixelScalar(v) {
1757
+ if (!isRecord2(v)) return false;
1758
+ return typeof v.type === "string" && v.type.includes("pixels") && typeof v.value === "number";
1759
+ }
1760
+ function pixelOf(side) {
1761
+ if (!isRecord2(side)) return 0;
1762
+ const v = side.value;
1763
+ if (typeof v === "number") return v;
1764
+ if (typeof v === "string") {
1765
+ const n = Number(v);
1766
+ return Number.isFinite(n) ? n : 0;
1767
+ }
1768
+ return 0;
1769
+ }
1770
+ function snake(key) {
1771
+ return key.replace(/ID$/g, "_id").replace(/URL$/g, "_url").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
1772
+ }
1773
+ function rawToDsl(raw) {
1774
+ if (isRawCardEnvelope(raw)) return rawCardToDsl(raw);
1775
+ if (typeof raw === "string") {
1776
+ try {
1777
+ return rawToDsl(JSON.parse(raw));
1778
+ } catch {
1779
+ return raw;
1780
+ }
1781
+ }
1782
+ if (!isRecord2(raw)) return raw;
1783
+ if (typeof raw.json_card === "string" || isRecord2(raw.json_card)) {
1784
+ let inner = raw.json_card;
1785
+ if (typeof inner === "string") {
1786
+ try {
1787
+ inner = JSON.parse(inner);
1788
+ } catch {
1789
+ return raw;
1790
+ }
1791
+ }
1792
+ const attachment = isRecord2(raw.json_attachment) ? raw.json_attachment : {};
1793
+ const ctx = {
1794
+ images: isRecord2(attachment.images) ? attachment.images : {},
1795
+ atUsers: isRecord2(attachment.at_users) ? attachment.at_users : {}
1796
+ };
1797
+ return convertRoot(inner, ctx);
1798
+ }
1799
+ if (isRecord2(raw.card)) {
1800
+ const card = raw.card;
1801
+ if (typeof card.json_card === "string" || isRecord2(card.json_card)) {
1802
+ return rawToDsl(card);
1803
+ }
1804
+ return convertRoot(card, { images: {}, atUsers: {} });
1805
+ }
1806
+ if ("body" in raw || "header" in raw || "schema" in raw) {
1807
+ return convertRoot(raw, { images: {}, atUsers: {} });
1808
+ }
1809
+ return raw;
1810
+ }
1811
+ function degradeUnknown(el) {
1812
+ const rawTag = typeof el.tag === "string" ? el.tag : "undefined";
1813
+ return { tag: "unknown", raw_tag: rawTag, fallback: el };
1814
+ }
1815
+
1816
+ export { degradeUnknown, dslToRaw, isRawCardEnvelope, rawCardToDsl, rawToDsl };