@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 +933 -162
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +932 -163
- package/dist/raw-to-dsl.d.ts +60 -6
- package/dist/raw-to-dsl.d.ts.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -847,201 +847,970 @@ function isRecord(v) {
|
|
|
847
847
|
}
|
|
848
848
|
|
|
849
849
|
// src/raw-to-dsl.ts
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
backgroundColor: "background_color",
|
|
855
|
-
userID: "user_id",
|
|
856
|
-
showAvatar: "show_avatar",
|
|
857
|
-
showName: "show_name",
|
|
858
|
-
hasBorder: "has_border",
|
|
859
|
-
hoverTips: "hover_tips",
|
|
860
|
-
pillShaped: "pill_shaped",
|
|
861
|
-
tagStyle: "tag_style",
|
|
862
|
-
scaleType: "scale_type",
|
|
863
|
-
dropInvalidUserID: "drop_invalid_user_id",
|
|
864
|
-
udIcon: "ud_icon",
|
|
865
|
-
// video / audio
|
|
866
|
-
fileID: "file_key",
|
|
867
|
-
coverImageID: "image_key",
|
|
868
|
-
showTime: "show_time",
|
|
869
|
-
enableDownload: "enable_download",
|
|
870
|
-
audioID: "audio_id",
|
|
871
|
-
showProgressBar: "show_progress_bar",
|
|
872
|
-
fillColor: "fill_color"
|
|
873
|
-
};
|
|
874
|
-
var KNOWN_TAGS = /* @__PURE__ */ new Set([
|
|
875
|
-
"markdown",
|
|
876
|
-
"lark_md",
|
|
877
|
-
"div",
|
|
878
|
-
"column_set",
|
|
879
|
-
"column",
|
|
880
|
-
"hr",
|
|
881
|
-
"image",
|
|
882
|
-
"img",
|
|
883
|
-
"button",
|
|
884
|
-
"overflow",
|
|
885
|
-
"select_static",
|
|
886
|
-
"select_multi",
|
|
887
|
-
"input",
|
|
888
|
-
"date_picker",
|
|
889
|
-
"picker_time",
|
|
890
|
-
"picker_date",
|
|
891
|
-
"checker",
|
|
892
|
-
"chart",
|
|
893
|
-
"table",
|
|
894
|
-
"iframe",
|
|
895
|
-
"person",
|
|
896
|
-
"person_list",
|
|
897
|
-
"interactive_container",
|
|
898
|
-
"tag",
|
|
899
|
-
"text_tag",
|
|
900
|
-
"multi_image",
|
|
901
|
-
"plain_text",
|
|
902
|
-
"lark_md_inline",
|
|
903
|
-
"image_text",
|
|
904
|
-
"carousel",
|
|
905
|
-
"grid",
|
|
906
|
-
"speaking",
|
|
907
|
-
"vote",
|
|
908
|
-
"lottery",
|
|
909
|
-
"calender",
|
|
910
|
-
"schedule",
|
|
911
|
-
"share_chat",
|
|
912
|
-
"share_user",
|
|
913
|
-
"add_on",
|
|
914
|
-
"whole_text",
|
|
915
|
-
"cropper_image",
|
|
916
|
-
"highlight",
|
|
917
|
-
"local_file",
|
|
918
|
-
"file_preview",
|
|
919
|
-
"input_select",
|
|
920
|
-
"picker_datetime",
|
|
921
|
-
"picker_multi",
|
|
922
|
-
"rule",
|
|
923
|
-
"statistic",
|
|
924
|
-
"task",
|
|
925
|
-
"card_header",
|
|
926
|
-
"body",
|
|
927
|
-
"audio",
|
|
928
|
-
"video",
|
|
929
|
-
"markdown_audio",
|
|
930
|
-
"unknown"
|
|
931
|
-
]);
|
|
932
|
-
function isRecord2(v) {
|
|
933
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
934
|
-
}
|
|
935
|
-
function degradeUnknown(el) {
|
|
936
|
-
const rawTag = typeof el.tag === "string" ? el.tag : "undefined";
|
|
937
|
-
return { tag: "unknown", raw_tag: rawTag, fallback: el };
|
|
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";
|
|
938
854
|
}
|
|
939
|
-
function
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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;
|
|
944
863
|
}
|
|
945
|
-
const
|
|
946
|
-
|
|
864
|
+
const ctx = {
|
|
865
|
+
images: env.json_attachment?.images ?? {},
|
|
866
|
+
atUsers: env.json_attachment?.at_users ?? {}
|
|
947
867
|
};
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
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"
|
|
953
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);
|
|
954
957
|
return out;
|
|
955
958
|
}
|
|
956
|
-
function
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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;
|
|
964
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;
|
|
965
1054
|
}
|
|
966
|
-
|
|
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;
|
|
967
1061
|
}
|
|
968
|
-
|
|
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;
|
|
969
1151
|
}
|
|
970
|
-
|
|
971
|
-
|
|
1152
|
+
var DROP = /* @__PURE__ */ Symbol("drop");
|
|
1153
|
+
function convertProperty(property, ownerTag, ctx) {
|
|
972
1154
|
const out = {};
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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
|
+
}
|
|
976
1177
|
continue;
|
|
977
1178
|
}
|
|
978
|
-
if (
|
|
979
|
-
|
|
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
|
+
}
|
|
980
1193
|
continue;
|
|
981
1194
|
}
|
|
982
|
-
if (
|
|
983
|
-
|
|
1195
|
+
if (echoType === "builtin_image_size") continue;
|
|
1196
|
+
if (echoType.startsWith("builtin_") && typeof echoValue === "string") {
|
|
1197
|
+
if (enumVal === void 0) out[enumKey] = echoValue;
|
|
984
1198
|
continue;
|
|
985
1199
|
}
|
|
986
|
-
|
|
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
|
+
}
|
|
987
1216
|
}
|
|
988
1217
|
return out;
|
|
989
1218
|
}
|
|
990
|
-
function
|
|
991
|
-
if (
|
|
992
|
-
const
|
|
993
|
-
if (
|
|
994
|
-
if (
|
|
995
|
-
if ("
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
if (
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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 };
|
|
1005
1309
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
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);
|
|
1008
1329
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
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;
|
|
1011
1416
|
continue;
|
|
1012
1417
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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 = [];
|
|
1016
1449
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
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);
|
|
1019
1470
|
continue;
|
|
1020
1471
|
}
|
|
1021
|
-
|
|
1472
|
+
flushBuffer();
|
|
1473
|
+
out += childToMarkdown(child, ctx);
|
|
1022
1474
|
}
|
|
1475
|
+
flushBuffer();
|
|
1023
1476
|
return out;
|
|
1024
1477
|
}
|
|
1025
|
-
function
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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;
|
|
1029
1533
|
}
|
|
1030
|
-
return v;
|
|
1031
1534
|
}
|
|
1032
|
-
function
|
|
1033
|
-
|
|
1034
|
-
const
|
|
1035
|
-
|
|
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;
|
|
1036
1736
|
}
|
|
1037
|
-
function
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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 };
|
|
1045
1814
|
}
|
|
1046
1815
|
|
|
1047
|
-
export { degradeUnknown, dslToRaw, rawToDsl };
|
|
1816
|
+
export { degradeUnknown, dslToRaw, isRawCardEnvelope, rawCardToDsl, rawToDsl };
|