@open-feishu-card/adapter 0.2.1 → 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.cjs CHANGED
@@ -849,203 +849,974 @@ function isRecord(v) {
849
849
  }
850
850
 
851
851
  // src/raw-to-dsl.ts
852
- var CAMEL_RENAME = {
853
- textAlign: "text_align",
854
- textStyle: "text_style",
855
- backgroundStyle: "background_style",
856
- backgroundColor: "background_color",
857
- userID: "user_id",
858
- showAvatar: "show_avatar",
859
- showName: "show_name",
860
- hasBorder: "has_border",
861
- hoverTips: "hover_tips",
862
- pillShaped: "pill_shaped",
863
- tagStyle: "tag_style",
864
- scaleType: "scale_type",
865
- dropInvalidUserID: "drop_invalid_user_id",
866
- udIcon: "ud_icon",
867
- // video / audio
868
- fileID: "file_key",
869
- coverImageID: "image_key",
870
- showTime: "show_time",
871
- enableDownload: "enable_download",
872
- audioID: "audio_id",
873
- showProgressBar: "show_progress_bar",
874
- fillColor: "fill_color"
875
- };
876
- var KNOWN_TAGS = /* @__PURE__ */ new Set([
877
- "markdown",
878
- "lark_md",
879
- "div",
880
- "column_set",
881
- "column",
882
- "hr",
883
- "image",
884
- "img",
885
- "button",
886
- "overflow",
887
- "select_static",
888
- "select_multi",
889
- "input",
890
- "date_picker",
891
- "picker_time",
892
- "picker_date",
893
- "checker",
894
- "chart",
895
- "table",
896
- "iframe",
897
- "person",
898
- "person_list",
899
- "interactive_container",
900
- "tag",
901
- "text_tag",
902
- "multi_image",
903
- "plain_text",
904
- "lark_md_inline",
905
- "image_text",
906
- "carousel",
907
- "grid",
908
- "speaking",
909
- "vote",
910
- "lottery",
911
- "calender",
912
- "schedule",
913
- "share_chat",
914
- "share_user",
915
- "add_on",
916
- "whole_text",
917
- "cropper_image",
918
- "highlight",
919
- "local_file",
920
- "file_preview",
921
- "input_select",
922
- "picker_datetime",
923
- "picker_multi",
924
- "rule",
925
- "statistic",
926
- "task",
927
- "card_header",
928
- "body",
929
- "audio",
930
- "video",
931
- "markdown_audio",
932
- "unknown"
933
- ]);
934
- function isRecord2(v) {
935
- return typeof v === "object" && v !== null && !Array.isArray(v);
936
- }
937
- function degradeUnknown(el) {
938
- const rawTag = typeof el.tag === "string" ? el.tag : "undefined";
939
- return { tag: "unknown", raw_tag: rawTag, fallback: el };
852
+ function isRawCardEnvelope(value) {
853
+ if (typeof value !== "object" || value === null) return false;
854
+ const v = value;
855
+ return typeof v.json_card === "string" && typeof v.card_schema === "number";
940
856
  }
941
- function rawToDsl(raw) {
942
- let card = {};
943
- if (isRecord2(raw)) {
944
- const env = isRecord2(raw.json_card) ? raw.json_card : isRecord2(raw.card) ? raw.card : raw;
945
- if (isRecord2(env)) card = env;
857
+ function rawCardToDsl(envelope) {
858
+ if (!isRawCardEnvelope(envelope)) return envelope;
859
+ const env = envelope;
860
+ let inner;
861
+ try {
862
+ inner = JSON.parse(env.json_card ?? "");
863
+ } catch {
864
+ return envelope;
946
865
  }
947
- const out = {
948
- schema: typeof card.schema === "string" ? card.schema : "2.0"
866
+ const ctx = {
867
+ images: env.json_attachment?.images ?? {},
868
+ atUsers: env.json_attachment?.at_users ?? {}
949
869
  };
950
- if (isRecord2(card.config)) out.config = card.config;
951
- if (isRecord2(card.header)) out.header = convertHeaderToDsl(card.header);
952
- if (isRecord2(card.body)) out.body = convertBodyToDsl(card.body);
953
- else if (Array.isArray(card.elements)) {
954
- out.body = { elements: card.elements.map(convertElToDsl).filter(Boolean) };
870
+ return convertRoot(inner, ctx);
871
+ }
872
+ var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
873
+ "new_body",
874
+ "source",
875
+ // Editor line-count mirror on markdown/plain_text. Schema 2.0 has no `lines`
876
+ // field; Feishu rejects it with code 200621 ("unknown property, property:
877
+ // lines"). Raw emits it on every text node (lines:0), so drop it everywhere.
878
+ "lines"
879
+ ]);
880
+ function isI18nMirror(key) {
881
+ return key.startsWith("i18n");
882
+ }
883
+ var IMG_RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
884
+ "compact_width",
885
+ "custom_width",
886
+ "mode"
887
+ ]);
888
+ var VIDEO_RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
889
+ // duration lives on im.file.create upload, not on the video component.
890
+ "duration",
891
+ "duration_ms",
892
+ // editor instance id — public audio has audio_id, video does NOT have video_id.
893
+ "video_id"
894
+ ]);
895
+ var VIDEO_PUBLIC_FIELDS = /* @__PURE__ */ new Set([
896
+ "element_id",
897
+ "margin",
898
+ "file_key",
899
+ "i18n_file_key",
900
+ "enable_download",
901
+ "show_time",
902
+ "cover",
903
+ "hover_tips",
904
+ "fallback"
905
+ ]);
906
+ var AUDIO_PUBLIC_FIELDS = /* @__PURE__ */ new Set([
907
+ "element_id",
908
+ "margin",
909
+ "file_key",
910
+ "i18n_file_key",
911
+ "audio_id",
912
+ "disabled",
913
+ "disabled_tips",
914
+ "show_progress_bar",
915
+ "show_time",
916
+ "time_display",
917
+ "time_position",
918
+ "border",
919
+ "style",
920
+ "padding",
921
+ "background_style",
922
+ "fill_color",
923
+ "width",
924
+ "fallback"
925
+ ]);
926
+ var TAG_DEFAULT_DROPS = {
927
+ column: {
928
+ disabled: false
929
+ },
930
+ column_set: {
931
+ disabled: false,
932
+ horizontal_spacing: "default"
933
+ },
934
+ button: {
935
+ disabled: false
936
+ },
937
+ interactive_container: {
938
+ disabled: false,
939
+ required: false,
940
+ has_border: false,
941
+ height: "auto"
955
942
  }
943
+ };
944
+ var VALUE_ECHO_TARGETS = {
945
+ horizontal_spacing_value: "horizontal_spacing",
946
+ vertical_spacing_value: "vertical_spacing",
947
+ width_value: "width",
948
+ height_value: "height",
949
+ corner_radius_value: "corner_radius",
950
+ size_value: "size"
951
+ };
952
+ function convertRoot(root, ctx) {
953
+ if (!isRecord2(root)) return root;
954
+ const out = {};
955
+ out.schema = typeof root.schema === "string" ? root.schema : "2.0";
956
+ out.config = convertConfig(isRecord2(root.config) ? root.config : {});
957
+ if (isRecord2(root.body)) out.body = convertBody2(root.body, ctx);
958
+ if (isRecord2(root.header)) out.header = convertHeader2(root.header, ctx);
956
959
  return out;
957
960
  }
