@open-feishu-card/adapter 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1861,8 +1861,109 @@ function degradeUnknown(el) {
1861
1861
  return { tag: "unknown", raw_tag: rawTag, fallback: el };
1862
1862
  }
1863
1863
 
1864
+ // src/normalize-stored-colors.ts
1865
+ function hexColorToRgba(hex) {
1866
+ const h = hex.trim().replace(/^#/, "");
1867
+ if (h.length === 8) {
1868
+ const r = parseInt(h.slice(0, 2), 16);
1869
+ const g = parseInt(h.slice(2, 4), 16);
1870
+ const b = parseInt(h.slice(4, 6), 16);
1871
+ const a = Math.round(parseInt(h.slice(6, 8), 16) / 255 * 100) / 100;
1872
+ return `rgba(${r},${g},${b},${a})`;
1873
+ }
1874
+ if (h.length === 6) {
1875
+ const r = parseInt(h.slice(0, 2), 16);
1876
+ const g = parseInt(h.slice(2, 4), 16);
1877
+ const b = parseInt(h.slice(4, 6), 16);
1878
+ return `rgba(${r},${g},${b},1)`;
1879
+ }
1880
+ return void 0;
1881
+ }
1882
+ function readCustomBackgroundStyle(node) {
1883
+ const raw = node.custom_background_style;
1884
+ if (!raw || typeof raw !== "object") return void 0;
1885
+ const o = raw;
1886
+ const lightRaw = typeof o.light_mode === "string" ? o.light_mode : void 0;
1887
+ const darkRaw = typeof o.dark_mode === "string" ? o.dark_mode : void 0;
1888
+ if (!lightRaw || !darkRaw) return void 0;
1889
+ const light = lightRaw.startsWith("#") ? hexColorToRgba(lightRaw) : lightRaw;
1890
+ const dark = darkRaw.startsWith("#") ? hexColorToRgba(darkRaw) : darkRaw;
1891
+ if (!light || !dark) return void 0;
1892
+ return { light_mode: light, dark_mode: dark };
1893
+ }
1894
+ function normalizeStoredCardColors(dsl) {
1895
+ const colorEntries = {};
1896
+ const styleKey = (entry) => `${entry.light_mode}|${entry.dark_mode}`;
1897
+ const keyToAlias = /* @__PURE__ */ new Map();
1898
+ let bgSeq = 0;
1899
+ const ensureAlias = (prefix, entry) => {
1900
+ const existing2 = keyToAlias.get(styleKey(entry));
1901
+ if (existing2) return existing2;
1902
+ const alias = `${prefix}-${bgSeq++}`;
1903
+ colorEntries[alias] = entry;
1904
+ keyToAlias.set(styleKey(entry), alias);
1905
+ return alias;
1906
+ };
1907
+ const walk = (node) => {
1908
+ if (Array.isArray(node)) {
1909
+ for (const child of node) walk(child);
1910
+ return;
1911
+ }
1912
+ if (!node || typeof node !== "object") return;
1913
+ const obj = node;
1914
+ if (obj.tag === "column") {
1915
+ const custom = readCustomBackgroundStyle(obj);
1916
+ if (custom) {
1917
+ obj.background_style = ensureAlias("cus-bg", custom);
1918
+ delete obj.custom_background_style;
1919
+ }
1920
+ }
1921
+ for (const value of Object.values(obj)) walk(value);
1922
+ };
1923
+ walk(dsl.body);
1924
+ if (Object.keys(colorEntries).length === 0) return;
1925
+ const firstBg = colorEntries["cus-bg-0"] ?? Object.values(colorEntries)[0];
1926
+ if (firstBg) {
1927
+ const m = firstBg.light_mode.match(/^rgba\((\d+),(\d+),(\d+),/);
1928
+ if (m) {
1929
+ colorEntries["cus-accent"] = {
1930
+ light_mode: `rgba(${m[1]},${m[2]},${m[3]},1)`,
1931
+ dark_mode: firstBg.dark_mode.replace(/,\s*[\d.]+\)$/, ",1)")
1932
+ };
1933
+ }
1934
+ }
1935
+ colorEntries["cus-muted"] ??= {
1936
+ light_mode: "rgba(100,106,115,1)",
1937
+ dark_mode: "rgba(166,166,166,1)"
1938
+ };
1939
+ const config = dsl.config && typeof dsl.config === "object" ? dsl.config : {};
1940
+ dsl.config = config;
1941
+ const style = config.style && typeof config.style === "object" ? config.style : {};
1942
+ config.style = style;
1943
+ const existing = style.color && typeof style.color === "object" ? style.color : {};
1944
+ style.color = { ...colorEntries, ...existing };
1945
+ if (!colorEntries["cus-accent"]) return;
1946
+ const applyDefaultFontColors = (node) => {
1947
+ if (Array.isArray(node)) {
1948
+ for (const child of node) applyDefaultFontColors(child);
1949
+ return;
1950
+ }
1951
+ if (!node || typeof node !== "object") return;
1952
+ const obj = node;
1953
+ if (obj.tag === "markdown" && obj.font_color === void 0) {
1954
+ const size = obj.text_size;
1955
+ const content = typeof obj.content === "string" ? obj.content : "";
1956
+ if (size === "small") obj.font_color = "cus-muted";
1957
+ else if (size === "heading" || content.includes("**")) obj.font_color = "cus-accent";
1958
+ }
1959
+ for (const value of Object.values(obj)) applyDefaultFontColors(value);
1960
+ };
1961
+ applyDefaultFontColors(dsl.body);
1962
+ }
1963
+
1864
1964
  exports.degradeUnknown = degradeUnknown;
1865
1965
  exports.dslToRaw = dslToRaw;
1866
1966
  exports.isRawCardEnvelope = isRawCardEnvelope;
1967
+ exports.normalizeStoredCardColors = normalizeStoredCardColors;
1867
1968
  exports.rawCardToDsl = rawCardToDsl;
1868
1969
  exports.rawToDsl = rawToDsl;
package/dist/index.d.ts CHANGED
@@ -8,5 +8,7 @@
8
8
  */
9
9
  export { dslToRaw } from './dsl-to-raw.js';
10
10
  export { rawToDsl, rawCardToDsl, isRawCardEnvelope, degradeUnknown, } from './raw-to-dsl.js';
11
+ export { normalizeStoredCardColors } from './normalize-stored-colors.js';
12
+ export type { CustomColorEntry } from './normalize-stored-colors.js';
11
13
  export type { CardElement, ElementProperty, JsonCard } from './types.js';
12
14
  //# 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,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,MAAM,8BAA8B,CAAC;AACzE,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1859,4 +1859,104 @@ function degradeUnknown(el) {
1859
1859
  return { tag: "unknown", raw_tag: rawTag, fallback: el };
1860
1860
  }
