@open-feishu-card/adapter 0.3.18 → 0.3.19

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
@@ -1013,14 +1013,15 @@ function behaviorsToActions(behaviors) {
1013
1013
  for (const b of behaviors) {
1014
1014
  if (!isRecord2(b)) continue;
1015
1015
  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 : {} } });
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 } });
1018
1019
  continue;
1019
1020
  }
1020
1021
  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;
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;
1024
1025
  out.push({ type, action });
1025
1026
  }
1026
1027
  return out;
@@ -1028,10 +1029,10 @@ function behaviorsToActions(behaviors) {
1028
1029
  function multiUrlToOpenUrlAction(multi) {
1029
1030
  const url = typeof multi.url === "string" ? multi.url : typeof multi.default_url === "string" ? multi.default_url : "";
1030
1031
  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;
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;
1033
1034
  if (typeof multi.android_url === "string" && multi.android_url) {
1034
- action.androidURL = multi.android_url;
1035
+ action.android_url = multi.android_url;
1035
1036
  }
1036
1037
  return { type: "open_url", action };
1037
1038
  }
@@ -1343,7 +1344,7 @@ function isRawCardEnvelope(value) {
1343
1344
  const v = value;
1344
1345
  return typeof v.json_card === "string" && typeof v.card_schema === "number";
1345
1346
  }
1346
- function rawCardToDsl(envelope) {
1347
+ function rawCardToDsl(envelope, options) {
1347
1348
  if (!isRawCardEnvelope(envelope)) return envelope;
1348
1349
  const env = envelope;
1349
1350
  let inner;
@@ -1354,7 +1355,8 @@ function rawCardToDsl(envelope) {
1354
1355
  }
1355
1356
  const ctx = makeCtx({
1356
1357
  images: env.json_attachment?.images ?? {},
1357
- atUsers: env.json_attachment?.at_users ?? {}
1358
+ atUsers: env.json_attachment?.at_users ?? {},
1359
+ onWarn: options?.onWarn
1358
1360
  });
1359
1361
  return convertRoot(inner, ctx);
1360
1362
  }
@@ -1362,7 +1364,8 @@ function makeCtx(partial) {
1362
1364
  return {
1363
1365
  images: partial?.images ?? {},
1364
1366
  atUsers: partial?.atUsers ?? {},
1365
- colors: createColorRegistry()
1367
+ colors: createColorRegistry(),
1368
+ onWarn: partial?.onWarn
1366
1369
  };
1367
1370
  }
1368
1371
  var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
@@ -1786,6 +1789,67 @@ function openUrlFieldsToMultiUrl(fields) {
1786
1789
  if (fields.android_url) multi.android_url = fields.android_url;
1787
1790
  return multi;
1788
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
+ }
1789
1853
  function convertProperty(property, ownerTag, ctx) {
1790
1854
  const out = {};
1791
1855
  const siblings = {
@@ -1923,19 +1987,7 @@ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1923
1987
  }
1924
1988
  if (ownerTag === "button" && key === "action_type") return DROP;
1925
1989
  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
- }
1990
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx)).filter((b) => b !== void 0);
1939
1991
  if (behaviors.length === 0) return DROP;
1940
1992
  if (siblings.buttonActionType === "link" && behaviors.length === 1 && behaviors[0]?.type === "open_url") {
1941
1993
  const only = behaviors[0];
@@ -1945,29 +1997,23 @@ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1945
1997
  return ["behaviors", behaviors];
1946
1998
  }
1947
1999
  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);
2000
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx)).filter((b) => b !== void 0);
1961
2001
  if (behaviors.length === 0) return DROP;
1962
2002
  return ["behaviors", behaviors];
1963
2003
  }
1964
2004
  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;
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;
1971
2017
  return ["action", { multi_url: openUrlFieldsToMultiUrl(fields) }];
1972
2018
  }
1973
2019
  return DROP;