958
- function convertHeaderToDsl(header) {
959
- const property = isRecord2(header.property) ? header.property : header;
960
- const out = {};
961
- for (const [k, v] of Object.entries(property)) {
962
- if (k === "ud_icon" || k === "udIcon") {
963
- if (isRecord2(v) && typeof v.tag === "string") {
964
- out.ud_icon = convertElToDsl(v);
965
- continue;
961
+ function convertConfig(_node) {
962
+ return { update_multi: true };
963
+ }
964
+ function convertHeader2(node, ctx) {
965
+ const property = isRecord2(node.property) ? node.property : {};
966
+ const out = convertProperty(property, "header", ctx);
967
+ return out;
968
+ }
969
+ function convertBody2(node, ctx) {
970
+ const property = isRecord2(node.property) ? node.property : node;
971
+ return convertProperty(property, "body", ctx);
972
+ }
973
+ function unwrap(node, ctx) {
974
+ const tag = typeof node.tag === "string" ? node.tag : void 0;
975
+ const property = isRecord2(node.property) ? node.property : {};
976
+ if (tag === "markdown") return rebuildMarkdown(property, ctx);
977
+ if (tag === "plain_text") {
978
+ const content = typeof property.content === "string" ? property.content : "";
979
+ return { tag: "plain_text", content };
980
+ }
981
+ if (tag === "standard_icon") {
982
+ const out = { tag: "standard_icon" };
983
+ if (typeof property.token === "string") out.token = property.token;
984
+ if (typeof property.color === "string") out.color = property.color;
985
+ const widthPx = isPixelScalar(property.widthValue) ? pixelOf(property.widthValue) : void 0;
986
+ const heightPx = isPixelScalar(property.heightValue) ? pixelOf(property.heightValue) : void 0;
987
+ if (widthPx !== void 0 && heightPx !== void 0) {
988
+ out.size = `${widthPx}px ${heightPx}px`;
989
+ } else if (isRecord2(property.size) && hasSide(property.size)) out.size = formatBox(property.size);
990
+ else if (isPixelScalar(property.size)) out.size = `${pixelOf(property.size)}px`;
991
+ else if (typeof property.size === "string") out.size = property.size;
992
+ return out;
993
+ }
994
+ if (tag === "ud_icon") {
995
+ const token = typeof property.token === "string" ? property.token : void 0;
996
+ const out = { tag: "standard_icon" };
997
+ if (token) out.token = token;
998
+ return out;
999
+ }
1000
+ if (tag === "chart") {
1001
+ const out = { tag: "chart" };
1002
+ if (typeof property.colorTheme === "string") out.color_theme = property.colorTheme;
1003
+ if (typeof property.preview === "boolean") out.preview = property.preview;
1004
+ if (property.chartSpec !== void 0) out.chart_spec = property.chartSpec;
1005
+ return out;
1006
+ }
1007
+ if (tag === "table") {
1008
+ const out = { tag: "table" };
1009
+ const rawColumns = Array.isArray(property.columns) ? property.columns : [];
1010
+ out.columns = rawColumns.map((col) => {
1011
+ if (!isRecord2(col)) return {};
1012
+ const c = {};
1013
+ if (typeof col.name === "string") c.name = col.name;
1014
+ const displayName = typeof col.displayName === "string" ? col.displayName : typeof col.display_name === "string" ? col.display_name : void 0;
1015
+ if (displayName !== void 0) c.display_name = displayName;
1016
+ const dataType = typeof col.dataType === "string" ? col.dataType : typeof col.data_type === "string" ? col.data_type : void 0;
1017
+ if (dataType !== void 0) c.data_type = dataType;
1018
+ const hAlign = typeof col.horizontalAlign === "string" ? col.horizontalAlign : typeof col.horizontal_align === "string" ? col.horizontal_align : void 0;
1019
+ if (hAlign !== void 0) c.horizontal_align = hAlign;
1020
+ const wv = col.widthValue;
1021
+ if (isRecord2(wv)) {
1022
+ if (wv.type === "pixels" && typeof wv.value === "number") c.width = `${wv.value}px`;
1023
+ else if (typeof wv.value === "string") c.width = wv.value;
1024
+ } else if (typeof col.width === "string" || typeof col.width === "number") {
1025
+ c.width = col.width;
966
1026
  }
1027
+ if (isRecord2(col.format)) c.format = col.format;
1028
+ return c;
1029
+ });
1030
+ const rawRows = Array.isArray(property.rows) ? property.rows : [];
1031
+ out.rows = rawRows.map((row) => {
1032
+ if (!isRecord2(row)) return {};
1033
+ const r = {};
1034
+ for (const [k, cell] of Object.entries(row)) {
1035
+ let value = isRecord2(cell) && "data" in cell ? cell.data : cell;
1036
+ if (isRecord2(value) && value.tag === "markdown" && isRecord2(value.property)) {
1037
+ const elements = Array.isArray(value.property.elements) ? value.property.elements : [];
1038
+ value = renderChildren(elements, ctx);
1039
+ }
1040
+ r[k] = value;
1041
+ }
1042
+ return r;
1043
+ });
1044
+ if (isRecord2(property.headerStyle)) {
1045
+ const hs = {};
1046
+ for (const [k, v] of Object.entries(property.headerStyle)) {
1047
+ const sk = snake(k);
1048
+ if (sk === "platform_text_size") continue;
1049
+ if (sk === "lines_value") {
1050
+ if (isRecord2(v) && typeof v.value === "number") hs.lines = v.value;
1051
+ continue;
1052
+ }
1053
+ hs[sk] = v;
1054
+ }
1055
+ out.header_style = hs;
967
1056
  }
968
- out[CAMEL_RENAME[k] ?? k] = unwrapTextLeaf(v);
1057
+ const rhv = property.rowHeightValue;
1058
+ if (isRecord2(rhv) && typeof rhv.value === "string") out.row_height = rhv.value;
1059
+ else if (typeof property.rowHeight === "string") out.row_height = property.rowHeight;
1060
+ if (typeof property.pageSize === "number") out.page_size = property.pageSize;
1061
+ if (typeof property.freezeFirstColumn === "boolean") out.freeze_first_column = property.freezeFirstColumn;
1062
+ return out;
969
1063
  }
970
- return out;
1064
+ if (tag === "input") {
1065
+ const allow = /* @__PURE__ */ new Set([
1066
+ "name",
1067
+ "placeholder",
1068
+ "default_value",
1069
+ "value",
1070
+ "label",
1071
+ "label_position",
1072
+ "margin",
1073
+ "max_length",
1074
+ "input_type",
1075
+ "width"
1076
+ ]);
1077
+ const out = { tag: "input" };
1078
+ const converted2 = convertProperty(property, "input", ctx);
1079
+ for (const [k, v] of Object.entries(converted2)) {
1080
+ if (allow.has(k)) out[k] = v;
1081
+ }
1082
+ if (converted2.required === true) out.required = true;
1083
+ return out;
1084
+ }
1085
+ if (tag === "person" || tag === "person_v1") {
1086
+ const out = { tag: "person" };
1087
+ if (typeof property.userID === "string") out.user_id = property.userID;
1088
+ else if (typeof property.user_id === "string") out.user_id = property.user_id;
1089
+ if (typeof property.size === "string") out.size = property.size;
1090
+ if (typeof property.showAvatar === "boolean") out.show_avatar = property.showAvatar;
1091
+ if (typeof property.showName === "boolean") out.show_name = property.showName;
1092
+ if (typeof property.style === "string") out.style = property.style;
1093
+ if (isRecord2(property.margin) && hasSide(property.margin)) out.margin = formatBox(property.margin);
1094
+ else if (typeof property.margin === "string") out.margin = property.margin;
1095
+ return out;
1096
+ }
1097
+ if (tag === "person_list") {
1098
+ const out = { tag: "person_list" };
1099
+ if (typeof property.size === "string") out.size = property.size;
1100
+ if (typeof property.showAvatar === "boolean") out.show_avatar = property.showAvatar;
1101
+ if (typeof property.showName === "boolean") out.show_name = property.showName;
1102
+ if (typeof property.lines === "number") out.lines = property.lines;
1103
+ if (typeof property.dropInvalidUserID === "boolean") out.drop_invalid_user_id = property.dropInvalidUserID;
1104
+ else if (typeof property.drop_invalid_user_id === "boolean") out.drop_invalid_user_id = property.drop_invalid_user_id;
1105
+ if (isRecord2(property.margin) && hasSide(property.margin)) out.margin = formatBox(property.margin);
1106
+ else if (typeof property.margin === "string") out.margin = property.margin;
1107
+ const rawPersons = Array.isArray(property.persons) ? property.persons : [];
1108
+ out.persons = rawPersons.map((p) => {
1109
+ if (!isRecord2(p)) return void 0;
1110
+ const id = typeof p.id === "string" ? p.id : typeof p.userID === "string" ? p.userID : void 0;
1111
+ return id ? { id } : void 0;
1112
+ }).filter((p) => p !== void 0);
1113
+ const iconSrc = isRecord2(property.icon) ? property.icon : isRecord2(property.udIcon) ? property.udIcon : void 0;
1114
+ if (iconSrc) {
1115
+ const ip = isRecord2(iconSrc.property) ? iconSrc.property : iconSrc;
1116
+ const icon = {};
1117
+ const iconTag = typeof iconSrc.tag === "string" ? iconSrc.tag : typeof ip.tag === "string" ? ip.tag : "standard_icon";
1118
+ icon.tag = iconTag === "ud_icon" ? "standard_icon" : iconTag;
1119
+ if (typeof ip.token === "string") icon.token = ip.token;
1120
+ if (typeof ip.color === "string") icon.color = ip.color;
1121
+ if (typeof ip.imageID === "string") icon.img_key = ip.imageID;
1122
+ else if (typeof ip.img_key === "string") icon.img_key = ip.img_key;
1123
+ if (icon.token !== void 0 || icon.img_key !== void 0) out.icon = icon;
1124
+ }
1125
+ return out;
1126
+ }
1127
+ if (tag === "div" && isRecord2(property.text) && property.fields === void 0) {
1128
+ const text = property.text;
1129
+ const textTag = typeof text.tag === "string" ? text.tag : "";
1130
+ if ((textTag === "markdown" || textTag === "lark_md") && isRecord2(text.property)) {
1131
+ return unwrap(text, ctx);
1132
+ }
1133
+ if (textTag === "plain_text") {
1134
+ const out = { tag: "div" };
1135
+ const widthVal = property.widthValue;
1136
+ if (isRecord2(widthVal) && typeof widthVal.value === "string" && typeof widthVal.type === "string" && widthVal.type.startsWith("builtin_")) {
1137
+ out.width = widthVal.value;
1138
+ } else if (typeof property.width === "string") {
1139
+ out.width = property.width;
1140
+ }
1141
+ const tp = isRecord2(text.property) ? text.property : {};
1142
+ const content = typeof tp.content === "string" ? tp.content : "";
1143
+ const textAlign = typeof tp.textAlign === "string" ? tp.textAlign : "left";
1144
+ out.text = { tag: "plain_text", content, text_align: textAlign };
1145
+ if (isRecord2(property.icon)) {
1146
+ out.icon = unwrap(property.icon, ctx);
1147
+ }
1148
+ return out;
1149
+ }
1150
+ }
1151
+ const converted = convertProperty(property, tag, ctx);
1152
+ return tag ? { tag, ...converted } : converted;
971
1153
  }
972
- function convertBodyToDsl(body) {
973
- const property = isRecord2(body.property) ? body.property : body;
1154
+ var DROP = /* @__PURE__ */ Symbol("drop");
1155
+ function convertProperty(property, ownerTag, ctx) {
974
1156
  const out = {};
975
- for (const [k, v] of Object.entries(property)) {
976
- if (k === "elements" && Array.isArray(v)) {
977
- out.elements = v.map(convertElToDsl).filter(Boolean);
1157
+ const siblings = {
1158
+ buttonActionType: ownerTag === "button" && typeof property.actionType === "string" ? property.actionType : void 0
1159
+ };
1160
+ for (const [rawKey, rawValue] of Object.entries(property)) {
1161
+ const result = convertField(rawKey, rawValue, ownerTag, ctx, siblings);
1162
+ if (result === DROP) continue;
1163
+ const [outKey, outValue] = result;
1164
+ if (outValue === DROP) continue;
1165
+ out[outKey] = outValue;
1166
+ }
1167
+ for (const [echoKey, enumKey] of Object.entries(VALUE_ECHO_TARGETS)) {
1168
+ if (!(echoKey in out)) continue;
1169
+ const echoVal = out[echoKey];
1170
+ const enumVal = out[enumKey];
1171
+ delete out[echoKey];
1172
+ if (!isRecord2(echoVal)) continue;
1173
+ const echoType = typeof echoVal.type === "string" ? echoVal.type : "";
1174
+ const echoValue = echoVal.value;
1175
+ if (echoType.includes("pixels") && typeof echoValue === "number") {
1176
+ if (enumVal === void 0 || enumVal === "default" || enumVal === "auto" || enumVal === "weighted") {
1177
+ out[enumKey] = `${echoValue}px`;
1178
+ }
978
1179
  continue;
979
1180
  }
980
- if (isRecord2(v) && typeof v.tag === "string") {
981
- out[CAMEL_RENAME[k] ?? k] = convertElToDsl(v);
1181
+ if (echoType === "aspect_ratio" && isRecord2(echoValue)) {
1182
+ const w = echoValue.width;
1183
+ const h = echoValue.height;
1184
+ if (typeof w === "number" && typeof h === "number") {
1185
+ out["size"] = `${w}:${h}`;
1186
+ }
1187
+ continue;
1188
+ }
1189
+ if (echoType === "width_height_pixels" && isRecord2(echoValue)) {
1190
+ const w = echoValue.width;
1191
+ const h = echoValue.height;
1192
+ if (typeof w === "number" && typeof h === "number") {
1193
+ out["size"] = `${w}px ${h}px`;
1194
+ }
982
1195
  continue;
983
1196
  }
984
- if (Array.isArray(v)) {
985
- out[CAMEL_RENAME[k] ?? k] = v.map((item) => isRecord2(item) && typeof item.tag === "string" ? convertElToDsl(item) : item);
1197
+ if (echoType === "builtin_image_size") continue;
1198
+ if (echoType.startsWith("builtin_") && typeof echoValue === "string") {
1199
+ if (enumVal === void 0) out[enumKey] = echoValue;
986
1200
  continue;
987
1201
  }
988
- out[CAMEL_RENAME[k] ?? k] = unwrapBox(v);
1202
+ }
1203
+ if (ownerTag && TAG_DEFAULT_DROPS[ownerTag]) {
1204
+ const defaults = TAG_DEFAULT_DROPS[ownerTag];
1205
+ for (const [k, defVal] of Object.entries(defaults)) {
1206
+ if (out[k] === defVal) delete out[k];
1207
+ }
1208
+ }
1209
+ if (ownerTag === "video") {
1210
+ for (const k of Object.keys(out)) {
1211
+ if (!VIDEO_PUBLIC_FIELDS.has(k)) delete out[k];
1212
+ }
1213
+ }
1214
+ if (ownerTag === "audio") {
1215
+ for (const k of Object.keys(out)) {
1216
+ if (!AUDIO_PUBLIC_FIELDS.has(k)) delete out[k];
1217
+ }
989
1218
  }
990
1219
  return out;
991
1220
  }
992
- function convertElToDsl(el) {
993
- if (!isRecord2(el)) return void 0;
994
- const tag = typeof el.tag === "string" ? el.tag : "";
995
- if (!tag) return void 0;
996
- if (!KNOWN_TAGS.has(tag)) return degradeUnknown(el);
997
- if ("fallback" in el && isRecord2(el.fallback)) return el;
998
- const property = isRecord2(el.property) ? el.property : {};
999
- const out = {};
1000
- if (tag !== "plain_text") out.tag = tag === "img" ? "image" : tag;
1001
- for (const [k, v] of Object.entries(property)) {
1002
- const key = CAMEL_RENAME[k] ?? k;
1003
- if (k === "elements" && Array.isArray(v)) {
1004
- if (tag === "markdown") {
1005
- out.content = elementsToMarkdown(v);
1006
- continue;
1221
+ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1222
+ if (rawKey === "originTag" || rawKey === "markdownElements") return DROP;
1223
+ const key = snake(rawKey);
1224
+ if (RAW_ONLY_FIELDS.has(key)) return DROP;
1225
+ if (isI18nMirror(key)) return DROP;
1226
+ if (key === "actions" && Array.isArray(rawValue) && rawValue.length === 0) {
1227
+ return DROP;
1228
+ }
1229
+ if ((key === "padding" || key === "margin") && isRecord2(rawValue) && hasSide(rawValue)) {
1230
+ return [key, formatBox(rawValue)];
1231
+ }
1232
+ if (key in VALUE_ECHO_TARGETS) {
1233
+ return [key, rawValue];
1234
+ }
1235
+ if (isPixelScalar(rawValue)) {
1236
+ return [key, `${pixelOf(rawValue)}px`];
1237
+ }
1238
+ if (ownerTag === "img" && key === "image_id") {
1239
+ const id = typeof rawValue === "string" || typeof rawValue === "number" ? String(rawValue) : void 0;
1240
+ const origin = id !== void 0 ? ctx.images[id]?.origin_key : void 0;
1241
+ if (origin) return ["img_key", origin];
1242
+ return [key, rawValue];
1243
+ }
1244
+ if (ownerTag === "img" && IMG_RAW_ONLY_FIELDS.has(key)) return DROP;
1245
+ if (ownerTag === "video" && key === "cover_image_id") {
1246
+ const imgKey = resolveAttachmentImgKey(rawValue, ctx);
1247
+ if (!imgKey) return DROP;
1248
+ return ["cover", { img_key: imgKey }];
1249
+ }
1250
+ if (ownerTag === "video" && key === "cover") {
1251
+ const cover = normalizeVideoCover(rawValue, ctx);
1252
+ return cover ? ["cover", cover] : DROP;
1253
+ }
1254
+ if ((ownerTag === "video" || ownerTag === "audio") && key === "file_id") {
1255
+ if (typeof rawValue === "string" && rawValue) return ["file_key", rawValue];
1256
+ return DROP;
1257
+ }
1258
+ if (ownerTag === "video" && VIDEO_RAW_ONLY_FIELDS.has(key)) return DROP;
1259
+ if ((key === "alt" || key === "hover_tips") && isRecord2(rawValue) && rawValue.tag === "plain_text") {
1260
+ const p = isRecord2(rawValue.property) ? rawValue.property : {};
1261
+ return [key, { tag: "plain_text", content: typeof p.content === "string" ? p.content : "" }];
1262
+ }
1263
+ if (key === "text" && isRecord2(rawValue) && rawValue.tag === "plain_text") {
1264
+ const p = isRecord2(rawValue.property) ? rawValue.property : {};
1265
+ return [key, { tag: "plain_text", content: typeof p.content === "string" ? p.content : "" }];
1266
+ }
1267
+ if (ownerTag === "header" && key === "ud_icon" && isRecord2(rawValue) && rawValue.tag === "ud_icon") {
1268
+ const p = isRecord2(rawValue.property) ? rawValue.property : {};
1269
+ const token = typeof p.token === "string" ? p.token : void 0;
1270
+ if (!token) return DROP;
1271
+ return ["icon", { tag: "standard_icon", token }];
1272
+ }
1273
+ if (ownerTag === "collapsible_panel" && key === "header" && isRecord2(rawValue)) {
1274
+ const cleaned = {};
1275
+ for (const [k, v] of Object.entries(rawValue)) {
1276
+ const sk = snake(k);
1277
+ if (sk === "position" || sk === "width" || sk === "vertical_align") continue;
1278
+ const sub = convertField(k, v, "collapsible_panel_header", ctx, siblings);
1279
+ if (sub === DROP) continue;
1280
+ const [subKey, subVal] = sub;
1281
+ if (subVal === DROP) continue;
1282
+ cleaned[subKey] = subVal;
1283
+ }
1284
+ return [key, cleaned];
1285
+ }
1286
+ if (ownerTag === "button" && key === "action_type") return DROP;
1287
+ if (ownerTag === "button" && key === "actions" && Array.isArray(rawValue)) {
1288
+ const openUrls = [];
1289
+ for (const a of rawValue) {
1290
+ if (!isRecord2(a)) continue;
1291
+ const inner = isRecord2(a.action) ? a.action : {};
1292
+ const t = typeof a.type === "string" ? a.type : void 0;
1293
+ if (t === "open_url" && typeof inner.url === "string") openUrls.push(inner.url);
1294
+ }
1295
+ if (openUrls.length === 0) return DROP;
1296
+ if (siblings.buttonActionType === "link") {
1297
+ return ["multi_url", { url: openUrls[0] }];
1298
+ }
1299
+ return [
1300
+ "behaviors",
1301
+ openUrls.map((url) => ({ type: "open_url", default_url: url }))
1302
+ ];
1303
+ }
1304
+ if (ownerTag === "interactive_container" && key === "actions" && Array.isArray(rawValue)) {
1305
+ const behaviors = rawValue.map((a) => {
1306
+ if (!isRecord2(a)) return void 0;
1307
+ const inner = isRecord2(a.action) ? a.action : {};
1308
+ const t = typeof a.type === "string" ? a.type : void 0;
1309
+ if (t === "open_url" && typeof inner.url === "string") {
1310
+ return { type: "open_url", default_url: inner.url };
1007
1311
  }
1008
- out.elements = v.map(convertElToDsl).filter(Boolean);
1009
- continue;
1312
+ if (t === "callback") {
1313
+ return { type: "callback", value: inner.value ?? {} };
1314
+ }
1315
+ return void 0;
1316
+ }).filter((b) => b !== void 0);
1317
+ if (behaviors.length === 0) return DROP;
1318
+ return ["behaviors", behaviors];
1319
+ }
1320
+ if (key === "border" && isRecord2(rawValue)) {
1321
+ const b = rawValue;
1322
+ const out = {};
1323
+ if (typeof b.borderColor === "string") out.color = b.borderColor;
1324
+ else if (typeof b.color === "string") out.color = b.color;
1325
+ if (isPixelScalar(b.cornerRadius)) out.corner_radius = `${pixelOf(b.cornerRadius)}px`;
1326
+ else if (typeof b.cornerRadius === "string") out.corner_radius = b.cornerRadius;
1327
+ else if (b.cornerRadius !== void 0) out.corner_radius = b.cornerRadius;
1328
+ for (const [bk, bv] of Object.entries(b)) {
1329
+ if (bk === "borderColor" || bk === "cornerRadius" || bk === "color") continue;
1330
+ out[snake(bk)] = convertGeneric(bv, ctx);
1010
1331
  }
1011
- if (k === "columns" && Array.isArray(v)) {
1012
- out.columns = v.map(convertElToDsl).filter(Boolean);
1332
+ return [key, out];
1333
+ }
1334
+ if (key === "url" && isRecord2(rawValue)) {
1335
+ const v = rawValue;
1336
+ const main = typeof v.url === "string" ? v.url : "";
1337
+ const others = ["androidURL", "iosURL", "pcURL", "harmonyURL"].map((k) => v[k]).filter((x) => typeof x === "string" && x.length > 0);
1338
+ if (others.length === 0) return [key, main];
1339
+ return [
1340
+ key,
1341
+ {
1342
+ url: main,
1343
+ android_url: typeof v.androidURL === "string" ? v.androidURL : "",
1344
+ ios_url: typeof v.iosURL === "string" ? v.iosURL : "",
1345
+ pc_url: typeof v.pcURL === "string" ? v.pcURL : ""
1346
+ }
1347
+ ];
1348
+ }
1349
+ return [key, convertGeneric(rawValue, ctx)];
1350
+ }
1351
+ function renderInlinePerson(prop) {
1352
+ const id = typeof prop.userID === "string" ? prop.userID : typeof prop.user_id === "string" ? prop.user_id : typeof prop.id === "string" ? prop.id : "";
1353
+ if (!id) return "";
1354
+ const parts = [`id='${id}'`];
1355
+ if (typeof prop.showName === "boolean") parts.push(`show_name=${prop.showName}`);
1356
+ else if (typeof prop.show_name === "boolean") parts.push(`show_name=${prop.show_name}`);
1357
+ if (typeof prop.showAvatar === "boolean") parts.push(`show_avatar=${prop.showAvatar}`);
1358
+ else if (typeof prop.show_avatar === "boolean") parts.push(`show_avatar=${prop.show_avatar}`);
1359
+ if (typeof prop.style === "string") parts.push(`style='${prop.style}'`);
1360
+ return `<person ${parts.join(" ")}></person>`;
1361
+ }
1362
+ function renderInlineAudio(prop) {
1363
+ const fileKey = typeof prop.fileID === "string" ? prop.fileID : typeof prop.file_key === "string" ? prop.file_key : typeof prop.file_id === "string" ? prop.file_id : "";
1364
+ if (!fileKey) return "";
1365
+ const attrs = [`file_key='${fileKey}'`];
1366
+ const audioId = typeof prop.audioID === "string" ? prop.audioID : typeof prop.audio_id === "string" ? prop.audio_id : void 0;
1367
+ if (audioId) attrs.push(`audio_id='${audioId}'`);
1368
+ if (prop.showTime === true || prop.show_time === true) attrs.push("show_time=true");
1369
+ const style = typeof prop.style === "string" ? prop.style : void 0;
1370
+ if (style) attrs.push(`style='${style}'`);
1371
+ const bg = typeof prop.backgroundColor === "string" ? prop.backgroundColor : typeof prop.background_color === "string" ? prop.background_color : void 0;
1372
+ if (bg) attrs.push(`background_color='${bg}'`);
1373
+ const fill = typeof prop.fillColor === "string" ? prop.fillColor : typeof prop.fill_color === "string" ? prop.fill_color : void 0;
1374
+ if (fill) attrs.push(`fill_color='${fill}'`);
1375
+ const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1376
+ if (urlObj) {
1377
+ if (typeof urlObj.url === "string" && urlObj.url) attrs.push(`fallback_url='${urlObj.url}'`);
1378
+ if (typeof urlObj.pcURL === "string" && urlObj.pcURL) attrs.push(`fallback_pc_url='${urlObj.pcURL}'`);
1379
+ else if (typeof urlObj.pc_url === "string" && urlObj.pc_url) attrs.push(`fallback_pc_url='${urlObj.pc_url}'`);
1380
+ if (typeof urlObj.iosURL === "string" && urlObj.iosURL) attrs.push(`fallback_ios_url='${urlObj.iosURL}'`);
1381
+ else if (typeof urlObj.ios_url === "string" && urlObj.ios_url) attrs.push(`fallback_ios_url='${urlObj.ios_url}'`);
1382
+ if (typeof urlObj.androidURL === "string" && urlObj.androidURL) attrs.push(`fallback_android_url='${urlObj.androidURL}'`);
1383
+ else if (typeof urlObj.android_url === "string" && urlObj.android_url) attrs.push(`fallback_android_url='${urlObj.android_url}'`);
1384
+ if (typeof urlObj.harmonyURL === "string" && urlObj.harmonyURL) attrs.push(`fallback_harmony_url='${urlObj.harmonyURL}'`);
1385
+ else if (typeof urlObj.harmony_url === "string" && urlObj.harmony_url) attrs.push(`fallback_harmony_url='${urlObj.harmony_url}'`);
1386
+ } else if (typeof prop.fallback_url === "string" && prop.fallback_url) {
1387
+ attrs.push(`fallback_url='${prop.fallback_url}'`);
1388
+ }
1389
+ const fb = isRecord2(prop.fallbackText) ? prop.fallbackText : isRecord2(prop.fallback_text) ? prop.fallback_text : void 0;
1390
+ if (fb) {
1391
+ const fbProp = isRecord2(fb.property) ? fb.property : fb;
1392
+ const fbContent = typeof fbProp.content === "string" ? fbProp.content : typeof fb.content === "string" ? fb.content : "";
1393
+ if (fbContent) attrs.push(`fallback_text='${fbContent}'`);
1394
+ }
1395
+ return `<audio ${attrs.join(" ")}></audio>`;
1396
+ }
1397
+ function resolveAtUserId(prop, ctx) {
1398
+ if (typeof prop.userID === "string" && prop.userID) return prop.userID;
1399
+ if (typeof prop.userId === "string" && prop.userId) return prop.userId;
1400
+ if (typeof prop.user_id === "string" && prop.user_id) return prop.user_id;
1401
+ const key = typeof prop.atUserKey === "string" ? prop.atUserKey : typeof prop.at_user_key === "string" ? prop.at_user_key : void 0;
1402
+ if (!key || !ctx.atUsers[key]) return void 0;
1403
+ if (/^(ou_|on_)/.test(key)) return key;
1404
+ return ctx.atUsers[key].user_id;
1405
+ }
1406
+ function rebuildMarkdown(property, ctx) {
1407
+ const out = { tag: "markdown" };
1408
+ const elements = Array.isArray(property.elements) ? property.elements : [];
1409
+ const content = renderChildren(elements, ctx);
1410
+ if (content) out.content = content;
1411
+ for (const [rawKey, rawValue] of Object.entries(property)) {
1412
+ if (rawKey === "elements" || rawKey === "markdownElements" || rawKey === "originTag") continue;
1413
+ if (rawKey === "udIcon") continue;
1414
+ if (rawKey === "textStyle") {
1415
+ const ts = isRecord2(rawValue) ? rawValue : {};
1416
+ const size = typeof ts.size === "string" ? ts.size : void 0;
1417
+ if (size) out.text_size = size;
1013
1418
  continue;
1014
1419
  }
1015
- if (isRecord2(v) && typeof v.tag === "string") {
1016
- out[key] = convertElToDsl(v);
1017
- continue;
1420
+ const result = convertField(rawKey, rawValue, "markdown", ctx);
1421
+ if (result === DROP) continue;
1422
+ const [outKey, outValue] = result;
1423
+ if (outValue === DROP) continue;
1424
+ out[outKey] = outValue;
1425
+ }
1426
+ return out;
1427
+ }
1428
+ function wrapEmphasis(text, bold, color) {
1429
+ if (!bold && !color) return text;
1430
+ const lead = text.match(/^\s*/)[0];
1431
+ const trail = text.match(/\s*$/)[0];
1432
+ const core = text.slice(lead.length, text.length - trail.length);
1433
+ if (core.length === 0) return text;
1434
+ let wrapped = core;
1435
+ if (bold) wrapped = `**${wrapped}**`;
1436
+ if (color) wrapped = `<font color='${color}'>${wrapped}</font>`;
1437
+ return lead + wrapped + trail;
1438
+ }
1439
+ function renderChildren(children, ctx) {
1440
+ let out = "";
1441
+ let buffer = [];
1442
+ function flushBuffer() {
1443
+ if (buffer.length === 0) return;
1444
+ let group = [];
1445
+ function emitGroup() {
1446
+ if (group.length === 0) return;
1447
+ const inner = group.map((s) => s.content).join("");
1448
+ const { bold, color } = group[0];
1449
+ out += wrapEmphasis(inner, bold, color);
1450
+ group = [];
1018
1451
  }
1019
- if (Array.isArray(v)) {
1020
- out[key] = v.map((item) => isRecord2(item) && typeof item.tag === "string" ? convertElToDsl(item) : item);
1452
+ for (const seg of buffer) {
1453
+ if (group.length === 0) {
1454
+ group.push(seg);
1455
+ continue;
1456
+ }
1457
+ const head = group[0];
1458
+ if (head.bold === seg.bold && head.color === seg.color) group.push(seg);
1459
+ else {
1460
+ emitGroup();
1461
+ group.push(seg);
1462
+ }
1463
+ }
1464
+ emitGroup();
1465
+ buffer = [];
1466
+ }
1467
+ for (const child of children) {
1468
+ if (!isRecord2(child)) continue;
1469
+ const inline = childToInlineSegment(child, ctx);
1470
+ if (inline) {
1471
+ buffer.push(inline);
1021
1472
  continue;
1022
1473
  }
1023
- out[key] = unwrapBox(unwrapTextLeaf(v));
1474
+ flushBuffer();
1475
+ out += childToMarkdown(child, ctx);
1024
1476
  }
1477
+ flushBuffer();
1025
1478
  return out;
1026
1479
  }
1027
- function unwrapTextLeaf(v) {
1028
- if (isRecord2(v) && v.tag === "plain_text") {
1029
- const p = isRecord2(v.property) ? v.property : {};
1030
- return typeof p.content === "string" ? p.content : "";
1480
+ function childToInlineSegment(child, ctx) {
1481
+ const tag = child.tag;
1482
+ const prop = isRecord2(child.property) ? child.property : {};
1483
+ const ts = isRecord2(prop.textStyle) ? prop.textStyle : void 0;
1484
+ const bold = !!(ts && Array.isArray(ts.attributes) && ts.attributes.includes("bold"));
1485
+ const rawColor = ts && typeof ts.color === "string" ? ts.color : void 0;
1486
+ const color = rawColor === "default" ? void 0 : rawColor;
1487
+ switch (tag) {
1488
+ case "plain_text":
1489
+ return {
1490
+ content: typeof prop.content === "string" ? prop.content : "",
1491
+ bold,
1492
+ color
1493
+ };
1494
+ case "code_span":
1495
+ return {
1496
+ content: `\`${typeof prop.content === "string" ? prop.content : ""}\``,
1497
+ bold,
1498
+ color
1499
+ };
1500
+ case "link": {
1501
+ const text = typeof prop.content === "string" ? prop.content : "";
1502
+ const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1503
+ const url = urlObj && typeof urlObj.url === "string" ? urlObj.url : "";
1504
+ return { content: `[${text}](${url})`, bold, color };
1505
+ }
1506
+ case "at_all":
1507
+ return { content: "<at id=all></at>", bold, color };
1508
+ case "at": {
1509
+ const userId = resolveAtUserId(prop, ctx);
1510
+ return { content: userId ? `<at id=${userId}></at>` : "<at></at>", bold, color };
1511
+ }
1512
+ case "text_tag": {
1513
+ const tagColor = typeof prop.color === "string" ? prop.color : void 0;
1514
+ const inner = isRecord2(prop.text) ? prop.text : void 0;
1515
+ const innerProp = inner && isRecord2(inner.property) ? inner.property : void 0;
1516
+ const innerContent = innerProp && typeof innerProp.content === "string" ? innerProp.content : "";
1517
+ const rendered = tagColor ? `<text_tag color='${tagColor}'>${innerContent}</text_tag>` : `<text_tag>${innerContent}</text_tag>`;
1518
+ return { content: rendered, bold: false };
1519
+ }
1520
+ case "emoji": {
1521
+ const key = typeof prop.key === "string" ? prop.key : typeof prop.emojiKey === "string" ? prop.emojiKey : "";
1522
+ return key ? { content: `:${key}:`, bold: false } : void 0;
1523
+ }
1524
+ case "person":
1525
+ case "person_v1": {
1526
+ const rendered = renderInlinePerson(prop);
1527
+ return rendered ? { content: rendered, bold: false } : void 0;
1528
+ }
1529
+ case "markdown_audio": {
1530
+ const rendered = renderInlineAudio(prop);
1531
+ return rendered ? { content: rendered, bold: false } : void 0;
1532
+ }
1533
+ default:
1534
+ return void 0;
1031
1535
  }
1032
- return v;
1033
1536
  }
1034
- function unwrapBox(v) {
1035
- if (!isRecord2(v) || !isRecord2(v.top) || !isRecord2(v.right)) return v;
1036
- const side = (s) => isRecord2(s) && typeof s.value === "number" ? `${s.value}px` : "0px";
1037
- return `${side(v.top)} ${side(v.right)} ${side(v.bottom ?? v.top)} ${side(v.left ?? v.right)}`;
1537
+ function childToMarkdown(child, ctx) {
1538
+ const tag = child.tag;
1539
+ const prop = isRecord2(child.property) ? child.property : {};
1540
+ switch (tag) {
1541
+ case "plain_text": {
1542
+ const content = typeof prop.content === "string" ? prop.content : "";
1543
+ const ts = isRecord2(prop.textStyle) ? prop.textStyle : void 0;
1544
+ const color = ts && typeof ts.color === "string" ? ts.color : void 0;
1545
+ const attrs = ts && Array.isArray(ts.attributes) ? ts.attributes : [];
1546
+ return wrapEmphasis(content, attrs.includes("bold"), color);
1547
+ }
1548
+ case "br":
1549
+ return "\n";
1550
+ case "emoji": {
1551
+ const key = typeof prop.key === "string" ? prop.key : typeof prop.emojiKey === "string" ? prop.emojiKey : "";
1552
+ return key ? `:${key}:` : "";
1553
+ }
1554
+ case "at_all":
1555
+ return "<at id=all></at>";
1556
+ case "at": {
1557
+ const userId = resolveAtUserId(prop, ctx);
1558
+ return userId ? `<at id=${userId}></at>` : "<at></at>";
1559
+ }
1560
+ case "person":
1561
+ case "person_v1":
1562
+ return renderInlinePerson(prop);
1563
+ case "markdown_audio":
1564
+ return renderInlineAudio(prop);
1565
+ case "link": {
1566
+ const text = typeof prop.content === "string" ? prop.content : "";
1567
+ const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1568
+ const url = urlObj && typeof urlObj.url === "string" ? urlObj.url : "";
1569
+ return `[${text}](${url})`;
1570
+ }
1571
+ case "code_span": {
1572
+ const text = typeof prop.content === "string" ? prop.content : "";
1573
+ return `\`${text}\``;
1574
+ }
1575
+ case "text_tag": {
1576
+ const color = typeof prop.color === "string" ? prop.color : void 0;
1577
+ const inner = isRecord2(prop.text) ? prop.text : void 0;
1578
+ const innerProp = inner && isRecord2(inner.property) ? inner.property : void 0;
1579
+ const innerContent = innerProp && typeof innerProp.content === "string" ? innerProp.content : "";
1580
+ if (color) return `<text_tag color='${color}'>${innerContent}</text_tag>`;
1581
+ return `<text_tag>${innerContent}</text_tag>`;
1582
+ }
1583
+ case "code_block": {
1584
+ const lang = typeof prop.language === "string" ? prop.language : "";
1585
+ const blocks = Array.isArray(prop.contents) ? prop.contents : [];
1586
+ const lines = blocks.map((blk) => {
1587
+ if (!isRecord2(blk)) return "";
1588
+ const inner = Array.isArray(blk.contents) ? blk.contents : [];
1589
+ let line = "";
1590
+ for (const seg of inner) {
1591
+ if (isRecord2(seg) && typeof seg.content === "string") line += seg.content;
1592
+ }
1593
+ return line;
1594
+ });
1595
+ const body = lines.join("\n").replace(/\n+$/, "");
1596
+ return `
1597
+ \`\`\`${lang}
1598
+ ${body}
1599
+ \`\`\`
1600
+ `;
1601
+ }
1602
+ case "list": {
1603
+ const items = Array.isArray(prop.items) ? prop.items : [];
1604
+ const lines = [];
1605
+ const levelCounters = [];
1606
+ for (const item of items) {
1607
+ if (!isRecord2(item)) continue;
1608
+ const level = typeof item.level === "number" ? item.level : 0;
1609
+ const type = typeof item.type === "string" ? item.type : "ul";
1610
+ const indent = " ".repeat(level);
1611
+ const inner = Array.isArray(item.elements) ? item.elements : [];
1612
+ let text = "";
1613
+ for (const seg of inner) {
1614
+ if (!isRecord2(seg)) continue;
1615
+ text += childToMarkdown(seg, ctx);
1616
+ }
1617
+ if (type === "ol") {
1618
+ let n;
1619
+ if (typeof item.order === "number" && Number.isFinite(item.order) && item.order > 0) {
1620
+ n = Math.floor(item.order);
1621
+ levelCounters.length = level + 1;
1622
+ levelCounters[level] = n;
1623
+ } else {
1624
+ levelCounters.length = level + 1;
1625
+ levelCounters[level] = (levelCounters[level] ?? 0) + 1;
1626
+ n = levelCounters[level];
1627
+ }
1628
+ lines.push(`${indent}${n}. ${text}`);
1629
+ } else {
1630
+ lines.push(`${indent}- ${text}`);
1631
+ }
1632
+ }
1633
+ return `
1634
+ ${lines.join("\n")}
1635
+
1636
+ `;
1637
+ }
1638
+ case "heading": {
1639
+ const level = typeof prop.level === "number" ? prop.level : 1;
1640
+ const inner = Array.isArray(prop.elements) ? prop.elements : [];
1641
+ let body = "";
1642
+ for (const seg of inner) {
1643
+ if (isRecord2(seg)) body += childToMarkdown(seg, ctx);
1644
+ }
1645
+ return `
1646
+
1647
+ ${"#".repeat(level)} ${body}
1648
+
1649
+ `;
1650
+ }
1651
+ case "hr": {
1652
+ return "\n\n---\n\n";
1653
+ }
1654
+ case "table": {
1655
+ const columns = Array.isArray(prop.columns) ? prop.columns : [];
1656
+ const rows = Array.isArray(prop.rows) ? prop.rows : [];
1657
+ if (columns.length === 0) return "";
1658
+ const headers = columns.map(
1659
+ (col) => isRecord2(col) && typeof col.displayName === "string" ? col.displayName : ""
1660
+ );
1661
+ const headerLine = `| ${headers.join(" | ")} |`;
1662
+ const separator = `| ${columns.map(() => "--------").join(" | ")} |`;
1663
+ const bodyLines = rows.map((row) => {
1664
+ if (!isRecord2(row)) return `| ${columns.map(() => "").join(" | ")} |`;
1665
+ const cells = columns.map((col) => {
1666
+ const name = isRecord2(col) && typeof col.name === "string" ? col.name : "";
1667
+ const cell = isRecord2(row[name]) ? row[name] : void 0;
1668
+ const data = cell && isRecord2(cell.data) ? cell.data : void 0;
1669
+ if (!data || data.tag !== "markdown") return "";
1670
+ const cellProp = isRecord2(data.property) ? data.property : {};
1671
+ const inner = Array.isArray(cellProp.elements) ? cellProp.elements : [];
1672
+ const text = renderChildren(inner, ctx);
1673
+ return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
1674
+ });
1675
+ return `| ${cells.join(" | ")} |`;
1676
+ });
1677
+ return `
1678
+
1679
+ ${headerLine}
1680
+ ${separator}
1681
+ ${bodyLines.join("\n")}
1682
+
1683
+ `;
1684
+ }
1685
+ case "blockquote": {
1686
+ const inner = Array.isArray(prop.elements) ? prop.elements : [];
1687
+ let body = "";
1688
+ for (const seg of inner) {
1689
+ if (isRecord2(seg)) body += childToMarkdown(seg, ctx);
1690
+ }
1691
+ return `
1692
+
1693
+ > ${body.replace(/\n/g, "\n> ")}
1694
+
1695
+ `;
1696
+ }
1697
+ default:
1698
+ return typeof prop.content === "string" ? prop.content : "";
1699
+ }
1700
+ }
1701
+ function convertGeneric(value, ctx) {
1702
+ if (Array.isArray(value)) {
1703
+ return value.map((item) => {
1704
+ if (isRecord2(item) && typeof item.tag === "string" && isRecord2(item.property)) {
1705
+ return unwrap(item, ctx);
1706
+ }
1707
+ return convertGeneric(item, ctx);
1708
+ });
1709
+ }
1710
+ if (isRecord2(value)) {
1711
+ if (typeof value.tag === "string" && isRecord2(value.property)) {
1712
+ return unwrap(value, ctx);
1713
+ }
1714
+ if (hasSide(value)) {
1715
+ return formatBox(value);
1716
+ }
1717
+ if (isPixelScalar(value)) {
1718
+ return `${pixelOf(value)}px`;
1719
+ }
1720
+ const out = {};
1721
+ for (const [k, v] of Object.entries(value)) {
1722
+ out[snake(k)] = convertGeneric(v, ctx);
1723
+ }
1724
+ return out;
1725
+ }
1726
+ return value;
1727
+ }
1728
+ function isRecord2(v) {
1729
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1730
+ }
1731
+ function resolveAttachmentImgKey(rawValue, ctx) {
1732
+ if (typeof rawValue !== "string" && typeof rawValue !== "number") return void 0;
1733
+ const id = String(rawValue);
1734
+ const origin = ctx.images[id]?.origin_key;
1735
+ if (typeof origin === "string" && origin) return origin;
1736
+ if (id.startsWith("img_")) return id;
1737
+ return void 0;
1038
1738
  }
1039
- function elementsToMarkdown(els) {
1040
- const parts = [];
1041
- for (const el of els) {
1042
- const p = isRecord2(el?.property) ? el.property : {};
1043
- if (typeof p.content === "string") parts.push(p.content);
1044
- else if (Array.isArray(p.elements)) parts.push(elementsToMarkdown(p.elements));
1045
- }
1046
- return parts.join("");
1739
+ function normalizeVideoCover(rawValue, ctx) {
1740
+ if (typeof rawValue === "string" || typeof rawValue === "number") {
1741
+ const imgKey2 = resolveAttachmentImgKey(rawValue, ctx);
1742
+ return imgKey2 ? { img_key: imgKey2 } : void 0;
1743
+ }
1744
+ if (!isRecord2(rawValue)) return void 0;
1745
+ if (typeof rawValue.img_key === "string" && rawValue.img_key) {
1746
+ return { img_key: rawValue.img_key };
1747
+ }
1748
+ const candidate = rawValue.imageID ?? rawValue.image_id ?? rawValue.cover_image_id ?? rawValue.coverImageId;
1749
+ const imgKey = resolveAttachmentImgKey(candidate, ctx);
1750
+ return imgKey ? { img_key: imgKey } : void 0;
1751
+ }
1752
+ function hasSide(v) {
1753
+ 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);
1754
+ }
1755
+ function formatBox(v) {
1756
+ return `${pixelOf(v.top)}px ${pixelOf(v.right)}px ${pixelOf(v.bottom)}px ${pixelOf(v.left)}px`;
1757
+ }
1758
+ function isPixelScalar(v) {
1759
+ if (!isRecord2(v)) return false;
1760
+ return typeof v.type === "string" && v.type.includes("pixels") && typeof v.value === "number";
1761
+ }
1762
+ function pixelOf(side) {
1763
+ if (!isRecord2(side)) return 0;
1764
+ const v = side.value;
1765
+ if (typeof v === "number") return v;
1766
+ if (typeof v === "string") {
1767
+ const n = Number(v);
1768
+ return Number.isFinite(n) ? n : 0;
1769
+ }
1770
+ return 0;
1771
+ }
1772
+ function snake(key) {
1773
+ 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();
1774
+ }
1775
+ function rawToDsl(raw) {
1776
+ if (isRawCardEnvelope(raw)) return rawCardToDsl(raw);
1777
+ if (typeof raw === "string") {
1778
+ try {
1779
+ return rawToDsl(JSON.parse(raw));
1780
+ } catch {
1781
+ return raw;
1782
+ }
1783
+ }
1784
+ if (!isRecord2(raw)) return raw;
1785
+ if (typeof raw.json_card === "string" || isRecord2(raw.json_card)) {
1786
+ let inner = raw.json_card;
1787
+ if (typeof inner === "string") {
1788
+ try {
1789
+ inner = JSON.parse(inner);
1790
+ } catch {
1791
+ return raw;
1792
+ }
1793
+ }
1794
+ const attachment = isRecord2(raw.json_attachment) ? raw.json_attachment : {};
1795
+ const ctx = {
1796
+ images: isRecord2(attachment.images) ? attachment.images : {},
1797
+ atUsers: isRecord2(attachment.at_users) ? attachment.at_users : {}
1798
+ };
1799
+ return convertRoot(inner, ctx);
1800
+ }
1801
+ if (isRecord2(raw.card)) {
1802
+ const card = raw.card;
1803
+ if (typeof card.json_card === "string" || isRecord2(card.json_card)) {
1804
+ return rawToDsl(card);
1805
+ }
1806
+ return convertRoot(card, { images: {}, atUsers: {} });
1807
+ }
1808
+ if ("body" in raw || "header" in raw || "schema" in raw) {
1809
+ return convertRoot(raw, { images: {}, atUsers: {} });
1810
+ }
1811
+ return raw;
1812
+ }
1813
+ function degradeUnknown(el) {
1814
+ const rawTag = typeof el.tag === "string" ? el.tag : "undefined";
1815
+ return { tag: "unknown", raw_tag: rawTag, fallback: el };
1047
1816
  }
1048
1817
 
1049
1818
  exports.degradeUnknown = degradeUnknown;
1050
1819
  exports.dslToRaw = dslToRaw;
1820
+ exports.isRawCardEnvelope = isRawCardEnvelope;
1821
+ exports.rawCardToDsl = rawCardToDsl;
1051
1822
  exports.rawToDsl = rawToDsl;