1861
1861
 
1862
- export { degradeUnknown, dslToRaw, isRawCardEnvelope, rawCardToDsl, rawToDsl };
1862
+ // src/normalize-stored-colors.ts
1863
+ function hexColorToRgba(hex) {
1864
+ const h = hex.trim().replace(/^#/, "");
1865
+ if (h.length === 8) {
1866
+ const r = parseInt(h.slice(0, 2), 16);
1867
+ const g = parseInt(h.slice(2, 4), 16);
1868
+ const b = parseInt(h.slice(4, 6), 16);
1869
+ const a = Math.round(parseInt(h.slice(6, 8), 16) / 255 * 100) / 100;
1870
+ return `rgba(${r},${g},${b},${a})`;
1871
+ }
1872
+ if (h.length === 6) {
1873
+ const r = parseInt(h.slice(0, 2), 16);
1874
+ const g = parseInt(h.slice(2, 4), 16);
1875
+ const b = parseInt(h.slice(4, 6), 16);
1876
+ return `rgba(${r},${g},${b},1)`;
1877
+ }
1878
+ return void 0;
1879
+ }
1880
+ function readCustomBackgroundStyle(node) {
1881
+ const raw = node.custom_background_style;
1882
+ if (!raw || typeof raw !== "object") return void 0;
1883
+ const o = raw;
1884
+ const lightRaw = typeof o.light_mode === "string" ? o.light_mode : void 0;
1885
+ const darkRaw = typeof o.dark_mode === "string" ? o.dark_mode : void 0;
1886
+ if (!lightRaw || !darkRaw) return void 0;
1887
+ const light = lightRaw.startsWith("#") ? hexColorToRgba(lightRaw) : lightRaw;
1888
+ const dark = darkRaw.startsWith("#") ? hexColorToRgba(darkRaw) : darkRaw;
1889
+ if (!light || !dark) return void 0;
1890
+ return { light_mode: light, dark_mode: dark };
1891
+ }
1892
+ function normalizeStoredCardColors(dsl) {
1893
+ const colorEntries = {};
1894
+ const styleKey = (entry) => `${entry.light_mode}|${entry.dark_mode}`;
1895
+ const keyToAlias = /* @__PURE__ */ new Map();
1896
+ let bgSeq = 0;
1897
+ const ensureAlias = (prefix, entry) => {
1898
+ const existing2 = keyToAlias.get(styleKey(entry));
1899
+ if (existing2) return existing2;
1900
+ const alias = `${prefix}-${bgSeq++}`;
1901
+ colorEntries[alias] = entry;
1902
+ keyToAlias.set(styleKey(entry), alias);
1903
+ return alias;
1904
+ };
1905
+ const walk = (node) => {
1906
+ if (Array.isArray(node)) {
1907
+ for (const child of node) walk(child);
1908
+ return;
1909
+ }
1910
+ if (!node || typeof node !== "object") return;
1911
+ const obj = node;
1912
+ if (obj.tag === "column") {
1913
+ const custom = readCustomBackgroundStyle(obj);
1914
+ if (custom) {
1915
+ obj.background_style = ensureAlias("cus-bg", custom);
1916
+ delete obj.custom_background_style;
1917
+ }
1918
+ }
1919
+ for (const value of Object.values(obj)) walk(value);
1920
+ };
1921
+ walk(dsl.body);
1922
+ if (Object.keys(colorEntries).length === 0) return;
1923
+ const firstBg = colorEntries["cus-bg-0"] ?? Object.values(colorEntries)[0];
1924
+ if (firstBg) {
1925
+ const m = firstBg.light_mode.match(/^rgba\((\d+),(\d+),(\d+),/);
1926
+ if (m) {
1927
+ colorEntries["cus-accent"] = {
1928
+ light_mode: `rgba(${m[1]},${m[2]},${m[3]},1)`,
1929
+ dark_mode: firstBg.dark_mode.replace(/,\s*[\d.]+\)$/, ",1)")
1930
+ };
1931
+ }
1932
+ }
1933
+ colorEntries["cus-muted"] ??= {
1934
+ light_mode: "rgba(100,106,115,1)",
1935
+ dark_mode: "rgba(166,166,166,1)"
1936
+ };
1937
+ const config = dsl.config && typeof dsl.config === "object" ? dsl.config : {};
1938
+ dsl.config = config;
1939
+ const style = config.style && typeof config.style === "object" ? config.style : {};
1940
+ config.style = style;
1941
+ const existing = style.color && typeof style.color === "object" ? style.color : {};
1942
+ style.color = { ...colorEntries, ...existing };
1943
+ if (!colorEntries["cus-accent"]) return;
1944
+ const applyDefaultFontColors = (node) => {
1945
+ if (Array.isArray(node)) {
1946
+ for (const child of node) applyDefaultFontColors(child);
1947
+ return;
1948
+ }
1949
+ if (!node || typeof node !== "object") return;
1950
+ const obj = node;
1951
+ if (obj.tag === "markdown" && obj.font_color === void 0) {
1952
+ const size = obj.text_size;
1953
+ const content = typeof obj.content === "string" ? obj.content : "";
1954
+ if (size === "small") obj.font_color = "cus-muted";
1955
+ else if (size === "heading" || content.includes("**")) obj.font_color = "cus-accent";
1956
+ }
1957
+ for (const value of Object.values(obj)) applyDefaultFontColors(value);
1958
+ };
1959
+ applyDefaultFontColors(dsl.body);
1960
+ }
1961
+
1962
+ export { degradeUnknown, dslToRaw, isRawCardEnvelope, normalizeStoredCardColors, rawCardToDsl, rawToDsl };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 飞书存储卡片时会将 config.style.color 的 cus-N 展开为 custom_background_style(#RRGGBBAA),
3
+ * 并丢弃 config.style 与 markdown.font_color。回读底稿后须 normalize,否则 modify 再发会丢色。
4
+ */
5
+ export type CustomColorEntry = {
6
+ light_mode: string;
7
+ dark_mode: string;
8
+ };
9
+ /**
10
+ * 将飞书回读的 custom_background_style 还原为可再次发送的 background_style cus-N + config.style.color。
11
+ * 若 markdown 缺少 font_color,按 text_size / 加粗内容补 cus-accent / cus-muted(飞书回读会丢 font_color)。
12
+ */
13
+ export declare function normalizeStoredCardColors(dsl: Record<string, unknown>): void;
14
+ //# sourceMappingURL=normalize-stored-colors.d.ts.map
@@ -0,0 +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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/adapter",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "公开 DSL 与飞书内部 RAW JSON 的双向适配器;raw→DSL 对齐 feishu-raw-card-to-dsl",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -21,7 +21,8 @@
21
21
  "tsup": "^8.3.5",
22
22
  "tsx": "^4.19.3",
23
23
  "typescript": "^7.0.2",
24
- "vitest": "^2.1.9"
24
+ "vitest": "^2.1.9",
25
+ "@open-feishu-card/schema": "0.1.4"
25
26
  },
26
27
  "license": "MIT",
27
28
  "publishConfig": {