@open-feishu-card/adapter 0.3.3 → 0.3.4

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 markdown.font_color / 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);
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 : {};
@@ -991,6 +993,130 @@ function normalizeStoredCardColors(dsl) {
991
993
  };
992
994
  applyDefaultFontColors(dsl.body);
993
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);
1020
+ return;
1021
+ }
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
+ );
1033
+ }
1034
+ for (const value of Object.values(obj)) walk(value);
1035
+ };
1036
+ walk(node);
1037
+ }
1038
+ function normalizeMarkdownInlineHexColors(obj, ensureAlias) {
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) {
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);
1119
+ }
994
1120
 
995
1121
  // src/raw-to-dsl.ts
996
1122
  function isRawCardEnvelope(value) {
@@ -1007,12 +1133,19 @@ function rawCardToDsl(envelope) {
1007
1133
  } catch {
1008
1134
  return envelope;
1009
1135
  }
1010
- const ctx = {
1136
+ const ctx = makeCtx({
1011
1137
  images: env.json_attachment?.images ?? {},
1012
1138
  atUsers: env.json_attachment?.at_users ?? {}
1013
- };
1139
+ });
1014
1140
  return convertRoot(inner, ctx);
1015
1141
  }
1142
+ function makeCtx(partial) {
1143
+ return {
1144
+ images: partial?.images ?? {},
1145
+ atUsers: partial?.atUsers ?? {},
1146
+ colors: createColorRegistry()
1147
+ };
1148
+ }
1016
1149
  var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
1017
1150
  "new_body",
1018
1151
  "source",
@@ -1100,7 +1233,7 @@ function convertRoot(root, ctx) {
1100
1233
  out.config = convertConfig(isRecord2(root.config) ? root.config : {});
1101
1234
  if (isRecord2(root.body)) out.body = convertBody2(root.body, ctx);
1102
1235
  if (isRecord2(root.header)) out.header = convertHeader2(root.header, ctx);
1103
- normalizeStoredCardColors(out);
1236
+ normalizeStoredCardColors(out, ctx.colors);
1104
1237
  return out;
1105
1238
  }
