@open-feishu-card/adapter 0.3.3 → 0.3.5

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.
@@ -3,11 +3,10 @@
3
3
  * customBackgroundStyle / custom_background_style → column backgrounds
4
4
  * textStyle.customColor / textStyle.custom_color → inline text color
5
5
  *
6
- * Public DSL uses config.style.color aliases (cus-N) for backgrounds after
7
- * normalizeStoredCardColors; inline text accepts enum names or #RRGGBBAA in
8
- * `<font color='…'>`. These helpers pick the light-mode value for projection
9
- * and rendering (dark mode follows Feishu client theme — out of scope here).
6
+ * Public DSL stores custom colors in config.style.color (cus-accent / cus-bg-N …)
7
+ * and references them via <font color> in markdown content / background_style — not inline #hex.
10
8
  */
9
+ import type { CustomColorEntry } from './normalize-stored-colors.js';
11
10
  export type ThemeColorPair = {
12
11
  light_mode?: string;
13
12
  lightMode?: string;
@@ -15,8 +14,13 @@ export type ThemeColorPair = {
15
14
  darkMode?: string;
16
15
  };
17
16
  export declare function readThemeColorLight(pair: unknown): string | undefined;
17
+ export declare function readThemeColorDark(pair: unknown): string | undefined;
18
+ export declare function isCustomHexColor(color: string): boolean;
18
19
  /** Enum `color` wins; else fall back to customColor light hex/rgba. */
19
20
  export declare function resolveTextStyleColor(textStyle: unknown): string | undefined;
21
+ /** When text color comes from customColor (not enum), return the full light/dark pair. */
22
+ export declare function resolveTextStyleCustomColorPair(textStyle: unknown): ThemeColorPair | undefined;
23
+ export declare function themePairToColorEntry(pair: ThemeColorPair): CustomColorEntry | undefined;
20
24
  /** Light hex/rgba from column (or container) custom background, if any. */
21
25
  export declare function resolveCustomBackgroundLight(property: unknown): string | undefined;
22
26
  //# sourceMappingURL=custom-colors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"custom-colors.d.ts","sourceRoot":"","sources":["../src/custom-colors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOrE;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAO5E;AAED,2EAA2E;AAC3E,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAKlF"}
1
+ {"version":3,"file":"custom-colors.d.ts","sourceRoot":"","sources":["../src/custom-colors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOrE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOpE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAO5E;AAED,0FAA0F;AAC1F,wBAAgB,+BAA+B,CAAC,SAAS,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAQ9F;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,GAAG,gBAAgB,GAAG,SAAS,CAQxF;AAED,2EAA2E;AAC3E,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAKlF"}
package/dist/index.cjs CHANGED
@@ -868,31 +868,22 @@ function isRecord(v) {
868
868
  return typeof v === "object" && v !== null && !Array.isArray(v);
869
869
  }
870
870
 
871
- // src/custom-colors.ts
872
- function readThemeColorLight(pair) {
873
- if (!pair || typeof pair !== "object") return void 0;
874
- const o = pair;
875
- const raw = o.light_mode ?? o.lightMode;
876
- if (typeof raw !== "string") return void 0;
877
- const v = raw.trim();
878
- return v || void 0;
871
+ // src/normalize-stored-colors.ts
872
+ function createColorRegistry() {
873
+ return { entries: {}, keyToAlias: /* @__PURE__ */ new Map(), bgSeq: 0, textSeq: 0 };
879
874
  }
880
- function resolveTextStyleColor(textStyle) {
881
- if (!textStyle || typeof textStyle !== "object") return void 0;
882
- const ts = textStyle;
883
- const enumColor = typeof ts.color === "string" ? ts.color.trim() : void 0;
884
- if (enumColor && enumColor !== "default") return enumColor;
885
- const custom = ts.custom_color ?? ts.customColor;
886
- return readThemeColorLight(custom);
875
+ function colorEntryKey(entry) {
876
+ return `${entry.light_mode}|${entry.dark_mode}`;
887
877
  }
888
- function resolveCustomBackgroundLight(property) {
889
- if (!property || typeof property !== "object") return void 0;
890
- const p = property;
891
- const custom = p.custom_background_style ?? p.customBackgroundStyle;
892
- return readThemeColorLight(custom);
878
+ function registerColorEntry(registry, entry, prefix) {
879
+ const key = colorEntryKey(entry);
880
+ const existing = registry.keyToAlias.get(key);
881
+ if (existing) return existing;
882
+ const alias = prefix === "cus-bg" ? `cus-bg-${registry.bgSeq++}` : `cus-text-${registry.textSeq++}`;
883
+ registry.entries[alias] = entry;
884
+ registry.keyToAlias.set(key, alias);
885
+ return alias;
893
886
  }
894
-
895
- // src/normalize-stored-colors.ts
896
887
  function hexColorToRgba(hex) {
897
888
  const h = hex.trim().replace(/^#/, "");
898
889
  if (h.length === 8) {
@@ -922,17 +913,17 @@ function readCustomBackgroundStyle(node) {
922
913
  if (!light || !dark) return void 0;
923
914
  return { light_mode: light, dark_mode: dark };
924
915
  }
925
- function normalizeStoredCardColors(dsl) {
926
- const colorEntries = {};
927
- const styleKey = (entry) => `${entry.light_mode}|${entry.dark_mode}`;
928
- const keyToAlias = /* @__PURE__ */ new Map();
929
- let bgSeq = 0;
916
+ function normalizeStoredCardColors(dsl, preRegistered) {
917
+ const colorEntries = { ...preRegistered?.entries ?? {} };
918
+ const keyToAlias = new Map(preRegistered?.keyToAlias ?? []);
919
+ let bgSeq = preRegistered?.bgSeq ?? 0;
920
+ let textSeq = preRegistered?.textSeq ?? 0;
930
921
  const ensureAlias = (prefix, entry) => {
931
- const existing2 = keyToAlias.get(styleKey(entry));
922
+ const existing2 = keyToAlias.get(colorEntryKey(entry));
932
923
  if (existing2) return existing2;
933
- const alias = `${prefix}-${bgSeq++}`;
924
+ const alias = prefix === "cus-bg" ? `cus-bg-${bgSeq++}` : `cus-text-${textSeq++}`;
934
925
  colorEntries[alias] = entry;
935
- keyToAlias.set(styleKey(entry), alias);
926
+ keyToAlias.set(colorEntryKey(entry), alias);
936
927
  return alias;
937
928
  };
938
929
  const walk = (node) => {
@@ -949,11 +940,15 @@ function normalizeStoredCardColors(dsl) {
949
940
  delete obj.custom_background_style;
950
941
  }
951
942
  }
943
+ if (obj.tag === "markdown" && typeof obj.content === "string") {
944
+ normalizeMarkdownInlineHexColors(obj, ensureAlias, !!preRegistered);
945
+ }
952
946
  for (const value of Object.values(obj)) walk(value);
953
947
  };
954
948
  walk(dsl.body);
955
949
  if (Object.keys(colorEntries).length === 0) return;
956
- const firstBg = colorEntries["cus-bg-0"] ?? Object.values(colorEntries)[0];
950
+ const firstBgKey = Object.keys(colorEntries).find((k) => k.startsWith("cus-bg-"));
951
+ const firstBg = firstBgKey ? colorEntries[firstBgKey] : void 0;
957
952
  if (firstBg) {
958
953
  const m = firstBg.light_mode.match(/^rgba\((\d+),(\d+),(\d+),/);
959
954
  if (m) {
@@ -967,6 +962,13 @@ function normalizeStoredCardColors(dsl) {
967
962
  light_mode: "rgba(100,106,115,1)",
968
963
  dark_mode: "rgba(166,166,166,1)"
969
964
  };
965
+ const aliasRenames = /* @__PURE__ */ new Map();
966
+ for (const to of ["cus-accent", "cus-muted"]) {
967
+ for (const [from, target] of unifyMatchingAlias(colorEntries, "cus-text", to)) {
968
+ aliasRenames.set(from, target);
969
+ }
970
+ }
971
+ remapColorReferences(dsl.body, aliasRenames);
970
972
  const config = dsl.config && typeof dsl.config === "object" ? dsl.config : {};
971
973
  dsl.config = config;
972
974
  const style = config.style && typeof config.style === "object" ? config.style : {};
@@ -974,22 +976,148 @@ function normalizeStoredCardColors(dsl) {
974
976
  const existing = style.color && typeof style.color === "object" ? style.color : {};
975
977
  style.color = { ...colorEntries, ...existing };
976
978
  if (!colorEntries["cus-accent"]) return;
977
- const applyDefaultFontColors = (node) => {
978
- if (Array.isArray(node)) {
979
- for (const child of node) applyDefaultFontColors(child);
979
+ if (!preRegistered) {
980
+ const applyDefaultFontColors = (node) => {
981
+ if (Array.isArray(node)) {
982
+ for (const child of node) applyDefaultFontColors(child);
983
+ return;
984
+ }
985
+ if (!node || typeof node !== "object") return;
986
+ const obj = node;
987
+ if (obj.tag === "markdown" && obj.font_color === void 0) {
988
+ const size = obj.text_size;
989
+ const content = typeof obj.content === "string" ? obj.content : "";
990
+ if (size === "small") obj.font_color = "cus-muted";
991
+ else if (size === "heading" || content.includes("**")) obj.font_color = "cus-accent";
992
+ }
993
+ for (const value of Object.values(obj)) applyDefaultFontColors(value);
994
+ };
995
+ applyDefaultFontColors(dsl.body);
996
+ }
997
+ }
998
+ function unifyMatchingAlias(entries, fromPrefix, to) {
999
+ const renames = /* @__PURE__ */ new Map();
1000
+ const target = entries[to];
1001
+ if (!target) return renames;
1002
+ for (const alias of Object.keys(entries)) {
1003
+ if (alias === to) continue;
1004
+ if (!alias.startsWith(`${fromPrefix}-`) && alias !== fromPrefix) continue;
1005
+ const entry = entries[alias];
1006
+ const matches = to === "cus-accent" || to === "cus-muted" ? entry.light_mode === target.light_mode : colorEntryKey(entry) === colorEntryKey(target);
1007
+ if (matches) {
1008
+ renames.set(alias, to);
1009
+ delete entries[alias];
1010
+ }
1011
+ }
1012
+ return renames;
1013
+ }
1014
+ function remapColorReferences(node, aliasRenames) {
1015
+ const remap = (alias) => {
1016
+ if (!alias) return alias;
1017
+ return aliasRenames.get(alias) ?? alias;
1018
+ };
1019
+ const walk = (n) => {
1020
+ if (Array.isArray(n)) {
1021
+ for (const c of n) walk(c);
980
1022
  return;
981
1023
  }
982
- if (!node || typeof node !== "object") return;
983
- const obj = node;
984
- if (obj.tag === "markdown" && obj.font_color === void 0) {
985
- const size = obj.text_size;
986
- const content = typeof obj.content === "string" ? obj.content : "";
987
- if (size === "small") obj.font_color = "cus-muted";
988
- else if (size === "heading" || content.includes("**")) obj.font_color = "cus-accent";
1024
+ if (!n || typeof n !== "object") return;
1025
+ const obj = n;
1026
+ if (typeof obj.font_color === "string") obj.font_color = remap(obj.font_color);
1027
+ if (typeof obj.content === "string" && obj.content.includes("<font")) {
1028
+ obj.content = obj.content.replace(
1029
+ /<font color=['"]([^'"]+)['"]>/g,
1030
+ (_m, color) => {
1031
+ const next = remap(color);
1032
+ return next ? `<font color='${next}'>` : _m;
1033
+ }
1034
+ );
989
1035
  }
990
- for (const value of Object.values(obj)) applyDefaultFontColors(value);
1036
+ for (const value of Object.values(obj)) walk(value);
991
1037
  };
992
- applyDefaultFontColors(dsl.body);
1038
+ walk(node);
1039
+ }
1040
+ function normalizeMarkdownInlineHexColors(obj, ensureAlias, keepFontTags = false) {
1041
+ const content = obj.content;
1042
+ const hexColor = (raw) => {
1043
+ const v = raw.trim();
1044
+ if (v.startsWith("#")) {
1045
+ const rgba = hexColorToRgba(v);
1046
+ return rgba ? { light_mode: rgba, dark_mode: rgba } : void 0;
1047
+ }
1048
+ if (/^rgba?\(/i.test(v)) return { light_mode: v, dark_mode: v };
1049
+ return void 0;
1050
+ };
1051
+ const single = content.match(/^<font color=['"]([^'"]+)['"]\s*>([\s\S]*)<\/font>$/);
1052
+ if (single && !keepFontTags) {
1053
+ const entry = hexColor(single[1]);
1054
+ if (entry) {
1055
+ const alias = ensureAlias("cus-text", entry);
1056
+ if (obj.font_color === void 0) obj.font_color = alias;
1057
+ obj.content = single[2];
1058
+ return;
1059
+ }
1060
+ }
1061
+ obj.content = content.replace(/<font color=['"]([^'"]+)['"]>/g, (match, raw) => {
1062
+ const entry = hexColor(raw);
1063
+ if (!entry) return match;
1064
+ const alias = ensureAlias("cus-text", entry);
1065
+ return `<font color='${alias}'>`;
1066
+ });
1067
+ }
1068
+
1069
+ // src/custom-colors.ts
1070
+ function readThemeColorLight(pair) {
1071
+ if (!pair || typeof pair !== "object") return void 0;
1072
+ const o = pair;
1073
+ const raw = o.light_mode ?? o.lightMode;
1074
+ if (typeof raw !== "string") return void 0;
1075
+ const v = raw.trim();
1076
+ return v || void 0;
1077
+ }
1078
+ function readThemeColorDark(pair) {
1079
+ if (!pair || typeof pair !== "object") return void 0;
1080
+ const o = pair;
1081
+ const raw = o.dark_mode ?? o.darkMode;
1082
+ if (typeof raw !== "string") return void 0;
1083
+ const v = raw.trim();
1084
+ return v || void 0;
1085
+ }
1086
+ function isCustomHexColor(color) {
1087
+ const v = color.trim();
1088
+ return v.startsWith("#") || /^rgba?\(/i.test(v);
1089
+ }
1090
+ function resolveTextStyleColor(textStyle) {
1091
+ if (!textStyle || typeof textStyle !== "object") return void 0;
1092
+ const ts = textStyle;
1093
+ const enumColor = typeof ts.color === "string" ? ts.color.trim() : void 0;
1094
+ if (enumColor && enumColor !== "default") return enumColor;
1095
+ const custom = ts.custom_color ?? ts.customColor;
1096
+ return readThemeColorLight(custom);
1097
+ }
1098
+ function resolveTextStyleCustomColorPair(textStyle) {
1099
+ if (!textStyle || typeof textStyle !== "object") return void 0;
1100
+ const ts = textStyle;
1101
+ const enumColor = typeof ts.color === "string" ? ts.color.trim() : void 0;
1102
+ if (enumColor && enumColor !== "default") return void 0;
1103
+ const custom = ts.custom_color ?? ts.customColor;
1104
+ if (!custom || typeof custom !== "object") return void 0;
1105
+ return custom;
1106
+ }
1107
+ function themePairToColorEntry(pair) {
1108
+ const lightRaw = readThemeColorLight(pair);
1109
+ const darkRaw = readThemeColorDark(pair);
1110
+ if (!lightRaw || !darkRaw) return void 0;
1111
+ const light = lightRaw.startsWith("#") ? hexColorToRgba(lightRaw) : lightRaw;
1112
+ const dark = darkRaw.startsWith("#") ? hexColorToRgba(darkRaw) : darkRaw;
1113
+ if (!light || !dark) return void 0;
1114
+ return { light_mode: light, dark_mode: dark };
1115
+ }
1116
+ function resolveCustomBackgroundLight(property) {
1117
+ if (!property || typeof property !== "object") return void 0;
1118
+ const p = property;
1119
+ const custom = p.custom_background_style ?? p.customBackgroundStyle;
1120
+ return readThemeColorLight(custom);
993
1121
  }
994
1122
 
995
1123
  // src/raw-to-dsl.ts
@@ -1007,12 +1135,19 @@ function rawCardToDsl(envelope) {
1007
1135
  } catch {
1008
1136
  return envelope;
1009
1137
  }
1010
- const ctx = {
1138
+ const ctx = makeCtx({
1011
1139
  images: env.json_attachment?.images ?? {},
1012
1140
  atUsers: env.json_attachment?.at_users ?? {}
1013
- };
1141
+ });
1014
1142
  return convertRoot(inner, ctx);
1015
1143
  }
1144
+ function makeCtx(partial) {
1145
+ return {
1146
+ images: partial?.images ?? {},
1147
+ atUsers: partial?.atUsers ?? {},
1148
+ colors: createColorRegistry()
1149
+ };
1150
+ }
1016
1151
  var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
1017
1152
  "new_body",
1018
1153
  "source",
@@ -1100,7 +1235,7 @@ function convertRoot(root, ctx) {
1100
1235
  out.config = convertConfig(isRecord2(root.config) ? root.config : {});
1101
1236
  if (isRecord2(root.body)) out.body = convertBody2(root.body, ctx);
1102
1237
  if (isRecord2(root.header)) out.header = convertHeader2(root.header, ctx);
1103
- normalizeStoredCardColors(out);
1238
+ normalizeStoredCardColors(out, ctx.colors);
1104
1239
  return out;
1105
1240
  }
1106
1241
  function convertConfig(_node) {
@@ -1202,7 +1337,7 @@ function unwrap(node, ctx) {
1202
1337
  let value = isRecord2(cell) && "data" in cell ? cell.data : cell;
1203
1338
  if (isRecord2(value) && value.tag === "markdown" && isRecord2(value.property)) {
1204
1339
  const elements = Array.isArray(value.property.elements) ? value.property.elements : [];
1205
- value = renderChildren(elements, ctx);
1340
+ value = renderChildren(elements, ctx).content;
1206
1341
  }
1207
1342
  r[k] = value;
1208
1343
  }
@@ -1577,7 +1712,7 @@ function resolveAtUserId(prop, ctx) {
1577
1712
  function rebuildMarkdown(property, ctx) {
1578
1713
  const out = { tag: "markdown" };
1579
1714
  const elements = Array.isArray(property.elements) ? property.elements : [];
1580
- const content = renderChildren(elements, ctx);
1715
+ const { content } = renderChildren(elements, ctx);
1581
1716
  if (content) out.content = content;
1582
1717
  for (const [rawKey, rawValue] of Object.entries(property)) {
1583
1718
  if (rawKey === "elements" || rawKey === "markdownElements" || rawKey === "originTag") continue;
@@ -1607,6 +1742,28 @@ function wrapEmphasis(text, bold, color) {
1607
1742
  if (color) wrapped = `<font color='${color}'>${wrapped}</font>`;
1608
1743
  return lead + wrapped + trail;
1609
1744
  }
1745
+ function resolveInlineStyle(ts, ctx) {
1746
+ const bold = !!(ts && Array.isArray(ts.attributes) && ts.attributes.includes("bold"));
1747
+ const customPair = resolveTextStyleCustomColorPair(ts);
1748
+ if (customPair) {
1749
+ const entry = themePairToColorEntry(customPair);
1750
+ if (entry) {
1751
+ const alias = registerColorEntry(ctx.colors, entry, "cus-text");
1752
+ return { bold, color: alias, colorKind: "custom" };
1753
+ }
1754
+ }
1755
+ const rawColor = resolveTextStyleColor(ts);
1756
+ const color = rawColor === "default" ? void 0 : rawColor;
1757
+ if (!color) return { bold };
1758
+ if (isCustomHexColor(color)) {
1759
+ const rgba = color.startsWith("#") ? themePairToColorEntry({ lightMode: color, darkMode: color }) : { light_mode: color, dark_mode: color };
1760
+ if (rgba) {
1761
+ const alias = registerColorEntry(ctx.colors, rgba, "cus-text");
1762
+ return { bold, color: alias, colorKind: "custom" };
1763
+ }
1764
+ }
1765
+ return { bold, color, colorKind: "enum" };
1766
+ }
1610
1767
  function renderChildren(children, ctx) {
1611
1768
  let out = "";
1612
1769
  let buffer = [];
@@ -1626,8 +1783,9 @@ function renderChildren(children, ctx) {
1626
1783
  continue;
1627
1784
  }
1628
1785
  const head = group[0];
1629
- if (head.bold === seg.bold && head.color === seg.color) group.push(seg);
1630
- else {
1786
+ if (head.bold === seg.bold && head.color === seg.color && head.colorKind === seg.colorKind) {
1787
+ group.push(seg);
1788
+ } else {
1631
1789
  emitGroup();
1632
1790
  group.push(seg);
1633
1791
  }
@@ -1646,33 +1804,33 @@ function renderChildren(children, ctx) {
1646
1804
  out += childToMarkdown(child, ctx);
1647
1805
  }
1648
1806
  flushBuffer();
1649
- return out;
1807
+ return { content: out };
1650
1808
  }
1651
1809
  function childToInlineSegment(child, ctx) {
1652
1810
  const tag = child.tag;
1653
1811
  const prop = isRecord2(child.property) ? child.property : {};
1654
1812
  const ts = isRecord2(prop.textStyle) ? prop.textStyle : void 0;
1655
- const bold = !!(ts && Array.isArray(ts.attributes) && ts.attributes.includes("bold"));
1656
- const rawColor = resolveTextStyleColor(ts);
1657
- const color = rawColor === "default" ? void 0 : rawColor;
1813
+ const { bold, color, colorKind } = resolveInlineStyle(ts, ctx);
1658
1814
  switch (tag) {
1659
1815
  case "plain_text":
1660
1816
  return {
1661
1817
  content: typeof prop.content === "string" ? prop.content : "",
1662
1818
  bold,
1663
- color
1819
+ color,
1820
+ colorKind
1664
1821
  };
1665
1822
  case "code_span":
1666
1823
  return {
1667
1824
  content: `\`${typeof prop.content === "string" ? prop.content : ""}\``,
1668
1825
  bold,
1669
- color
1826
+ color,
1827
+ colorKind
1670
1828
  };
1671
1829
  case "link": {
1672
1830
  const text = typeof prop.content === "string" ? prop.content : "";
1673
1831
  const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1674
1832
  const url = urlObj && typeof urlObj.url === "string" ? urlObj.url : "";
1675
- return { content: `[${text}](${url})`, bold, color };
1833
+ return { content: `[${text}](${url})`, bold, color, colorKind };
1676
1834
  }
1677
1835
  case "at_all":
1678
1836
  return { content: "<at id=all></at>", bold, color };
@@ -1712,10 +1870,8 @@ function childToMarkdown(child, ctx) {
1712
1870
  case "plain_text": {
1713
1871
  const content = typeof prop.content === "string" ? prop.content : "";
1714
1872
  const ts = isRecord2(prop.textStyle) ? prop.textStyle : void 0;
1715
- const rawColor = resolveTextStyleColor(ts);
1716
- const color = rawColor === "default" ? void 0 : rawColor;
1717
- const attrs = ts && Array.isArray(ts.attributes) ? ts.attributes : [];
1718
- return wrapEmphasis(content, attrs.includes("bold"), color);
1873
+ const { bold, color } = resolveInlineStyle(ts, ctx);
1874
+ return wrapEmphasis(content, bold, color);
1719
1875
  }
1720
1876
  case "br":
1721
1877
  return "\n";
@@ -1841,7 +1997,7 @@ ${"#".repeat(level)} ${body}
1841
1997
  if (!data || data.tag !== "markdown") return "";
1842
1998
  const cellProp = isRecord2(data.property) ? data.property : {};
1843
1999
  const inner = Array.isArray(cellProp.elements) ? cellProp.elements : [];
1844
- const text = renderChildren(inner, ctx);
2000
+ const { content: text } = renderChildren(inner, ctx);
1845
2001
  return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
1846
2002
  });
1847
2003
  return `| ${cells.join(" | ")} |`;
@@ -1964,10 +2120,10 @@ function rawToDsl(raw) {
1964
2120
  }
1965
2121
  }
1966
2122
  const attachment = isRecord2(raw.json_attachment) ? raw.json_attachment : {};
1967
- const ctx = {
2123
+ const ctx = makeCtx({
1968
2124
  images: isRecord2(attachment.images) ? attachment.images : {},
1969
2125
  atUsers: isRecord2(attachment.at_users) ? attachment.at_users : {}
1970
- };
2126
+ });
1971
2127
  return convertRoot(inner, ctx);
1972
2128
  }
1973
2129
  if (isRecord2(raw.card)) {
@@ -1975,10 +2131,10 @@ function rawToDsl(raw) {
1975
2131
  if (typeof card.json_card === "string" || isRecord2(card.json_card)) {
1976
2132
  return rawToDsl(card);
1977
2133
  }
1978
- return convertRoot(card, { images: {}, atUsers: {} });
2134
+ return convertRoot(card, makeCtx());
1979
2135
  }
1980
2136
  if ("body" in raw || "header" in raw || "schema" in raw) {
1981
- return convertRoot(raw, { images: {}, atUsers: {} });
2137
+ return convertRoot(raw, makeCtx());
1982
2138
  }
1983
2139
  return raw;
1984
2140
  }
@@ -1987,12 +2143,18 @@ function degradeUnknown(el) {
1987
2143
  return { tag: "unknown", raw_tag: rawTag, fallback: el };
1988
2144
  }
1989
2145
 
2146
+ exports.createColorRegistry = createColorRegistry;
1990
2147
  exports.degradeUnknown = degradeUnknown;
1991
2148
  exports.dslToRaw = dslToRaw;
2149
+ exports.isCustomHexColor = isCustomHexColor;
1992
2150
  exports.isRawCardEnvelope = isRawCardEnvelope;
1993
2151
  exports.normalizeStoredCardColors = normalizeStoredCardColors;
1994
2152
  exports.rawCardToDsl = rawCardToDsl;
1995
2153
  exports.rawToDsl = rawToDsl;
2154
+ exports.readThemeColorDark = readThemeColorDark;
1996
2155
  exports.readThemeColorLight = readThemeColorLight;
2156
+ exports.registerColorEntry = registerColorEntry;
1997
2157
  exports.resolveCustomBackgroundLight = resolveCustomBackgroundLight;
1998
2158
  exports.resolveTextStyleColor = resolveTextStyleColor;
2159
+ exports.resolveTextStyleCustomColorPair = resolveTextStyleCustomColorPair;
2160
+ exports.themePairToColorEntry = themePairToColorEntry;
package/dist/index.d.ts CHANGED
@@ -8,9 +8,9 @@
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 { normalizeStoredCardColors } from './normalize-stored-colors.js';
11
+ export { normalizeStoredCardColors, createColorRegistry, registerColorEntry } from './normalize-stored-colors.js';
12
12
  export type { CustomColorEntry } from './normalize-stored-colors.js';
13
- export { readThemeColorLight, resolveCustomBackgroundLight, resolveTextStyleColor, } from './custom-colors.js';
13
+ export { readThemeColorLight, readThemeColorDark, resolveCustomBackgroundLight, resolveTextStyleColor, resolveTextStyleCustomColorPair, themePairToColorEntry, isCustomHexColor, } from './custom-colors.js';
14
14
  export type { ThemeColorPair } from './custom-colors.js';
15
15
  export type { CardElement, ElementProperty, JsonCard } from './types.js';
16
16
  //# sourceMappingURL=index.d.ts.map
@@ -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,MAAM,8BAA8B,CAAC;AACzE,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,qBAAqB,GACtB,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,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,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
@@ -866,31 +866,22 @@ function isRecord(v) {
866
866
  return typeof v === "object" && v !== null && !Array.isArray(v);
867
867
  }
868
868
 
869
- // src/custom-colors.ts
870
- function readThemeColorLight(pair) {
871
- if (!pair || typeof pair !== "object") return void 0;
872
- const o = pair;
873
- const raw = o.light_mode ?? o.lightMode;
874
- if (typeof raw !== "string") return void 0;
875
- const v = raw.trim();
876
- return v || void 0;
869
+ // src/normalize-stored-colors.ts
870
+ function createColorRegistry() {
871
+ return { entries: {}, keyToAlias: /* @__PURE__ */ new Map(), bgSeq: 0, textSeq: 0 };
877
872
  }
878
- function resolveTextStyleColor(textStyle) {
879
- if (!textStyle || typeof textStyle !== "object") return void 0;
880
- const ts = textStyle;
881
- const enumColor = typeof ts.color === "string" ? ts.color.trim() : void 0;
882
- if (enumColor && enumColor !== "default") return enumColor;
883
- const custom = ts.custom_color ?? ts.customColor;
884
- return readThemeColorLight(custom);
873
+ function colorEntryKey(entry) {
874
+ return `${entry.light_mode}|${entry.dark_mode}`;
885
875
  }
886
- function resolveCustomBackgroundLight(property) {
887
- if (!property || typeof property !== "object") return void 0;
888
- const p = property;
889
- const custom = p.custom_background_style ?? p.customBackgroundStyle;
890
- return readThemeColorLight(custom);
876
+ function registerColorEntry(registry, entry, prefix) {
877
+ const key = colorEntryKey(entry);
878
+ const existing = registry.keyToAlias.get(key);
879
+ if (existing) return existing;
880
+ const alias = prefix === "cus-bg" ? `cus-bg-${registry.bgSeq++}` : `cus-text-${registry.textSeq++}`;
881
+ registry.entries[alias] = entry;
882
+ registry.keyToAlias.set(key, alias);
883
+ return alias;
891
884
  }
892
-
893
- // src/normalize-stored-colors.ts
894
885
  function hexColorToRgba(hex) {
895
886
  const h = hex.trim().replace(/^#/, "");
896
887
  if (h.length === 8) {
@@ -920,17 +911,17 @@ function readCustomBackgroundStyle(node) {
920
911
  if (!light || !dark) return void 0;
921
912
  return { light_mode: light, dark_mode: dark };
922
913
  }
923
- function normalizeStoredCardColors(dsl) {
924
- const colorEntries = {};
925
- const styleKey = (entry) => `${entry.light_mode}|${entry.dark_mode}`;
926
- const keyToAlias = /* @__PURE__ */ new Map();
927
- let bgSeq = 0;
914
+ function normalizeStoredCardColors(dsl, preRegistered) {
915
+ const colorEntries = { ...preRegistered?.entries ?? {} };
916
+ const keyToAlias = new Map(preRegistered?.keyToAlias ?? []);
917
+ let bgSeq = preRegistered?.bgSeq ?? 0;
918
+ let textSeq = preRegistered?.textSeq ?? 0;
928
919
  const ensureAlias = (prefix, entry) => {
929
- const existing2 = keyToAlias.get(styleKey(entry));
920
+ const existing2 = keyToAlias.get(colorEntryKey(entry));
930
921
  if (existing2) return existing2;
931
- const alias = `${prefix}-${bgSeq++}`;
922
+ const alias = prefix === "cus-bg" ? `cus-bg-${bgSeq++}` : `cus-text-${textSeq++}`;
932
923
  colorEntries[alias] = entry;
933
- keyToAlias.set(styleKey(entry), alias);
924
+ keyToAlias.set(colorEntryKey(entry), alias);
934
925
  return alias;
935
926
  };
936
927
  const walk = (node) => {
@@ -947,11 +938,15 @@ function normalizeStoredCardColors(dsl) {
947
938
  delete obj.custom_background_style;
948
939
  }
949
940
  }
941
+ if (obj.tag === "markdown" && typeof obj.content === "string") {
942
+ normalizeMarkdownInlineHexColors(obj, ensureAlias, !!preRegistered);
943
+ }
950
944
  for (const value of Object.values(obj)) walk(value);
951
945
  };
952
946
  walk(dsl.body);
953
947
  if (Object.keys(colorEntries).length === 0) return;
954
- const firstBg = colorEntries["cus-bg-0"] ?? Object.values(colorEntries)[0];
948
+ const firstBgKey = Object.keys(colorEntries).find((k) => k.startsWith("cus-bg-"));
949
+ const firstBg = firstBgKey ? colorEntries[firstBgKey] : void 0;
955
950
  if (firstBg) {
956
951
  const m = firstBg.light_mode.match(/^rgba\((\d+),(\d+),(\d+),/);
957
952
  if (m) {
@@ -965,6 +960,13 @@ function normalizeStoredCardColors(dsl) {
965
960
  light_mode: "rgba(100,106,115,1)",
966
961
  dark_mode: "rgba(166,166,166,1)"
967
962
  };
963
+ const aliasRenames = /* @__PURE__ */ new Map();
964
+ for (const to of ["cus-accent", "cus-muted"]) {
965
+ for (const [from, target] of unifyMatchingAlias(colorEntries, "cus-text", to)) {
966
+ aliasRenames.set(from, target);
967
+ }
968
+ }
969
+ remapColorReferences(dsl.body, aliasRenames);
968
970
  const config = dsl.config && typeof dsl.config === "object" ? dsl.config : {};
969
971
  dsl.config = config;
970
972
  const style = config.style && typeof config.style === "object" ? config.style : {};
@@ -972,22 +974,148 @@ function normalizeStoredCardColors(dsl) {
972
974
  const existing = style.color && typeof style.color === "object" ? style.color : {};
973
975
  style.color = { ...colorEntries, ...existing };
974
976
  if (!colorEntries["cus-accent"]) return;
975
- const applyDefaultFontColors = (node) => {
976
- if (Array.isArray(node)) {
977
- for (const child of node) applyDefaultFontColors(child);
977
+ if (!preRegistered) {
978
+ const applyDefaultFontColors = (node) => {
979
+ if (Array.isArray(node)) {
980
+ for (const child of node) applyDefaultFontColors(child);
981
+ return;
982
+ }
983
+ if (!node || typeof node !== "object") return;
984
+ const obj = node;
985
+ if (obj.tag === "markdown" && obj.font_color === void 0) {
986
+ const size = obj.text_size;
987
+ const content = typeof obj.content === "string" ? obj.content : "";
988
+ if (size === "small") obj.font_color = "cus-muted";
989
+ else if (size === "heading" || content.includes("**")) obj.font_color = "cus-accent";
990
+ }
991
+ for (const value of Object.values(obj)) applyDefaultFontColors(value);
992
+ };
993
+ applyDefaultFontColors(dsl.body);
994
+ }
995
+ }
996
+ function unifyMatchingAlias(entries, fromPrefix, to) {
997
+ const renames = /* @__PURE__ */ new Map();
998
+ const target = entries[to];
999
+ if (!target) return renames;
1000
+ for (const alias of Object.keys(entries)) {
1001
+ if (alias === to) continue;
1002
+ if (!alias.startsWith(`${fromPrefix}-`) && alias !== fromPrefix) continue;
1003
+ const entry = entries[alias];
1004
+ const matches = to === "cus-accent" || to === "cus-muted" ? entry.light_mode === target.light_mode : colorEntryKey(entry) === colorEntryKey(target);
1005
+ if (matches) {
1006
+ renames.set(alias, to);
1007
+ delete entries[alias];
1008
+ }
1009
+ }
1010
+ return renames;
1011
+ }
1012
+ function remapColorReferences(node, aliasRenames) {
1013
+ const remap = (alias) => {
1014
+ if (!alias) return alias;
1015
+ return aliasRenames.get(alias) ?? alias;
1016
+ };
1017
+ const walk = (n) => {
1018
+ if (Array.isArray(n)) {
1019
+ for (const c of n) walk(c);
978
1020
  return;
979
1021
  }
980
- if (!node || typeof node !== "object") return;
981
- const obj = node;
982
- if (obj.tag === "markdown" && obj.font_color === void 0) {
983
- const size = obj.text_size;
984
- const content = typeof obj.content === "string" ? obj.content : "";
985
- if (size === "small") obj.font_color = "cus-muted";
986
- else if (size === "heading" || content.includes("**")) obj.font_color = "cus-accent";
1022
+ if (!n || typeof n !== "object") return;
1023
+ const obj = n;
1024
+ if (typeof obj.font_color === "string") obj.font_color = remap(obj.font_color);
1025
+ if (typeof obj.content === "string" && obj.content.includes("<font")) {
1026
+ obj.content = obj.content.replace(
1027
+ /<font color=['"]([^'"]+)['"]>/g,
1028
+ (_m, color) => {
1029
+ const next = remap(color);
1030
+ return next ? `<font color='${next}'>` : _m;
1031
+ }
1032
+ );
987
1033
  }
988
- for (const value of Object.values(obj)) applyDefaultFontColors(value);
1034
+ for (const value of Object.values(obj)) walk(value);
989
1035
  };
990
- applyDefaultFontColors(dsl.body);
1036
+ walk(node);
1037
+ }
1038
+ function normalizeMarkdownInlineHexColors(obj, ensureAlias, keepFontTags = false) {
1039
+ const content = obj.content;
1040
+ const hexColor = (raw) => {
1041
+ const v = raw.trim();
1042
+ if (v.startsWith("#")) {
1043
+ const rgba = hexColorToRgba(v);
1044
+ return rgba ? { light_mode: rgba, dark_mode: rgba } : void 0;
1045
+ }
1046
+ if (/^rgba?\(/i.test(v)) return { light_mode: v, dark_mode: v };
1047
+ return void 0;
1048
+ };
1049
+ const single = content.match(/^<font color=['"]([^'"]+)['"]\s*>([\s\S]*)<\/font>$/);
1050
+ if (single && !keepFontTags) {
1051
+ const entry = hexColor(single[1]);
1052
+ if (entry) {
1053
+ const alias = ensureAlias("cus-text", entry);
1054
+ if (obj.font_color === void 0) obj.font_color = alias;
1055
+ obj.content = single[2];
1056
+ return;
1057
+ }
1058
+ }
1059
+ obj.content = content.replace(/<font color=['"]([^'"]+)['"]>/g, (match, raw) => {
1060
+ const entry = hexColor(raw);
1061
+ if (!entry) return match;
1062
+ const alias = ensureAlias("cus-text", entry);
1063
+ return `<font color='${alias}'>`;
1064
+ });
1065
+ }
1066
+
1067
+ // src/custom-colors.ts
1068
+ function readThemeColorLight(pair) {
1069
+ if (!pair || typeof pair !== "object") return void 0;
1070
+ const o = pair;
1071
+ const raw = o.light_mode ?? o.lightMode;
1072
+ if (typeof raw !== "string") return void 0;
1073
+ const v = raw.trim();
1074
+ return v || void 0;
1075
+ }
1076
+ function readThemeColorDark(pair) {
1077
+ if (!pair || typeof pair !== "object") return void 0;
1078
+ const o = pair;
1079
+ const raw = o.dark_mode ?? o.darkMode;
1080
+ if (typeof raw !== "string") return void 0;
1081
+ const v = raw.trim();
1082
+ return v || void 0;
1083
+ }
1084
+ function isCustomHexColor(color) {
1085
+ const v = color.trim();
1086
+ return v.startsWith("#") || /^rgba?\(/i.test(v);
1087
+ }
1088
+ function resolveTextStyleColor(textStyle) {
1089
+ if (!textStyle || typeof textStyle !== "object") return void 0;
1090
+ const ts = textStyle;
1091
+ const enumColor = typeof ts.color === "string" ? ts.color.trim() : void 0;
1092
+ if (enumColor && enumColor !== "default") return enumColor;
1093
+ const custom = ts.custom_color ?? ts.customColor;
1094
+ return readThemeColorLight(custom);
1095
+ }
1096
+ function resolveTextStyleCustomColorPair(textStyle) {
1097
+ if (!textStyle || typeof textStyle !== "object") return void 0;
1098
+ const ts = textStyle;
1099
+ const enumColor = typeof ts.color === "string" ? ts.color.trim() : void 0;
1100
+ if (enumColor && enumColor !== "default") return void 0;
1101
+ const custom = ts.custom_color ?? ts.customColor;
1102
+ if (!custom || typeof custom !== "object") return void 0;
1103
+ return custom;
1104
+ }
1105
+ function themePairToColorEntry(pair) {
1106
+ const lightRaw = readThemeColorLight(pair);
1107
+ const darkRaw = readThemeColorDark(pair);
1108
+ if (!lightRaw || !darkRaw) return void 0;
1109
+ const light = lightRaw.startsWith("#") ? hexColorToRgba(lightRaw) : lightRaw;
1110
+ const dark = darkRaw.startsWith("#") ? hexColorToRgba(darkRaw) : darkRaw;
1111
+ if (!light || !dark) return void 0;
1112
+ return { light_mode: light, dark_mode: dark };
1113
+ }
1114
+ function resolveCustomBackgroundLight(property) {
1115
+ if (!property || typeof property !== "object") return void 0;
1116
+ const p = property;
1117
+ const custom = p.custom_background_style ?? p.customBackgroundStyle;
1118
+ return readThemeColorLight(custom);
991
1119
  }
992
1120
 
993
1121
  // src/raw-to-dsl.ts
@@ -1005,12 +1133,19 @@ function rawCardToDsl(envelope) {
1005
1133
  } catch {
1006
1134
  return envelope;
1007
1135
  }
1008
- const ctx = {
1136
+ const ctx = makeCtx({
1009
1137
  images: env.json_attachment?.images ?? {},
1010
1138
  atUsers: env.json_attachment?.at_users ?? {}
1011
- };
1139
+ });
1012
1140
  return convertRoot(inner, ctx);
1013
1141
  }
1142
+ function makeCtx(partial) {
1143
+ return {
1144
+ images: partial?.images ?? {},
1145
+ atUsers: partial?.atUsers ?? {},
1146
+ colors: createColorRegistry()
1147
+ };
1148
+ }
1014
1149
  var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
1015
1150
  "new_body",
1016
1151
  "source",
@@ -1098,7 +1233,7 @@ function convertRoot(root, ctx) {
1098
1233
  out.config = convertConfig(isRecord2(root.config) ? root.config : {});
1099
1234
  if (isRecord2(root.body)) out.body = convertBody2(root.body, ctx);
1100
1235
  if (isRecord2(root.header)) out.header = convertHeader2(root.header, ctx);
1101
- normalizeStoredCardColors(out);
1236
+ normalizeStoredCardColors(out, ctx.colors);
1102
1237
  return out;
1103
1238
  }
1104
1239
  function convertConfig(_node) {
@@ -1200,7 +1335,7 @@ function unwrap(node, ctx) {
1200
1335
  let value = isRecord2(cell) && "data" in cell ? cell.data : cell;
1201
1336
  if (isRecord2(value) && value.tag === "markdown" && isRecord2(value.property)) {
1202
1337
  const elements = Array.isArray(value.property.elements) ? value.property.elements : [];
1203
- value = renderChildren(elements, ctx);
1338
+ value = renderChildren(elements, ctx).content;
1204
1339
  }
1205
1340
  r[k] = value;
1206
1341
  }
@@ -1575,7 +1710,7 @@ function resolveAtUserId(prop, ctx) {
1575
1710
  function rebuildMarkdown(property, ctx) {
1576
1711
  const out = { tag: "markdown" };
1577
1712
  const elements = Array.isArray(property.elements) ? property.elements : [];
1578
- const content = renderChildren(elements, ctx);
1713
+ const { content } = renderChildren(elements, ctx);
1579
1714
  if (content) out.content = content;
1580
1715
  for (const [rawKey, rawValue] of Object.entries(property)) {
1581
1716
  if (rawKey === "elements" || rawKey === "markdownElements" || rawKey === "originTag") continue;
@@ -1605,6 +1740,28 @@ function wrapEmphasis(text, bold, color) {
1605
1740
  if (color) wrapped = `<font color='${color}'>${wrapped}</font>`;
1606
1741
  return lead + wrapped + trail;
1607
1742
  }
1743
+ function resolveInlineStyle(ts, ctx) {
1744
+ const bold = !!(ts && Array.isArray(ts.attributes) && ts.attributes.includes("bold"));
1745
+ const customPair = resolveTextStyleCustomColorPair(ts);
1746
+ if (customPair) {
1747
+ const entry = themePairToColorEntry(customPair);
1748
+ if (entry) {
1749
+ const alias = registerColorEntry(ctx.colors, entry, "cus-text");
1750
+ return { bold, color: alias, colorKind: "custom" };
1751
+ }
1752
+ }
1753
+ const rawColor = resolveTextStyleColor(ts);
1754
+ const color = rawColor === "default" ? void 0 : rawColor;
1755
+ if (!color) return { bold };
1756
+ if (isCustomHexColor(color)) {
1757
+ const rgba = color.startsWith("#") ? themePairToColorEntry({ lightMode: color, darkMode: color }) : { light_mode: color, dark_mode: color };
1758
+ if (rgba) {
1759
+ const alias = registerColorEntry(ctx.colors, rgba, "cus-text");
1760
+ return { bold, color: alias, colorKind: "custom" };
1761
+ }
1762
+ }
1763
+ return { bold, color, colorKind: "enum" };
1764
+ }
1608
1765
  function renderChildren(children, ctx) {
1609
1766
  let out = "";
1610
1767
  let buffer = [];
@@ -1624,8 +1781,9 @@ function renderChildren(children, ctx) {
1624
1781
  continue;
1625
1782
  }
1626
1783
  const head = group[0];
1627
- if (head.bold === seg.bold && head.color === seg.color) group.push(seg);
1628
- else {
1784
+ if (head.bold === seg.bold && head.color === seg.color && head.colorKind === seg.colorKind) {
1785
+ group.push(seg);
1786
+ } else {
1629
1787
  emitGroup();
1630
1788
  group.push(seg);
1631
1789
  }
@@ -1644,33 +1802,33 @@ function renderChildren(children, ctx) {
1644
1802
  out += childToMarkdown(child, ctx);
1645
1803
  }
1646
1804
  flushBuffer();
1647
- return out;
1805
+ return { content: out };
1648
1806
  }
1649
1807
  function childToInlineSegment(child, ctx) {
1650
1808
  const tag = child.tag;
1651
1809
  const prop = isRecord2(child.property) ? child.property : {};
1652
1810
  const ts = isRecord2(prop.textStyle) ? prop.textStyle : void 0;
1653
- const bold = !!(ts && Array.isArray(ts.attributes) && ts.attributes.includes("bold"));
1654
- const rawColor = resolveTextStyleColor(ts);
1655
- const color = rawColor === "default" ? void 0 : rawColor;
1811
+ const { bold, color, colorKind } = resolveInlineStyle(ts, ctx);
1656
1812
  switch (tag) {
1657
1813
  case "plain_text":
1658
1814
  return {
1659
1815
  content: typeof prop.content === "string" ? prop.content : "",
1660
1816
  bold,
1661
- color
1817
+ color,
1818
+ colorKind
1662
1819
  };
1663
1820
  case "code_span":
1664
1821
  return {
1665
1822
  content: `\`${typeof prop.content === "string" ? prop.content : ""}\``,
1666
1823
  bold,
1667
- color
1824
+ color,
1825
+ colorKind
1668
1826
  };
1669
1827
  case "link": {
1670
1828
  const text = typeof prop.content === "string" ? prop.content : "";
1671
1829
  const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1672
1830
  const url = urlObj && typeof urlObj.url === "string" ? urlObj.url : "";
1673
- return { content: `[${text}](${url})`, bold, color };
1831
+ return { content: `[${text}](${url})`, bold, color, colorKind };
1674
1832
  }
1675
1833
  case "at_all":
1676
1834
  return { content: "<at id=all></at>", bold, color };
@@ -1710,10 +1868,8 @@ function childToMarkdown(child, ctx) {
1710
1868
  case "plain_text": {
1711
1869
  const content = typeof prop.content === "string" ? prop.content : "";
1712
1870
  const ts = isRecord2(prop.textStyle) ? prop.textStyle : void 0;
1713
- const rawColor = resolveTextStyleColor(ts);
1714
- const color = rawColor === "default" ? void 0 : rawColor;
1715
- const attrs = ts && Array.isArray(ts.attributes) ? ts.attributes : [];
1716
- return wrapEmphasis(content, attrs.includes("bold"), color);
1871
+ const { bold, color } = resolveInlineStyle(ts, ctx);
1872
+ return wrapEmphasis(content, bold, color);
1717
1873
  }
1718
1874
  case "br":
1719
1875
  return "\n";
@@ -1839,7 +1995,7 @@ ${"#".repeat(level)} ${body}
1839
1995
  if (!data || data.tag !== "markdown") return "";
1840
1996
  const cellProp = isRecord2(data.property) ? data.property : {};
1841
1997
  const inner = Array.isArray(cellProp.elements) ? cellProp.elements : [];
1842
- const text = renderChildren(inner, ctx);
1998
+ const { content: text } = renderChildren(inner, ctx);
1843
1999
  return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
1844
2000
  });
1845
2001
  return `| ${cells.join(" | ")} |`;
@@ -1962,10 +2118,10 @@ function rawToDsl(raw) {
1962
2118
  }
1963
2119
  }
1964
2120
  const attachment = isRecord2(raw.json_attachment) ? raw.json_attachment : {};
1965
- const ctx = {
2121
+ const ctx = makeCtx({
1966
2122
  images: isRecord2(attachment.images) ? attachment.images : {},
1967
2123
  atUsers: isRecord2(attachment.at_users) ? attachment.at_users : {}
1968
- };
2124
+ });
1969
2125
  return convertRoot(inner, ctx);
1970
2126
  }
1971
2127
  if (isRecord2(raw.card)) {
@@ -1973,10 +2129,10 @@ function rawToDsl(raw) {
1973
2129
  if (typeof card.json_card === "string" || isRecord2(card.json_card)) {
1974
2130
  return rawToDsl(card);
1975
2131
  }
1976
- return convertRoot(card, { images: {}, atUsers: {} });
2132
+ return convertRoot(card, makeCtx());
1977
2133
  }
1978
2134
  if ("body" in raw || "header" in raw || "schema" in raw) {
1979
- return convertRoot(raw, { images: {}, atUsers: {} });
2135
+ return convertRoot(raw, makeCtx());
1980
2136
  }
1981
2137
  return raw;
1982
2138
  }
@@ -1985,4 +2141,4 @@ function degradeUnknown(el) {
1985
2141
  return { tag: "unknown", raw_tag: rawTag, fallback: el };
1986
2142
  }
1987
2143
 
1988
- export { degradeUnknown, dslToRaw, isRawCardEnvelope, normalizeStoredCardColors, rawCardToDsl, rawToDsl, readThemeColorLight, resolveCustomBackgroundLight, resolveTextStyleColor };
2144
+ export { createColorRegistry, degradeUnknown, dslToRaw, isCustomHexColor, isRawCardEnvelope, normalizeStoredCardColors, rawCardToDsl, rawToDsl, readThemeColorDark, readThemeColorLight, registerColorEntry, resolveCustomBackgroundLight, resolveTextStyleColor, resolveTextStyleCustomColorPair, themePairToColorEntry };
@@ -6,9 +6,20 @@ export type CustomColorEntry = {
6
6
  light_mode: string;
7
7
  dark_mode: string;
8
8
  };
9
+ export type ColorRegistry = {
10
+ entries: Record<string, CustomColorEntry>;
11
+ keyToAlias: Map<string, string>;
12
+ bgSeq: number;
13
+ textSeq: number;
14
+ };
15
+ export declare function createColorRegistry(): ColorRegistry;
16
+ export declare function colorEntryKey(entry: CustomColorEntry): string;
17
+ export declare function registerColorEntry(registry: ColorRegistry, entry: CustomColorEntry, prefix: 'cus-bg' | 'cus-text'): string;
18
+ export declare function hexColorToRgba(hex: string): string | undefined;
9
19
  /**
10
20
  * 将飞书回读的 custom_background_style 还原为可再次发送的 background_style cus-N + config.style.color。
11
21
  * 若 markdown 缺少 font_color,按 text_size / 加粗内容补 cus-accent / cus-muted(飞书回读会丢 font_color)。
22
+ * rawToDsl 可传入已登记的文字自定义色(preRegistered),与背景色一并写入 config.style.color。
12
23
  */
13
- export declare function normalizeStoredCardColors(dsl: Record<string, unknown>): void;
24
+ export declare function normalizeStoredCardColors(dsl: Record<string, unknown>, preRegistered?: ColorRegistry): void;
14
25
  //# sourceMappingURL=normalize-stored-colors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"normalize-stored-colors.d.ts","sourceRoot":"","sources":["../src/normalize-stored-colors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAmCzE;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAoF5E"}
1
+ {"version":3,"file":"normalize-stored-colors.d.ts","sourceRoot":"","sources":["../src/normalize-stored-colors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC1C,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,mBAAmB,IAAI,aAAa,CAEnD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAE7D;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAC5B,MAAM,CAUR;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB9D;AAiBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,aAAa,CAAC,EAAE,aAAa,GAC5B,IAAI,CAmGN"}
@@ -1 +1 @@
1
- {"version":3,"file":"raw-to-dsl.d.ts","sourceRoot":"","sources":["../src/raw-to-dsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAcH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIzD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAcvD;AAkgDD;;;;;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,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAcvD;AAojDD;;;;;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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/adapter",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "公开 DSL 与飞书内部 RAW JSON 的双向适配器;raw→DSL 对齐 feishu-raw-card-to-dsl",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",