@open-feishu-card/adapter 0.3.18 → 0.3.20

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
@@ -818,10 +818,12 @@ function convertEl(el, colors) {
818
818
  }
819
819
  }
820
820
  if (tag === "video") {
821
- const cover = el.image_key ?? el.cover_image_key ?? el.coverImageID;
821
+ const nestedCover = isRecord2(el.cover) ? el.cover.img_key : void 0;
822
+ const cover = nestedCover ?? el.image_key ?? el.cover_image_key ?? el.coverImageID;
822
823
  if (typeof cover === "string" && cover) property.coverImageID = cover;
823
824
  delete property.image_key;
824
825
  delete property.cover_image_key;
826
+ delete property.cover;
825
827
  }
826
828
  return withRawElementId(el, { tag, property });
827
829
  }
@@ -1013,14 +1015,15 @@ function behaviorsToActions(behaviors) {
1013
1015
  for (const b of behaviors) {
1014
1016
  if (!isRecord2(b)) continue;
1015
1017
  const type = typeof b.type === "string" ? b.type : "open_url";
1016
- if (type === "callback") {
1017
- out.push({ type: "callback", action: { value: isRecord2(b.value) ? b.value : {} } });
1018
+ if (type === "callback" || type === "action_request") {
1019
+ const value = isRecord2(b.value) || typeof b.value === "string" ? b.value : {};
1020
+ out.push({ type: "action_request", action: { value } });
1018
1021
  continue;
1019
1022
  }
1020
1023
  const action = { url: normalizeUrl(b.default_url) };
1021
- if (typeof b.pc_url === "string" && b.pc_url) action.pcURL = b.pc_url;
1022
- if (typeof b.ios_url === "string" && b.ios_url) action.iosURL = b.ios_url;
1023
- if (typeof b.android_url === "string" && b.android_url) action.androidURL = b.android_url;
1024
+ if (typeof b.pc_url === "string" && b.pc_url) action.pc_url = b.pc_url;
1025
+ if (typeof b.ios_url === "string" && b.ios_url) action.ios_url = b.ios_url;
1026
+ if (typeof b.android_url === "string" && b.android_url) action.android_url = b.android_url;
1024
1027
  out.push({ type, action });
1025
1028
  }
1026
1029
  return out;
@@ -1028,10 +1031,10 @@ function behaviorsToActions(behaviors) {
1028
1031
  function multiUrlToOpenUrlAction(multi) {
1029
1032
  const url = typeof multi.url === "string" ? multi.url : typeof multi.default_url === "string" ? multi.default_url : "";
1030
1033
  const action = { url };
1031
- if (typeof multi.pc_url === "string" && multi.pc_url) action.pcURL = multi.pc_url;
1032
- if (typeof multi.ios_url === "string" && multi.ios_url) action.iosURL = multi.ios_url;
1034
+ if (typeof multi.pc_url === "string" && multi.pc_url) action.pc_url = multi.pc_url;
1035
+ if (typeof multi.ios_url === "string" && multi.ios_url) action.ios_url = multi.ios_url;
1033
1036
  if (typeof multi.android_url === "string" && multi.android_url) {
1034
- action.androidURL = multi.android_url;
1037
+ action.android_url = multi.android_url;
1035
1038
  }
1036
1039
  return { type: "open_url", action };
1037
1040
  }
@@ -1343,7 +1346,7 @@ function isRawCardEnvelope(value) {
1343
1346
  const v = value;
1344
1347
  return typeof v.json_card === "string" && typeof v.card_schema === "number";
1345
1348
  }
1346
- function rawCardToDsl(envelope) {
1349
+ function rawCardToDsl(envelope, options) {
1347
1350
  if (!isRawCardEnvelope(envelope)) return envelope;
1348
1351
  const env = envelope;
1349
1352
  let inner;
@@ -1354,7 +1357,8 @@ function rawCardToDsl(envelope) {
1354
1357
  }
1355
1358
  const ctx = makeCtx({
1356
1359
  images: env.json_attachment?.images ?? {},
1357
- atUsers: env.json_attachment?.at_users ?? {}
1360
+ atUsers: env.json_attachment?.at_users ?? {},
1361
+ onWarn: options?.onWarn
1358
1362
  });
1359
1363
  return convertRoot(inner, ctx);
1360
1364
  }
@@ -1362,7 +1366,8 @@ function makeCtx(partial) {
1362
1366
  return {
1363
1367
  images: partial?.images ?? {},
1364
1368
  atUsers: partial?.atUsers ?? {},
1365
- colors: createColorRegistry()
1369
+ colors: createColorRegistry(),
1370
+ onWarn: partial?.onWarn
1366
1371
  };
1367
1372
  }
1368
1373
  var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
@@ -1786,6 +1791,67 @@ function openUrlFieldsToMultiUrl(fields) {
1786
1791
  if (fields.android_url) multi.android_url = fields.android_url;
1787
1792
  return multi;
1788
1793
  }
1794
+ var RAW_CALLBACK_TYPES = /* @__PURE__ */ new Set(["callback", "action_request"]);
1795
+ function callbackValueFromRaw(inner, ownerTag, warn) {
1796
+ const v = inner.value;
1797
+ if (isRecord3(v)) return v;
1798
+ if (typeof v === "string" && v.length > 0) return v;
1799
+ const hadObjectValue = inner.isValueObjectType === true;
1800
+ const hasActionId = typeof inner.actionID === "string" && inner.actionID.length > 0;
1801
+ if (hadObjectValue) {
1802
+ warn?.({
1803
+ code: "callback_payload_cleared",
1804
+ ownerTag,
1805
+ message: `\u670D\u52A1\u7AEF\u628A\u56DE\u8C03\u5BF9\u8C61\u8F7D\u8377\u6362\u6210\u4E86 actionID \u5F15\u7528${hasActionId ? ` (${String(inner.actionID)})` : ""}\uFF0C\u6295\u5F71\u51FA\u7684 behaviors.value \u662F\u7A7A\u5BF9\u8C61\uFF0C\u70B9\u51FB\u56DE\u8C03\u62FF\u4E0D\u5230\u539F\u59CB\u53C2\u6570\u3002\u4FDD\u4F4F\u53C2\u6570\u9700\u53D1\u9001\u4FA7\u7559\u5B58\u539F\u59CB DSL\u3002`
1806
+ });
1807
+ return {};
1808
+ }
1809
+ if (hasActionId) {
1810
+ warn?.({
1811
+ code: "callback_payload_cleared",
1812
+ ownerTag,
1813
+ message: `\u670D\u52A1\u7AEF\u628A\u56DE\u8C03\u8F7D\u8377\u6362\u6210\u4E86 actionID \u5F15\u7528 (${String(inner.actionID)})\uFF0C\u539F\u503C\u5DF2\u4E0D\u53EF\u89C1\uFF1B\u6295\u5F71\u51FA\u7684 behaviors.value \u4FDD\u7559\u7A7A\u5B57\u7B26\u4E32\uFF0C\u70B9\u51FB\u56DE\u8C03\u62FF\u4E0D\u5230\u539F\u59CB\u53C2\u6570\u3002`
1814
+ });
1815
+ return "";
1816
+ }
1817
+ if (v === void 0) {
1818
+ warn?.({
1819
+ code: "callback_payload_cleared",
1820
+ ownerTag,
1821
+ message: "\u56DE\u8C03 action \u6CA1\u6709 value\uFF08\u670D\u52A1\u7AEF\u56DE\u8BFB\u5F62\u6001\uFF09\uFF0C\u6295\u5F71\u4E3A behaviors.value: {}\uFF1B\u70B9\u51FB\u56DE\u8C03\u62FF\u4E0D\u5230\u539F\u59CB\u53C2\u6570\uFF0C\u9700\u53D1\u9001\u4FA7\u7559\u5B58\u539F\u59CB DSL\u3002"
1822
+ });
1823
+ return {};
1824
+ }
1825
+ return "";
1826
+ }
1827
+ function actionEntryToBehavior(entry, ownerTag, ctx, onWarn = ctx.onWarn) {
1828
+ if (!isRecord3(entry)) return void 0;
1829
+ const inner = isRecord3(entry.action) ? entry.action : {};
1830
+ const type = typeof entry.type === "string" ? entry.type : void 0;
1831
+ if (type === "open_url") {
1832
+ const fields = projectOpenUrlFields(inner);
1833
+ return fields ? { type: "open_url", ...fields } : void 0;
1834
+ }
1835
+ if (type && RAW_CALLBACK_TYPES.has(type)) {
1836
+ return {
1837
+ type: "callback",
1838
+ value: callbackValueFromRaw(inner, ownerTag, onWarn ?? void 0)
1839
+ };
1840
+ }
1841
+ const kept = {};
1842
+ for (const [k, v] of Object.entries(entry)) {
1843
+ if (k === "type" || k === "action") continue;
1844
+ kept[snake(k)] = convertGeneric(v, ctx);
1845
+ }
1846
+ if (type) kept.type = type;
1847
+ if (Object.keys(inner).length > 0) kept.action = convertGeneric(inner, ctx);
1848
+ onWarn?.({
1849
+ code: "unknown_action_type",
1850
+ ownerTag,
1851
+ message: `\u672A\u8BC6\u522B\u7684 action type: ${type ?? "(\u65E0 type)"}\uFF1B\u5DF2\u6309\u539F\u6837\u4FDD\u7559\u8FDB behaviors\uFF0C\u53D1\u5F80\u98DE\u4E66\u524D\u8BF7\u6E05\u6D17\u3002`
1852
+ });
1853
+ return kept;
1854
+ }
1789
1855
  function convertProperty(property, ownerTag, ctx) {
1790
1856
  const out = {};
1791
1857
  const siblings = {
@@ -1923,19 +1989,7 @@ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1923
1989
  }
1924
1990
  if (ownerTag === "button" && key === "action_type") return DROP;
1925
1991
  if (ownerTag === "button" && key === "actions" && Array.isArray(rawValue)) {
1926
- const behaviors = [];
1927
- for (const a of rawValue) {
1928
- if (!isRecord3(a)) continue;
1929
- const inner = isRecord3(a.action) ? a.action : {};
1930
- const t = typeof a.type === "string" ? a.type : void 0;
1931
- if (t === "callback") {
1932
- behaviors.push({ type: "callback", value: isRecord3(inner.value) ? inner.value : {} });
1933
- continue;
1934
- }
1935
- if (t !== "open_url") continue;
1936
- const fields = projectOpenUrlFields(inner);
1937
- if (fields) behaviors.push({ type: "open_url", ...fields });
1938
- }
1992
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx)).filter((b) => b !== void 0);
1939
1993
  if (behaviors.length === 0) return DROP;
1940
1994
  if (siblings.buttonActionType === "link" && behaviors.length === 1 && behaviors[0]?.type === "open_url") {
1941
1995
  const only = behaviors[0];
@@ -1945,29 +1999,23 @@ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1945
1999
  return ["behaviors", behaviors];
1946
2000
  }
1947
2001
  if (ownerTag === "interactive_container" && key === "actions" && Array.isArray(rawValue)) {
1948
- const behaviors = rawValue.map((a) => {
1949
- if (!isRecord3(a)) return void 0;
1950
- const inner = isRecord3(a.action) ? a.action : {};
1951
- const t = typeof a.type === "string" ? a.type : void 0;
1952
- if (t === "open_url") {
1953
- const fields = projectOpenUrlFields(inner);
1954
- return fields ? { type: "open_url", ...fields } : void 0;
1955
- }
1956
- if (t === "callback") {
1957
- return { type: "callback", value: inner.value ?? {} };
1958
- }
1959
- return void 0;
1960
- }).filter((b) => b !== void 0);
2002
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx)).filter((b) => b !== void 0);
1961
2003
  if (behaviors.length === 0) return DROP;
1962
2004
  return ["behaviors", behaviors];
1963
2005
  }
1964
2006
  if ((ownerTag === "column_set" || ownerTag === "column") && key === "actions" && Array.isArray(rawValue)) {
1965
- for (const a of rawValue) {
1966
- if (!isRecord3(a)) continue;
1967
- if (a.type !== "open_url") continue;
1968
- const inner = isRecord3(a.action) ? a.action : {};
1969
- const fields = projectOpenUrlFields(inner);
1970
- if (!fields) continue;
2007
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx, null)).filter((b) => b !== void 0);
2008
+ const openUrl = behaviors.find((b) => b.type === "open_url");
2009
+ const unrepresentable = behaviors.filter((b) => b.type !== "open_url");
2010
+ if (unrepresentable.length > 0) {
2011
+ ctx.onWarn?.({
2012
+ code: "unrepresentable_action",
2013
+ ownerTag,
2014
+ message: `${ownerTag} \u7684 actions \u542B ${unrepresentable.map((b) => String(b.type)).join(" / ")}\uFF0C\u516C\u5F00 DSL \u7684 ColumnAction \u53EA\u6709 multi_url\uFF0C\u65E0\u6CD5\u6295\u5F71\uFF1B\u8BE5 action \u4E0D\u4F1A\u51FA\u73B0\u5728 DSL \u91CC\u3002`
2015
+ });
2016
+ }
2017
+ if (openUrl) {
2018
+ const { type: _t, ...fields } = openUrl;
1971
2019
  return ["action", { multi_url: openUrlFieldsToMultiUrl(fields) }];
1972
2020
  }
1973
2021
  return DROP;
@@ -2449,11 +2497,11 @@ function pixelOf(side) {
2449
2497
  function snake(key) {
2450
2498
  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();
2451
2499
  }
2452
- function rawToDsl(raw) {
2453
- if (isRawCardEnvelope(raw)) return rawCardToDsl(raw);
2500
+ function rawToDsl(raw, options) {
2501
+ if (isRawCardEnvelope(raw)) return rawCardToDsl(raw, options);
2454
2502
  if (typeof raw === "string") {
2455
2503
  try {
2456
- return rawToDsl(JSON.parse(raw));
2504
+ return rawToDsl(JSON.parse(raw), options);
2457
2505
  } catch {
2458
2506
  return raw;
2459
2507
  }
@@ -2471,19 +2519,20 @@ function rawToDsl(raw) {
2471
2519
  const attachment = isRecord3(raw.json_attachment) ? raw.json_attachment : {};
2472
2520
  const ctx = makeCtx({
2473
2521
  images: isRecord3(attachment.images) ? attachment.images : {},
2474
- atUsers: isRecord3(attachment.at_users) ? attachment.at_users : {}
2522
+ atUsers: isRecord3(attachment.at_users) ? attachment.at_users : {},
2523
+ onWarn: options?.onWarn
2475
2524
  });
2476
2525
  return convertRoot(inner, ctx);
2477
2526
  }
2478
2527
  if (isRecord3(raw.card)) {
2479
2528
  const card = raw.card;
2480
2529
  if (typeof card.json_card === "string" || isRecord3(card.json_card)) {
2481
- return rawToDsl(card);
2530
+ return rawToDsl(card, options);
2482
2531
  }
2483
- return convertRoot(card, makeCtx());
2532
+ return convertRoot(card, makeCtx({ onWarn: options?.onWarn }));
2484
2533
  }
2485
2534
  if ("body" in raw || "header" in raw || "schema" in raw) {
2486
- return convertRoot(raw, makeCtx());
2535
+ return convertRoot(raw, makeCtx({ onWarn: options?.onWarn }));
2487
2536
  }
2488
2537
  return raw;
2489
2538
  }
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
  export { dslToRaw } from './dsl-to-raw.js';
10
10
  export { rawToDsl, rawCardToDsl, isRawCardEnvelope, degradeUnknown, } from './raw-to-dsl.js';
11
+ export type { RawToDslOptions, RawToDslWarning } from './raw-to-dsl.js';
11
12
  export { normalizeStoredCardColors, createColorRegistry, registerColorEntry } from './normalize-stored-colors.js';
12
13
  export type { CustomColorEntry } from './normalize-stored-colors.js';
13
14
  export { readThemeColorLight, readThemeColorDark, resolveCustomBackgroundLight, resolveCustomBorderLight, resolveTextStyleColor, resolveTextStyleCustomColorPair, themePairToColorEntry, isCustomHexColor, } from './custom-colors.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClH,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,4BAA4B,EAC5B,wBAAwB,EACxB,qBAAqB,EACrB,+BAA+B,EAC/B,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClH,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,4BAA4B,EAC5B,wBAAwB,EACxB,qBAAqB,EACrB,+BAA+B,EAC/B,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -816,10 +816,12 @@ function convertEl(el, colors) {
816
816
  }
817
817
  }
818
818
  if (tag === "video") {
819
- const cover = el.image_key ?? el.cover_image_key ?? el.coverImageID;
819
+ const nestedCover = isRecord2(el.cover) ? el.cover.img_key : void 0;
820
+ const cover = nestedCover ?? el.image_key ?? el.cover_image_key ?? el.coverImageID;
820
821
  if (typeof cover === "string" && cover) property.coverImageID = cover;
821
822
  delete property.image_key;
822
823
  delete property.cover_image_key;
824
+ delete property.cover;
823
825
  }
824
826
  return withRawElementId(el, { tag, property });
825
827
  }
@@ -1011,14 +1013,15 @@ function behaviorsToActions(behaviors) {
1011
1013
  for (const b of behaviors) {
1012
1014
  if (!isRecord2(b)) continue;
1013
1015
  const type = typeof b.type === "string" ? b.type : "open_url";
1014
- if (type === "callback") {
1015
- out.push({ type: "callback", action: { value: isRecord2(b.value) ? b.value : {} } });
1016
+ if (type === "callback" || type === "action_request") {
1017
+ const value = isRecord2(b.value) || typeof b.value === "string" ? b.value : {};
1018
+ out.push({ type: "action_request", action: { value } });
1016
1019
  continue;
1017
1020
  }
1018
1021
  const action = { url: normalizeUrl(b.default_url) };
1019
- if (typeof b.pc_url === "string" && b.pc_url) action.pcURL = b.pc_url;
1020
- if (typeof b.ios_url === "string" && b.ios_url) action.iosURL = b.ios_url;
1021
- if (typeof b.android_url === "string" && b.android_url) action.androidURL = b.android_url;
1022
+ if (typeof b.pc_url === "string" && b.pc_url) action.pc_url = b.pc_url;
1023
+ if (typeof b.ios_url === "string" && b.ios_url) action.ios_url = b.ios_url;
1024
+ if (typeof b.android_url === "string" && b.android_url) action.android_url = b.android_url;
1022
1025
  out.push({ type, action });
1023
1026
  }
1024
1027
  return out;
@@ -1026,10 +1029,10 @@ function behaviorsToActions(behaviors) {
1026
1029
  function multiUrlToOpenUrlAction(multi) {
1027
1030
  const url = typeof multi.url === "string" ? multi.url : typeof multi.default_url === "string" ? multi.default_url : "";
1028
1031
  const action = { url };
1029
- if (typeof multi.pc_url === "string" && multi.pc_url) action.pcURL = multi.pc_url;
1030
- if (typeof multi.ios_url === "string" && multi.ios_url) action.iosURL = multi.ios_url;
1032
+ if (typeof multi.pc_url === "string" && multi.pc_url) action.pc_url = multi.pc_url;
1033
+ if (typeof multi.ios_url === "string" && multi.ios_url) action.ios_url = multi.ios_url;
1031
1034
  if (typeof multi.android_url === "string" && multi.android_url) {
1032
- action.androidURL = multi.android_url;
1035
+ action.android_url = multi.android_url;
1033
1036
  }
1034
1037
  return { type: "open_url", action };
1035
1038
  }
@@ -1341,7 +1344,7 @@ function isRawCardEnvelope(value) {
1341
1344
  const v = value;
1342
1345
  return typeof v.json_card === "string" && typeof v.card_schema === "number";
1343
1346
  }
1344
- function rawCardToDsl(envelope) {
1347
+ function rawCardToDsl(envelope, options) {
1345
1348
  if (!isRawCardEnvelope(envelope)) return envelope;
1346
1349
  const env = envelope;
1347
1350
  let inner;
@@ -1352,7 +1355,8 @@ function rawCardToDsl(envelope) {
1352
1355
  }
1353
1356
  const ctx = makeCtx({
1354
1357
  images: env.json_attachment?.images ?? {},
1355
- atUsers: env.json_attachment?.at_users ?? {}
1358
+ atUsers: env.json_attachment?.at_users ?? {},
1359
+ onWarn: options?.onWarn
1356
1360
  });
1357
1361
  return convertRoot(inner, ctx);
1358
1362
  }
@@ -1360,7 +1364,8 @@ function makeCtx(partial) {
1360
1364
  return {
1361
1365
  images: partial?.images ?? {},
1362
1366
  atUsers: partial?.atUsers ?? {},
1363
- colors: createColorRegistry()
1367
+ colors: createColorRegistry(),
1368
+ onWarn: partial?.onWarn
1364
1369
  };
1365
1370
  }
1366
1371
  var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
@@ -1784,6 +1789,67 @@ function openUrlFieldsToMultiUrl(fields) {
1784
1789
  if (fields.android_url) multi.android_url = fields.android_url;
1785
1790
  return multi;
1786
1791
  }
1792
+ var RAW_CALLBACK_TYPES = /* @__PURE__ */ new Set(["callback", "action_request"]);
1793
+ function callbackValueFromRaw(inner, ownerTag, warn) {
1794
+ const v = inner.value;
1795
+ if (isRecord3(v)) return v;
1796
+ if (typeof v === "string" && v.length > 0) return v;
1797
+ const hadObjectValue = inner.isValueObjectType === true;
1798
+ const hasActionId = typeof inner.actionID === "string" && inner.actionID.length > 0;
1799
+ if (hadObjectValue) {
1800
+ warn?.({
1801
+ code: "callback_payload_cleared",
1802
+ ownerTag,
1803
+ message: `\u670D\u52A1\u7AEF\u628A\u56DE\u8C03\u5BF9\u8C61\u8F7D\u8377\u6362\u6210\u4E86 actionID \u5F15\u7528${hasActionId ? ` (${String(inner.actionID)})` : ""}\uFF0C\u6295\u5F71\u51FA\u7684 behaviors.value \u662F\u7A7A\u5BF9\u8C61\uFF0C\u70B9\u51FB\u56DE\u8C03\u62FF\u4E0D\u5230\u539F\u59CB\u53C2\u6570\u3002\u4FDD\u4F4F\u53C2\u6570\u9700\u53D1\u9001\u4FA7\u7559\u5B58\u539F\u59CB DSL\u3002`
1804
+ });
1805
+ return {};
1806
+ }
1807
+ if (hasActionId) {
1808
+ warn?.({
1809
+ code: "callback_payload_cleared",
1810
+ ownerTag,
1811
+ message: `\u670D\u52A1\u7AEF\u628A\u56DE\u8C03\u8F7D\u8377\u6362\u6210\u4E86 actionID \u5F15\u7528 (${String(inner.actionID)})\uFF0C\u539F\u503C\u5DF2\u4E0D\u53EF\u89C1\uFF1B\u6295\u5F71\u51FA\u7684 behaviors.value \u4FDD\u7559\u7A7A\u5B57\u7B26\u4E32\uFF0C\u70B9\u51FB\u56DE\u8C03\u62FF\u4E0D\u5230\u539F\u59CB\u53C2\u6570\u3002`
1812
+ });
1813
+ return "";
1814
+ }
1815
+ if (v === void 0) {
1816
+ warn?.({
1817
+ code: "callback_payload_cleared",
1818
+ ownerTag,
1819
+ message: "\u56DE\u8C03 action \u6CA1\u6709 value\uFF08\u670D\u52A1\u7AEF\u56DE\u8BFB\u5F62\u6001\uFF09\uFF0C\u6295\u5F71\u4E3A behaviors.value: {}\uFF1B\u70B9\u51FB\u56DE\u8C03\u62FF\u4E0D\u5230\u539F\u59CB\u53C2\u6570\uFF0C\u9700\u53D1\u9001\u4FA7\u7559\u5B58\u539F\u59CB DSL\u3002"
1820
+ });
1821
+ return {};
1822
+ }
1823
+ return "";
1824
+ }
1825
+ function actionEntryToBehavior(entry, ownerTag, ctx, onWarn = ctx.onWarn) {
1826
+ if (!isRecord3(entry)) return void 0;
1827
+ const inner = isRecord3(entry.action) ? entry.action : {};
1828
+ const type = typeof entry.type === "string" ? entry.type : void 0;
1829
+ if (type === "open_url") {
1830
+ const fields = projectOpenUrlFields(inner);
1831
+ return fields ? { type: "open_url", ...fields } : void 0;
1832
+ }
1833
+ if (type && RAW_CALLBACK_TYPES.has(type)) {
1834
+ return {
1835
+ type: "callback",
1836
+ value: callbackValueFromRaw(inner, ownerTag, onWarn ?? void 0)
1837
+ };
1838
+ }
1839
+ const kept = {};
1840
+ for (const [k, v] of Object.entries(entry)) {
1841
+ if (k === "type" || k === "action") continue;
1842
+ kept[snake(k)] = convertGeneric(v, ctx);
1843
+ }
1844
+ if (type) kept.type = type;
1845
+ if (Object.keys(inner).length > 0) kept.action = convertGeneric(inner, ctx);
1846
+ onWarn?.({
1847
+ code: "unknown_action_type",
1848
+ ownerTag,
1849
+ message: `\u672A\u8BC6\u522B\u7684 action type: ${type ?? "(\u65E0 type)"}\uFF1B\u5DF2\u6309\u539F\u6837\u4FDD\u7559\u8FDB behaviors\uFF0C\u53D1\u5F80\u98DE\u4E66\u524D\u8BF7\u6E05\u6D17\u3002`
1850
+ });
1851
+ return kept;
1852
+ }
1787
1853
  function convertProperty(property, ownerTag, ctx) {
1788
1854
  const out = {};
1789
1855
  const siblings = {
@@ -1921,19 +1987,7 @@ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1921
1987
  }
1922
1988
  if (ownerTag === "button" && key === "action_type") return DROP;
1923
1989
  if (ownerTag === "button" && key === "actions" && Array.isArray(rawValue)) {
1924
- const behaviors = [];
1925
- for (const a of rawValue) {
1926
- if (!isRecord3(a)) continue;
1927
- const inner = isRecord3(a.action) ? a.action : {};
1928
- const t = typeof a.type === "string" ? a.type : void 0;
1929
- if (t === "callback") {
1930
- behaviors.push({ type: "callback", value: isRecord3(inner.value) ? inner.value : {} });
1931
- continue;
1932
- }
1933
- if (t !== "open_url") continue;
1934
- const fields = projectOpenUrlFields(inner);
1935
- if (fields) behaviors.push({ type: "open_url", ...fields });
1936
- }
1990
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx)).filter((b) => b !== void 0);
1937
1991
  if (behaviors.length === 0) return DROP;
1938
1992
  if (siblings.buttonActionType === "link" && behaviors.length === 1 && behaviors[0]?.type === "open_url") {
1939
1993
  const only = behaviors[0];
@@ -1943,29 +1997,23 @@ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1943
1997
  return ["behaviors", behaviors];
1944
1998
  }
1945
1999
  if (ownerTag === "interactive_container" && key === "actions" && Array.isArray(rawValue)) {
1946
- const behaviors = rawValue.map((a) => {
1947
- if (!isRecord3(a)) return void 0;
1948
- const inner = isRecord3(a.action) ? a.action : {};
1949
- const t = typeof a.type === "string" ? a.type : void 0;
1950
- if (t === "open_url") {
1951
- const fields = projectOpenUrlFields(inner);
1952
- return fields ? { type: "open_url", ...fields } : void 0;
1953
- }
1954
- if (t === "callback") {
1955
- return { type: "callback", value: inner.value ?? {} };
1956
- }
1957
- return void 0;
1958
- }).filter((b) => b !== void 0);
2000
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx)).filter((b) => b !== void 0);
1959
2001
  if (behaviors.length === 0) return DROP;
1960
2002
  return ["behaviors", behaviors];
1961
2003
  }
1962
2004
  if ((ownerTag === "column_set" || ownerTag === "column") && key === "actions" && Array.isArray(rawValue)) {
1963
- for (const a of rawValue) {
1964
- if (!isRecord3(a)) continue;
1965
- if (a.type !== "open_url") continue;
1966
- const inner = isRecord3(a.action) ? a.action : {};
1967
- const fields = projectOpenUrlFields(inner);
1968
- if (!fields) continue;
2005
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx, null)).filter((b) => b !== void 0);
2006
+ const openUrl = behaviors.find((b) => b.type === "open_url");
2007
+ const unrepresentable = behaviors.filter((b) => b.type !== "open_url");
2008
+ if (unrepresentable.length > 0) {
2009
+ ctx.onWarn?.({
2010
+ code: "unrepresentable_action",
2011
+ ownerTag,
2012
+ message: `${ownerTag} \u7684 actions \u542B ${unrepresentable.map((b) => String(b.type)).join(" / ")}\uFF0C\u516C\u5F00 DSL \u7684 ColumnAction \u53EA\u6709 multi_url\uFF0C\u65E0\u6CD5\u6295\u5F71\uFF1B\u8BE5 action \u4E0D\u4F1A\u51FA\u73B0\u5728 DSL \u91CC\u3002`
2013
+ });
2014
+ }
2015
+ if (openUrl) {
2016
+ const { type: _t, ...fields } = openUrl;
1969
2017
  return ["action", { multi_url: openUrlFieldsToMultiUrl(fields) }];
1970
2018
  }
1971
2019
  return DROP;
@@ -2447,11 +2495,11 @@ function pixelOf(side) {
2447
2495
  function snake(key) {
2448
2496
  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();
2449
2497
  }
2450
- function rawToDsl(raw) {
2451
- if (isRawCardEnvelope(raw)) return rawCardToDsl(raw);
2498
+ function rawToDsl(raw, options) {
2499
+ if (isRawCardEnvelope(raw)) return rawCardToDsl(raw, options);
2452
2500
  if (typeof raw === "string") {
2453
2501
  try {
2454
- return rawToDsl(JSON.parse(raw));
2502
+ return rawToDsl(JSON.parse(raw), options);
2455
2503
  } catch {
2456
2504
  return raw;
2457
2505
  }
@@ -2469,19 +2517,20 @@ function rawToDsl(raw) {
2469
2517
  const attachment = isRecord3(raw.json_attachment) ? raw.json_attachment : {};
2470
2518
  const ctx = makeCtx({
2471
2519
  images: isRecord3(attachment.images) ? attachment.images : {},
2472
- atUsers: isRecord3(attachment.at_users) ? attachment.at_users : {}
2520
+ atUsers: isRecord3(attachment.at_users) ? attachment.at_users : {},
2521
+ onWarn: options?.onWarn
2473
2522
  });
2474
2523
  return convertRoot(inner, ctx);
2475
2524
  }
2476
2525
  if (isRecord3(raw.card)) {
2477
2526
  const card = raw.card;
2478
2527
  if (typeof card.json_card === "string" || isRecord3(card.json_card)) {
2479
- return rawToDsl(card);
2528
+ return rawToDsl(card, options);
2480
2529
  }
2481
- return convertRoot(card, makeCtx());
2530
+ return convertRoot(card, makeCtx({ onWarn: options?.onWarn }));
2482
2531
  }
2483
2532
  if ("body" in raw || "header" in raw || "schema" in raw) {
2484
- return convertRoot(raw, makeCtx());
2533
+ return convertRoot(raw, makeCtx({ onWarn: options?.onWarn }));
2485
2534
  }
2486
2535
  return raw;
2487
2536
  }
@@ -45,14 +45,31 @@
45
45
  * downstream sees an unknown field rather than silently losing data.
46
46
  */
47
47
  export declare function isRawCardEnvelope(value: unknown): boolean;
48
- export declare function rawCardToDsl(envelope: unknown): unknown;
48
+ export declare function rawCardToDsl(envelope: unknown, options?: RawToDslOptions): unknown;
49
+ /** rawToDsl 的诊断级别。默认静默:只有传了 onWarn 才会回调。 */
50
+ export interface RawToDslWarning {
51
+ /**
52
+ * unknown_action_type: action.type 不认识(已原样保留进 behaviors)
53
+ * callback_payload_cleared: 服务端把回调载荷换成了 actionID / 清空
54
+ * unrepresentable_action: 该宿主(column_set / column)的公开 schema 没有可放 action 的字段
55
+ */
56
+ code: "unknown_action_type" | "callback_payload_cleared" | "unrepresentable_action";
57
+ /** 可读诊断,中文 */
58
+ message: string;
59
+ /** 持有 actions 的元素 tag:button / interactive_container / column_set / column */
60
+ ownerTag?: string;
61
+ }
62
+ /** 诊断通道;不传则完全不打扰调用方。 */
63
+ export interface RawToDslOptions {
64
+ onWarn?: (warning: RawToDslWarning) => void;
65
+ }
49
66
  /**
50
67
  * Flexible entry used by this monorepo:
51
68
  * - raw envelope (`json_card` + `card_schema`) → same as `rawCardToDsl`
52
69
  * - already-parsed `{ json_card: object, json_attachment? }`
53
70
  * - `{ card: ... }` / bare card (e.g. output of `dslToRaw`) → `convertRoot`
54
71
  */
55
- export declare function rawToDsl(raw: unknown): unknown;
72
+ export declare function rawToDsl(raw: unknown, options?: RawToDslOptions): unknown;
56
73
  /**
57
74
  * Optional helper: wrap a private/unknown element as
58
75
  * `{ tag: 'unknown', raw_tag, fallback }`.
@@ -1 +1 @@
1
- {"version":3,"file":"raw-to-dsl.d.ts","sourceRoot":"","sources":["../src/raw-to-dsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAwBH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIzD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAcvD;AA2vDD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CA8C9C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGnF"}
1
+ {"version":3,"file":"raw-to-dsl.d.ts","sourceRoot":"","sources":["../src/raw-to-dsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAwBH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIzD;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,OAAO,EACjB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAeT;AAED,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,IAAI,EAAE,qBAAqB,GAAG,0BAA0B,GAAG,wBAAwB,CAAC;IACpF,cAAc;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAwB;AACxB,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;CAC7C;AA+2DD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CA+CzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGnF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/adapter",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
4
4
  "description": "公开 DSL 与飞书内部 RAW JSON 的双向适配器;raw→DSL 对齐 feishu-raw-card-to-dsl",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -22,7 +22,7 @@
22
22
  "tsx": "^4.19.3",
23
23
  "typescript": "^7.0.2",
24
24
  "vitest": "^2.1.9",
25
- "@open-feishu-card/schema": "0.1.7"
25
+ "@open-feishu-card/schema": "0.1.10"
26
26
  },
27
27
  "license": "MIT",
28
28
  "publishConfig": {