1106
1239
  function convertConfig(_node) {
@@ -1202,7 +1335,7 @@ function unwrap(node, ctx) {
1202
1335
  let value = isRecord2(cell) && "data" in cell ? cell.data : cell;
1203
1336
  if (isRecord2(value) && value.tag === "markdown" && isRecord2(value.property)) {
1204
1337
  const elements = Array.isArray(value.property.elements) ? value.property.elements : [];
1205
- value = renderChildren(elements, ctx);
1338
+ value = renderChildren(elements, ctx).content;
1206
1339
  }
1207
1340
  r[k] = value;
1208
1341
  }
@@ -1577,8 +1710,9 @@ function resolveAtUserId(prop, ctx) {
1577
1710
  function rebuildMarkdown(property, ctx) {
1578
1711
  const out = { tag: "markdown" };
1579
1712
  const elements = Array.isArray(property.elements) ? property.elements : [];
1580
- const content = renderChildren(elements, ctx);
1713
+ const { content, uniformFontColor } = renderChildren(elements, ctx);
1581
1714
  if (content) out.content = content;
1715
+ if (uniformFontColor) out.font_color = uniformFontColor;
1582
1716
  for (const [rawKey, rawValue] of Object.entries(property)) {
1583
1717
  if (rawKey === "elements" || rawKey === "markdownElements" || rawKey === "originTag") continue;
1584
1718
  if (rawKey === "udIcon") continue;
@@ -1596,7 +1730,7 @@ function rebuildMarkdown(property, ctx) {
1596
1730
  }
1597
1731
  return out;
1598
1732
  }
1599
- function wrapEmphasis(text, bold, color) {
1733
+ function wrapEmphasis(text, bold, color, colorKind, inlineCustomFont = false) {
1600
1734
  if (!bold && !color) return text;
1601
1735
  const lead = text.match(/^\s*/)[0];
1602
1736
  const trail = text.match(/\s*$/)[0];
@@ -1604,20 +1738,51 @@ function wrapEmphasis(text, bold, color) {
1604
1738
  if (core.length === 0) return text;
1605
1739
  let wrapped = core;
1606
1740
  if (bold) wrapped = `**${wrapped}**`;
1607
- if (color) wrapped = `<font color='${color}'>${wrapped}</font>`;
1741
+ if (color) {
1742
+ if (colorKind === "enum") wrapped = `<font color='${color}'>${wrapped}</font>`;
1743
+ else if (colorKind === "custom" && inlineCustomFont) wrapped = `<font color='${color}'>${wrapped}</font>`;
1744
+ }
1608
1745
  return lead + wrapped + trail;
1609
1746
  }
1747
+ function resolveInlineStyle(ts, ctx) {
1748
+ const bold = !!(ts && Array.isArray(ts.attributes) && ts.attributes.includes("bold"));
1749
+ const customPair = resolveTextStyleCustomColorPair(ts);
1750
+ if (customPair) {
1751
+ const entry = themePairToColorEntry(customPair);
1752
+ if (entry) {
1753
+ const alias = registerColorEntry(ctx.colors, entry, "cus-text");
1754
+ return { bold, color: alias, colorKind: "custom" };
1755
+ }
1756
+ }
1757
+ const rawColor = resolveTextStyleColor(ts);
1758
+ const color = rawColor === "default" ? void 0 : rawColor;
1759
+ if (!color) return { bold };
1760
+ if (isCustomHexColor(color)) {
1761
+ const rgba = color.startsWith("#") ? themePairToColorEntry({ lightMode: color, darkMode: color }) : { light_mode: color, dark_mode: color };
1762
+ if (rgba) {
1763
+ const alias = registerColorEntry(ctx.colors, rgba, "cus-text");
1764
+ return { bold, color: alias, colorKind: "custom" };
1765
+ }
1766
+ }
1767
+ return { bold, color, colorKind: "enum" };
1768
+ }
1610
1769
  function renderChildren(children, ctx) {
1611
1770
  let out = "";
1612
1771
  let buffer = [];
1772
+ let uniformFontColor;
1613
1773
  function flushBuffer() {
1614
1774
  if (buffer.length === 0) return;
1775
+ const customColors = buffer.filter((s) => s.colorKind === "custom" && s.color).map((s) => s.color);
1776
+ const hasEnumColor = buffer.some((s) => s.colorKind === "enum" && s.color);
1777
+ const uniformCustom = customColors.length > 0 && !hasEnumColor && customColors.every((c) => c === customColors[0]) ? customColors[0] : void 0;
1778
+ if (uniformCustom) uniformFontColor = uniformCustom;
1615
1779
  let group = [];
1616
1780
  function emitGroup() {
1617
1781
  if (group.length === 0) return;
1618
1782
  const inner = group.map((s) => s.content).join("");
1619
- const { bold, color } = group[0];
1620
- out += wrapEmphasis(inner, bold, color);
1783
+ const { bold, color, colorKind } = group[0];
1784
+ const inlineCustomFont = colorKind === "custom" && !!color && color !== uniformCustom;
1785
+ out += wrapEmphasis(inner, bold, color, colorKind, inlineCustomFont);
1621
1786
  group = [];
1622
1787
  }
1623
1788
  for (const seg of buffer) {
@@ -1626,8 +1791,9 @@ function renderChildren(children, ctx) {
1626
1791
  continue;
1627
1792
  }
1628
1793
  const head = group[0];
1629
- if (head.bold === seg.bold && head.color === seg.color) group.push(seg);
1630
- else {
1794
+ if (head.bold === seg.bold && head.color === seg.color && head.colorKind === seg.colorKind) {
1795
+ group.push(seg);
1796
+ } else {
1631
1797
  emitGroup();
1632
1798
  group.push(seg);
1633
1799
  }
@@ -1646,33 +1812,33 @@ function renderChildren(children, ctx) {
1646
1812
  out += childToMarkdown(child, ctx);
1647
1813
  }
1648
1814
  flushBuffer();
1649
- return out;
1815
+ return { content: out, uniformFontColor };
1650
1816
  }
1651
1817
  function childToInlineSegment(child, ctx) {
1652
1818
  const tag = child.tag;
1653
1819
  const prop = isRecord2(child.property) ? child.property : {};
1654
1820
  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;
1821
+ const { bold, color, colorKind } = resolveInlineStyle(ts, ctx);
1658
1822
  switch (tag) {
1659
1823
  case "plain_text":
1660
1824
  return {
1661
1825
  content: typeof prop.content === "string" ? prop.content : "",
1662
1826
  bold,
1663
- color
1827
+ color,
1828
+ colorKind
1664
1829
  };
1665
1830
  case "code_span":
1666
1831
  return {
1667
1832
  content: `\`${typeof prop.content === "string" ? prop.content : ""}\``,
1668
1833
  bold,
1669
- color
1834
+ color,
1835
+ colorKind
1670
1836
  };
1671
1837
  case "link": {
1672
1838
  const text = typeof prop.content === "string" ? prop.content : "";
1673
1839
  const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1674
1840
  const url = urlObj && typeof urlObj.url === "string" ? urlObj.url : "";
1675
- return { content: `[${text}](${url})`, bold, color };
1841
+ return { content: `[${text}](${url})`, bold, color, colorKind };
1676
1842
  }
1677
1843
  case "at_all":
1678
1844
  return { content: "<at id=all></at>", bold, color };
@@ -1712,10 +1878,8 @@ function childToMarkdown(child, ctx) {
1712
1878
  case "plain_text": {
1713
1879
  const content = typeof prop.content === "string" ? prop.content : "";
1714
1880
  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);
1881
+ const { bold, color, colorKind } = resolveInlineStyle(ts, ctx);
1882
+ return wrapEmphasis(content, bold, color, colorKind, colorKind === "custom");
1719
1883
  }
1720
1884
  case "br":
1721
1885
  return "\n";
@@ -1841,7 +2005,7 @@ ${"#".repeat(level)} ${body}
1841
2005
  if (!data || data.tag !== "markdown") return "";
1842
2006
  const cellProp = isRecord2(data.property) ? data.property : {};
1843
2007
  const inner = Array.isArray(cellProp.elements) ? cellProp.elements : [];
1844
- const text = renderChildren(inner, ctx);
2008
+ const { content: text } = renderChildren(inner, ctx);
1845
2009
  return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
1846
2010
  });
1847
2011
  return `| ${cells.join(" | ")} |`;
@@ -1964,10 +2128,10 @@ function rawToDsl(raw) {
1964
2128
  }
1965
2129
  }
1966
2130
  const attachment = isRecord2(raw.json_attachment) ? raw.json_attachment : {};
1967
- const ctx = {
2131
+ const ctx = makeCtx({
1968
2132
  images: isRecord2(attachment.images) ? attachment.images : {},
1969
2133
  atUsers: isRecord2(attachment.at_users) ? attachment.at_users : {}
1970
- };
2134
+ });
1971
2135
  return convertRoot(inner, ctx);
1972
2136
  }
1973
2137
  if (isRecord2(raw.card)) {
@@ -1975,10 +2139,10 @@ function rawToDsl(raw) {
1975
2139
  if (typeof card.json_card === "string" || isRecord2(card.json_card)) {
1976
2140
  return rawToDsl(card);
1977
2141
  }
1978
- return convertRoot(card, { images: {}, atUsers: {} });
2142
+ return convertRoot(card, makeCtx());
1979
2143
  }
1980
2144
  if ("body" in raw || "header" in raw || "schema" in raw) {
1981
- return convertRoot(raw, { images: {}, atUsers: {} });
2145
+ return convertRoot(raw, makeCtx());
1982
2146
  }
1983
2147
  return raw;
1984
2148
  }
@@ -1987,12 +2151,18 @@ function degradeUnknown(el) {
1987
2151
  return { tag: "unknown", raw_tag: rawTag, fallback: el };
1988
2152
  }
1989
2153
 
2154
+ exports.createColorRegistry = createColorRegistry;
1990
2155
  exports.degradeUnknown = degradeUnknown;
1991
2156
  exports.dslToRaw = dslToRaw;
2157
+ exports.isCustomHexColor = isCustomHexColor;
1992
2158
  exports.isRawCardEnvelope = isRawCardEnvelope;
1993
2159
  exports.normalizeStoredCardColors = normalizeStoredCardColors;
1994
2160
  exports.rawCardToDsl = rawCardToDsl;
1995
2161
  exports.rawToDsl = rawToDsl;
2162
+ exports.readThemeColorDark = readThemeColorDark;
1996
2163
  exports.readThemeColorLight = readThemeColorLight;
2164
+ exports.registerColorEntry = registerColorEntry;
1997
2165
  exports.resolveCustomBackgroundLight = resolveCustomBackgroundLight;
1998
2166
  exports.resolveTextStyleColor = resolveTextStyleColor;
2167
+ exports.resolveTextStyleCustomColorPair = resolveTextStyleCustomColorPair;
2168
+ 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);
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 : {};
@@ -989,6 +991,130 @@ function normalizeStoredCardColors(dsl) {
989
991
  };
990
992
  applyDefaultFontColors(dsl.body);
991
993
  }
994
+ function unifyMatchingAlias(entries, fromPrefix, to) {
995
+ const renames = /* @__PURE__ */ new Map();
996
+ const target = entries[to];
997
+ if (!target) return renames;
998
+ for (const alias of Object.keys(entries)) {
999
+ if (alias === to) continue;
1000
+ if (!alias.startsWith(`${fromPrefix}-`) && alias !== fromPrefix) continue;
1001
+ const entry = entries[alias];
1002
+ const matches = to === "cus-accent" || to === "cus-muted" ? entry.light_mode === target.light_mode : colorEntryKey(entry) === colorEntryKey(target);
1003
+ if (matches) {
1004
+ renames.set(alias, to);
1005
+ delete entries[alias];
1006
+ }
1007
+ }
1008
+ return renames;
1009
+ }
1010
+ function remapColorReferences(node, aliasRenames) {
1011
+ const remap = (alias) => {
1012
+ if (!alias) return alias;
1013
+ return aliasRenames.get(alias) ?? alias;
1014
+ };
1015
+ const walk = (n) => {
1016
+ if (Array.isArray(n)) {
1017
+ for (const c of n) walk(c);
1018
+ return;
1019
+ }
1020
+ if (!n || typeof n !== "object") return;
1021
+ const obj = n;
1022
+ if (typeof obj.font_color === "string") obj.font_color = remap(obj.font_color);
1023
+ if (typeof obj.content === "string" && obj.content.includes("<font")) {
1024
+ obj.content = obj.content.replace(
1025
+ /<font color=['"]([^'"]+)['"]>/g,
1026
+ (_m, color) => {
1027
+ const next = remap(color);
1028
+ return next ? `<font color='${next}'>` : _m;
1029
+ }
1030
+ );
1031
+ }
1032
+ for (const value of Object.values(obj)) walk(value);
1033
+ };
1034
+ walk(node);
1035
+ }
1036
+ function normalizeMarkdownInlineHexColors(obj, ensureAlias) {
1037
+ const content = obj.content;
1038
+ const hexColor = (raw) => {
1039
+ const v = raw.trim();
1040
+ if (v.startsWith("#")) {
1041
+ const rgba = hexColorToRgba(v);
1042
+ return rgba ? { light_mode: rgba, dark_mode: rgba } : void 0;
1043
+ }
1044
+ if (/^rgba?\(/i.test(v)) return { light_mode: v, dark_mode: v };
1045
+ return void 0;
1046
+ };
1047
+ const single = content.match(/^<font color=['"]([^'"]+)['"]\s*>([\s\S]*)<\/font>$/);
1048
+ if (single) {
1049
+ const entry = hexColor(single[1]);
1050
+ if (entry) {
1051
+ const alias = ensureAlias("cus-text", entry);
1052
+ if (obj.font_color === void 0) obj.font_color = alias;
1053
+ obj.content = single[2];
1054
+ return;
1055
+ }
1056
+ }
1057
+ obj.content = content.replace(/<font color=['"]([^'"]+)['"]>/g, (match, raw) => {
1058
+ const entry = hexColor(raw);
1059
+ if (!entry) return match;
1060
+ const alias = ensureAlias("cus-text", entry);
1061
+ return `<font color='${alias}'>`;
1062
+ });
1063
+ }
1064
+
1065
+ // src/custom-colors.ts
1066
+ function readThemeColorLight(pair) {
1067
+ if (!pair || typeof pair !== "object") return void 0;
1068
+ const o = pair;
1069
+ const raw = o.light_mode ?? o.lightMode;
1070
+ if (typeof raw !== "string") return void 0;
1071
+ const v = raw.trim();
1072
+ return v || void 0;
1073
+ }
1074
+ function readThemeColorDark(pair) {
1075
+ if (!pair || typeof pair !== "object") return void 0;
1076
+ const o = pair;
1077
+ const raw = o.dark_mode ?? o.darkMode;
1078
+ if (typeof raw !== "string") return void 0;
1079
+ const v = raw.trim();
1080
+ return v || void 0;
1081
+ }
1082
+ function isCustomHexColor(color) {
1083
+ const v = color.trim();
1084
+ return v.startsWith("#") || /^rgba?\(/i.test(v);
1085
+ }
1086
+ function resolveTextStyleColor(textStyle) {
1087
+ if (!textStyle || typeof textStyle !== "object") return void 0;
1088
+ const ts = textStyle;
1089
+ const enumColor = typeof ts.color === "string" ? ts.color.trim() : void 0;
1090
+ if (enumColor && enumColor !== "default") return enumColor;
1091
+ const custom = ts.custom_color ?? ts.customColor;
1092
+ return readThemeColorLight(custom);
1093
+ }
1094
+ function resolveTextStyleCustomColorPair(textStyle) {
1095
+ if (!textStyle || typeof textStyle !== "object") return void 0;
1096
+ const ts = textStyle;
1097
+ const enumColor = typeof ts.color === "string" ? ts.color.trim() : void 0;
1098
+ if (enumColor && enumColor !== "default") return void 0;
1099
+ const custom = ts.custom_color ?? ts.customColor;
1100
+ if (!custom || typeof custom !== "object") return void 0;
1101
+ return custom;
1102
+ }
1103
+ function themePairToColorEntry(pair) {
1104
+ const lightRaw = readThemeColorLight(pair);
1105
+ const darkRaw = readThemeColorDark(pair);
1106
+ if (!lightRaw || !darkRaw) return void 0;
1107
+ const light = lightRaw.startsWith("#") ? hexColorToRgba(lightRaw) : lightRaw;
1108
+ const dark = darkRaw.startsWith("#") ? hexColorToRgba(darkRaw) : darkRaw;
1109
+ if (!light || !dark) return void 0;
1110
+ return { light_mode: light, dark_mode: dark };
1111
+ }
1112
+ function resolveCustomBackgroundLight(property) {
1113
+ if (!property || typeof property !== "object") return void 0;
1114
+ const p = property;
1115
+ const custom = p.custom_background_style ?? p.customBackgroundStyle;
1116
+ return readThemeColorLight(custom);
1117
+ }
992
1118
 
993
1119
  // src/raw-to-dsl.ts
994
1120
  function isRawCardEnvelope(value) {
@@ -1005,12 +1131,19 @@ function rawCardToDsl(envelope) {
1005
1131
  } catch {
1006
1132
  return envelope;
1007
1133
  }
1008
- const ctx = {
1134
+ const ctx = makeCtx({
1009
1135
  images: env.json_attachment?.images ?? {},
1010
1136
  atUsers: env.json_attachment?.at_users ?? {}
1011
- };
1137
+ });
1012
1138
  return convertRoot(inner, ctx);
1013
1139
  }
1140
+ function makeCtx(partial) {
1141
+ return {
1142
+ images: partial?.images ?? {},
1143
+ atUsers: partial?.atUsers ?? {},
1144
+ colors: createColorRegistry()
1145
+ };
1146
+ }
1014
1147
  var RAW_ONLY_FIELDS = /* @__PURE__ */ new Set([
1015
1148
  "new_body",
1016
1149
  "source",
@@ -1098,7 +1231,7 @@ function convertRoot(root, ctx) {
1098
1231
  out.config = convertConfig(isRecord2(root.config) ? root.config : {});
1099
1232
  if (isRecord2(root.body)) out.body = convertBody2(root.body, ctx);
1100
1233
  if (isRecord2(root.header)) out.header = convertHeader2(root.header, ctx);
1101
- normalizeStoredCardColors(out);
1234
+ normalizeStoredCardColors(out, ctx.colors);
1102
1235
  return out;
1103
1236
  }
1104
1237
  function convertConfig(_node) {
@@ -1200,7 +1333,7 @@ function unwrap(node, ctx) {
1200
1333
  let value = isRecord2(cell) && "data" in cell ? cell.data : cell;
1201
1334
  if (isRecord2(value) && value.tag === "markdown" && isRecord2(value.property)) {
1202
1335
  const elements = Array.isArray(value.property.elements) ? value.property.elements : [];
1203
- value = renderChildren(elements, ctx);
1336
+ value = renderChildren(elements, ctx).content;
1204
1337
  }
1205
1338
  r[k] = value;
1206
1339
  }
@@ -1575,8 +1708,9 @@ function resolveAtUserId(prop, ctx) {
1575
1708
  function rebuildMarkdown(property, ctx) {
1576
1709
  const out = { tag: "markdown" };
1577
1710
  const elements = Array.isArray(property.elements) ? property.elements : [];
1578
- const content = renderChildren(elements, ctx);
1711
+ const { content, uniformFontColor } = renderChildren(elements, ctx);
1579
1712
  if (content) out.content = content;
1713
+ if (uniformFontColor) out.font_color = uniformFontColor;
1580
1714
  for (const [rawKey, rawValue] of Object.entries(property)) {
1581
1715
  if (rawKey === "elements" || rawKey === "markdownElements" || rawKey === "originTag") continue;
1582
1716
  if (rawKey === "udIcon") continue;
@@ -1594,7 +1728,7 @@ function rebuildMarkdown(property, ctx) {
1594
1728
  }
1595
1729
  return out;
1596
1730
  }
1597
- function wrapEmphasis(text, bold, color) {
1731
+ function wrapEmphasis(text, bold, color, colorKind, inlineCustomFont = false) {
1598
1732
  if (!bold && !color) return text;
1599
1733
  const lead = text.match(/^\s*/)[0];
1600
1734
  const trail = text.match(/\s*$/)[0];
@@ -1602,20 +1736,51 @@ function wrapEmphasis(text, bold, color) {
1602
1736
  if (core.length === 0) return text;
1603
1737
  let wrapped = core;
1604
1738
  if (bold) wrapped = `**${wrapped}**`;
1605
- if (color) wrapped = `<font color='${color}'>${wrapped}</font>`;
1739
+ if (color) {
1740
+ if (colorKind === "enum") wrapped = `<font color='${color}'>${wrapped}</font>`;
1741
+ else if (colorKind === "custom" && inlineCustomFont) wrapped = `<font color='${color}'>${wrapped}</font>`;
1742
+ }
1606
1743
  return lead + wrapped + trail;
1607
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
+ }
1608
1767
  function renderChildren(children, ctx) {
1609
1768
  let out = "";
1610
1769
  let buffer = [];
1770
+ let uniformFontColor;
1611
1771
  function flushBuffer() {
1612
1772
  if (buffer.length === 0) return;
1773
+ const customColors = buffer.filter((s) => s.colorKind === "custom" && s.color).map((s) => s.color);
1774
+ const hasEnumColor = buffer.some((s) => s.colorKind === "enum" && s.color);
1775
+ const uniformCustom = customColors.length > 0 && !hasEnumColor && customColors.every((c) => c === customColors[0]) ? customColors[0] : void 0;
1776
+ if (uniformCustom) uniformFontColor = uniformCustom;
1613
1777
  let group = [];
1614
1778
  function emitGroup() {
1615
1779
  if (group.length === 0) return;
1616
1780
  const inner = group.map((s) => s.content).join("");
1617
- const { bold, color } = group[0];
1618
- out += wrapEmphasis(inner, bold, color);
1781
+ const { bold, color, colorKind } = group[0];
1782
+ const inlineCustomFont = colorKind === "custom" && !!color && color !== uniformCustom;
1783
+ out += wrapEmphasis(inner, bold, color, colorKind, inlineCustomFont);
1619
1784
  group = [];
1620
1785
  }
1621
1786
  for (const seg of buffer) {
@@ -1624,8 +1789,9 @@ function renderChildren(children, ctx) {
1624
1789
  continue;
1625
1790
  }
1626
1791
  const head = group[0];
1627
- if (head.bold === seg.bold && head.color === seg.color) group.push(seg);
1628
- else {
1792
+ if (head.bold === seg.bold && head.color === seg.color && head.colorKind === seg.colorKind) {
1793
+ group.push(seg);
1794
+ } else {
1629
1795
  emitGroup();
1630
1796
  group.push(seg);
1631
1797
  }
@@ -1644,33 +1810,33 @@ function renderChildren(children, ctx) {
1644
1810
  out += childToMarkdown(child, ctx);
1645
1811
  }
1646
1812
  flushBuffer();
1647
- return out;
1813
+ return { content: out, uniformFontColor };
1648
1814
  }
1649
1815
  function childToInlineSegment(child, ctx) {
1650
1816
  const tag = child.tag;
1651
1817
  const prop = isRecord2(child.property) ? child.property : {};
1652
1818
  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;
1819
+ const { bold, color, colorKind } = resolveInlineStyle(ts, ctx);
1656
1820
  switch (tag) {
1657
1821
  case "plain_text":
1658
1822
  return {
1659
1823
  content: typeof prop.content === "string" ? prop.content : "",
1660
1824
  bold,
1661
- color
1825
+ color,
1826
+ colorKind
1662
1827
  };
1663
1828
  case "code_span":
1664
1829
  return {
1665
1830
  content: `\`${typeof prop.content === "string" ? prop.content : ""}\``,
1666
1831
  bold,
1667
- color
1832
+ color,
1833
+ colorKind
1668
1834
  };
1669
1835
  case "link": {
1670
1836
  const text = typeof prop.content === "string" ? prop.content : "";
1671
1837
  const urlObj = isRecord2(prop.url) ? prop.url : void 0;
1672
1838
  const url = urlObj && typeof urlObj.url === "string" ? urlObj.url : "";
1673
- return { content: `[${text}](${url})`, bold, color };
1839
+ return { content: `[${text}](${url})`, bold, color, colorKind };
1674
1840
  }
1675
1841
  case "at_all":
1676
1842
  return { content: "<at id=all></at>", bold, color };
@@ -1710,10 +1876,8 @@ function childToMarkdown(child, ctx) {
1710
1876
  case "plain_text": {
1711
1877
  const content = typeof prop.content === "string" ? prop.content : "";
1712
1878
  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);
1879
+ const { bold, color, colorKind } = resolveInlineStyle(ts, ctx);
1880
+ return wrapEmphasis(content, bold, color, colorKind, colorKind === "custom");
1717
1881
  }
1718
1882
  case "br":
1719
1883
  return "\n";
@@ -1839,7 +2003,7 @@ ${"#".repeat(level)} ${body}
1839
2003
  if (!data || data.tag !== "markdown") return "";
1840
2004
  const cellProp = isRecord2(data.property) ? data.property : {};
1841
2005
  const inner = Array.isArray(cellProp.elements) ? cellProp.elements : [];
1842
- const text = renderChildren(inner, ctx);
2006
+ const { content: text } = renderChildren(inner, ctx);
1843
2007
  return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
1844
2008
  });
1845
2009
  return `| ${cells.join(" | ")} |`;
@@ -1962,10 +2126,10 @@ function rawToDsl(raw) {
1962
2126
  }
1963
2127
  }
1964
2128
  const attachment = isRecord2(raw.json_attachment) ? raw.json_attachment : {};
1965
- const ctx = {
2129
+ const ctx = makeCtx({
1966
2130
  images: isRecord2(attachment.images) ? attachment.images : {},
1967
2131
  atUsers: isRecord2(attachment.at_users) ? attachment.at_users : {}
1968
- };
2132
+ });
1969
2133
  return convertRoot(inner, ctx);
1970
2134
  }
1971
2135
  if (isRecord2(raw.card)) {
@@ -1973,10 +2137,10 @@ function rawToDsl(raw) {
1973
2137
  if (typeof card.json_card === "string" || isRecord2(card.json_card)) {
1974
2138
  return rawToDsl(card);
1975
2139
  }
1976
- return convertRoot(card, { images: {}, atUsers: {} });
2140
+ return convertRoot(card, makeCtx());
1977
2141
  }
1978
2142
  if ("body" in raw || "header" in raw || "schema" in raw) {
1979
- return convertRoot(raw, { images: {}, atUsers: {} });
2143
+ return convertRoot(raw, makeCtx());
1980
2144
  }
1981
2145
  return raw;
1982
2146
  }
@@ -1985,4 +2149,4 @@ function degradeUnknown(el) {
1985
2149
  return { tag: "unknown", raw_tag: rawTag, fallback: el };
1986
2150
  }
1987
2151
 
1988
- export { degradeUnknown, dslToRaw, isRawCardEnvelope, normalizeStoredCardColors, rawCardToDsl, rawToDsl, readThemeColorLight, resolveCustomBackgroundLight, resolveTextStyleColor };
2152
+ 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,CAiGN"}
@@ -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;AAwkDD;;;;;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.4",
4
4
  "description": "公开 DSL 与飞书内部 RAW JSON 的双向适配器;raw→DSL 对齐 feishu-raw-card-to-dsl",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",