@@ -2449,11 +2495,11 @@ function pixelOf(side) {
2449
2495
  function snake(key) {
2450
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();
2451
2497
  }
2452
- function rawToDsl(raw) {
2453
- if (isRawCardEnvelope(raw)) return rawCardToDsl(raw);
2498
+ function rawToDsl(raw, options) {
2499
+ if (isRawCardEnvelope(raw)) return rawCardToDsl(raw, options);
2454
2500
  if (typeof raw === "string") {
2455
2501
  try {
2456
- return rawToDsl(JSON.parse(raw));
2502
+ return rawToDsl(JSON.parse(raw), options);
2457
2503
  } catch {
2458
2504
  return raw;
2459
2505
  }
@@ -2471,19 +2517,20 @@ function rawToDsl(raw) {
2471
2517
  const attachment = isRecord3(raw.json_attachment) ? raw.json_attachment : {};
2472
2518
  const ctx = makeCtx({
2473
2519
  images: isRecord3(attachment.images) ? attachment.images : {},
2474
- atUsers: isRecord3(attachment.at_users) ? attachment.at_users : {}
2520
+ atUsers: isRecord3(attachment.at_users) ? attachment.at_users : {},
2521
+ onWarn: options?.onWarn
2475
2522
  });
2476
2523
  return convertRoot(inner, ctx);
2477
2524
  }
2478
2525
  if (isRecord3(raw.card)) {
2479
2526
  const card = raw.card;
2480
2527
  if (typeof card.json_card === "string" || isRecord3(card.json_card)) {
2481
- return rawToDsl(card);
2528
+ return rawToDsl(card, options);
2482
2529
  }
2483
- return convertRoot(card, makeCtx());
2530
+ return convertRoot(card, makeCtx({ onWarn: options?.onWarn }));
2484
2531
  }
2485
2532
  if ("body" in raw || "header" in raw || "schema" in raw) {
2486
- return convertRoot(raw, makeCtx());
2533
+ return convertRoot(raw, makeCtx({ onWarn: options?.onWarn }));
2487
2534
  }
2488
2535
  return raw;
2489
2536
  }
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
@@ -1011,14 +1011,15 @@ function behaviorsToActions(behaviors) {
1011
1011
  for (const b of behaviors) {
1012
1012
  if (!isRecord2(b)) continue;
1013
1013
  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 : {} } });
1014
+ if (type === "callback" || type === "action_request") {
1015
+ const value = isRecord2(b.value) || typeof b.value === "string" ? b.value : {};
1016
+ out.push({ type: "action_request", action: { value } });
1016
1017
  continue;
1017
1018
  }
1018
1019
  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;
1020
+ if (typeof b.pc_url === "string" && b.pc_url) action.pc_url = b.pc_url;
1021
+ if (typeof b.ios_url === "string" && b.ios_url) action.ios_url = b.ios_url;
1022
+ if (typeof b.android_url === "string" && b.android_url) action.android_url = b.android_url;
1022
1023
  out.push({ type, action });
1023
1024
  }
1024
1025
  return out;
@@ -1026,10 +1027,10 @@ function behaviorsToActions(behaviors) {
1026
1027
  function multiUrlToOpenUrlAction(multi) {
1027
1028
  const url = typeof multi.url === "string" ? multi.url : typeof multi.default_url === "string" ? multi.default_url : "";
1028
1029
  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;
1030
+ if (typeof multi.pc_url === "string" && multi.pc_url) action.pc_url = multi.pc_url;
1031
+ if (typeof multi.ios_url === "string" && multi.ios_url) action.ios_url = multi.ios_url;
1031
1032
  if (typeof multi.android_url === "string" && multi.android_url) {
1032
- action.androidURL = multi.android_url;
1033
+ action.android_url = multi.android_url;
1033
1034
  }
1034
1035
  return { type: "open_url", action };
1035
1036
  }
@@ -1341,7 +1342,7 @@ function isRawCardEnvelope(value) {
1341
1342
  const v = value;
1342
1343
  return typeof v.json_card === "string" && typeof v.card_schema === "number";
1343
1344
  }
1344
- function rawCardToDsl(envelope) {
1345
+ function rawCardToDsl(envelope, options) {
1345
1346
  if (!isRawCardEnvelope(envelope)) return envelope;
1346
1347
  const env = envelope;
1347
1348
  let inner;
@@ -1352,7 +1353,8 @@ function rawCardToDsl(envelope) {
1352
1353
  }
1353
1354
  const ctx = makeCtx({
1354
1355
  images: env.json_attachment?.images ?? {},
1355
- atUsers: env.json_attachment?.at_users ?? {}
1356
+ atUsers: env.json_attachment?.at_users ?? {},
1357
+ onWarn: options?.onWarn
1356
1358
  });
1357
1359
  return convertRoot(inner, ctx);
1358
1360
  }
@@ -1360,7 +1362,8 @@ function makeCtx(partial) {
1360
1362
  return {
1361
1363
  images: partial?.images ?? {},
1362
1364
  atUsers: partial?.atUsers ?? {},
1363
- colors: createColorRegistry()
1365
+ colors: createColorRegistry(),
1366
+ onWarn: partial?.onWarn
1364
1367
  };
1365
1368
  }
1366
1369
  var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
@@ -1784,6 +1787,67 @@ function openUrlFieldsToMultiUrl(fields) {
1784
1787
  if (fields.android_url) multi.android_url = fields.android_url;
1785
1788
  return multi;
1786
1789
  }
1790
+ var RAW_CALLBACK_TYPES = /* @__PURE__ */ new Set(["callback", "action_request"]);
1791
+ function callbackValueFromRaw(inner, ownerTag, warn) {
1792
+ const v = inner.value;
1793
+ if (isRecord3(v)) return v;
1794
+ if (typeof v === "string" && v.length > 0) return v;
1795
+ const hadObjectValue = inner.isValueObjectType === true;
1796
+ const hasActionId = typeof inner.actionID === "string" && inner.actionID.length > 0;
1797
+ if (hadObjectValue) {
1798
+ warn?.({
1799
+ code: "callback_payload_cleared",
1800
+ ownerTag,
1801
+ 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`
1802
+ });
1803
+ return {};
1804
+ }
1805
+ if (hasActionId) {
1806
+ warn?.({
1807
+ code: "callback_payload_cleared",
1808
+ ownerTag,
1809
+ 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`
1810
+ });
1811
+ return "";
1812
+ }
1813
+ if (v === void 0) {
1814
+ warn?.({
1815
+ code: "callback_payload_cleared",
1816
+ ownerTag,
1817
+ 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"
1818
+ });
1819
+ return {};
1820
+ }
1821
+ return "";
1822
+ }
1823
+ function actionEntryToBehavior(entry, ownerTag, ctx, onWarn = ctx.onWarn) {
1824
+ if (!isRecord3(entry)) return void 0;
1825
+ const inner = isRecord3(entry.action) ? entry.action : {};
1826
+ const type = typeof entry.type === "string" ? entry.type : void 0;
1827
+ if (type === "open_url") {
1828
+ const fields = projectOpenUrlFields(inner);
1829
+ return fields ? { type: "open_url", ...fields } : void 0;
1830
+ }
1831
+ if (type && RAW_CALLBACK_TYPES.has(type)) {
1832
+ return {
1833
+ type: "callback",
1834
+ value: callbackValueFromRaw(inner, ownerTag, onWarn ?? void 0)
1835
+ };
1836
+ }
1837
+ const kept = {};
1838
+ for (const [k, v] of Object.entries(entry)) {
1839
+ if (k === "type" || k === "action") continue;
1840
+ kept[snake(k)] = convertGeneric(v, ctx);
1841
+ }
1842
+ if (type) kept.type = type;
1843
+ if (Object.keys(inner).length > 0) kept.action = convertGeneric(inner, ctx);
1844
+ onWarn?.({
1845
+ code: "unknown_action_type",
1846
+ ownerTag,
1847
+ 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`
1848
+ });
1849
+ return kept;
1850
+ }
1787
1851
  function convertProperty(property, ownerTag, ctx) {
1788
1852
  const out = {};
1789
1853
  const siblings = {
@@ -1921,19 +1985,7 @@ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1921
1985
  }
1922
1986
  if (ownerTag === "button" && key === "action_type") return DROP;
1923
1987
  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
- }
1988
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx)).filter((b) => b !== void 0);
1937
1989
  if (behaviors.length === 0) return DROP;
1938
1990
  if (siblings.buttonActionType === "link" && behaviors.length === 1 && behaviors[0]?.type === "open_url") {
1939
1991
  const only = behaviors[0];
@@ -1943,29 +1995,23 @@ function convertField(rawKey, rawValue, ownerTag, ctx, siblings = {}) {
1943
1995
  return ["behaviors", behaviors];
1944
1996
  }
1945
1997
  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);
1998
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx)).filter((b) => b !== void 0);
1959
1999
  if (behaviors.length === 0) return DROP;
1960
2000
  return ["behaviors", behaviors];
1961
2001
  }
1962
2002
  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;
2003
+ const behaviors = rawValue.map((a) => actionEntryToBehavior(a, ownerTag, ctx, null)).filter((b) => b !== void 0);
2004
+ const openUrl = behaviors.find((b) => b.type === "open_url");
2005
+ const unrepresentable = behaviors.filter((b) => b.type !== "open_url");
2006
+ if (unrepresentable.length > 0) {
2007
+ ctx.onWarn?.({
2008
+ code: "unrepresentable_action",
2009
+ ownerTag,
2010
+ 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`
2011
+ });
2012
+ }
2013
+ if (openUrl) {
2014
+ const { type: _t, ...fields } = openUrl;
1969
2015
  return ["action", { multi_url: openUrlFieldsToMultiUrl(fields) }];
1970
2016
  }
1971
2017
  return DROP;
@@ -2447,11 +2493,11 @@ function pixelOf(side) {
2447
2493
  function snake(key) {
2448
2494
  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
2495
  }
2450
- function rawToDsl(raw) {
2451
- if (isRawCardEnvelope(raw)) return rawCardToDsl(raw);
2496
+ function rawToDsl(raw, options) {
2497
+ if (isRawCardEnvelope(raw)) return rawCardToDsl(raw, options);
2452
2498
  if (typeof raw === "string") {
2453
2499
  try {
2454
- return rawToDsl(JSON.parse(raw));
2500
+ return rawToDsl(JSON.parse(raw), options);
2455
2501
  } catch {
2456
2502
  return raw;
2457
2503
  }
@@ -2469,19 +2515,20 @@ function rawToDsl(raw) {
2469
2515
  const attachment = isRecord3(raw.json_attachment) ? raw.json_attachment : {};
2470
2516
  const ctx = makeCtx({
2471
2517
  images: isRecord3(attachment.images) ? attachment.images : {},
2472
- atUsers: isRecord3(attachment.at_users) ? attachment.at_users : {}
2518
+ atUsers: isRecord3(attachment.at_users) ? attachment.at_users : {},
2519
+ onWarn: options?.onWarn
2473
2520
  });
2474
2521
  return convertRoot(inner, ctx);
2475
2522
  }
2476
2523
  if (isRecord3(raw.card)) {
2477
2524
  const card = raw.card;
2478
2525
  if (typeof card.json_card === "string" || isRecord3(card.json_card)) {
2479
- return rawToDsl(card);
2526
+ return rawToDsl(card, options);
2480
2527
  }
2481
- return convertRoot(card, makeCtx());
2528
+ return convertRoot(card, makeCtx({ onWarn: options?.onWarn }));
2482
2529
  }
2483
2530
  if ("body" in raw || "header" in raw || "schema" in raw) {
2484
- return convertRoot(raw, makeCtx());
2531
+ return convertRoot(raw, makeCtx({ onWarn: options?.onWarn }));
2485
2532
  }
2486
2533
  return raw;
2487
2534
  }
@@ -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.19",
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.9"
26
26
  },
27
27
  "license": "MIT",
28
28
  "publishConfig": {