@open-feishu-card/renderer 0.2.29 → 0.2.31

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
@@ -671,7 +671,8 @@ function resolveImageSrc(p, ctx) {
671
671
  if (p.src) return p.src;
672
672
  if (p.url) return resolveURL(p.url) || null;
673
673
  if (p.img_url) return p.img_url;
674
- if (p.imageID && ctx.options.imageBaseURL) return ctx.options.imageBaseURL + p.imageID;
674
+ const imageId = p.imageID || p.img_key;
675
+ if (imageId && ctx.options.imageBaseURL) return ctx.options.imageBaseURL + imageId;
675
676
  return null;
676
677
  }
677
678
  function resolveMediaSrc(p, kind, ctx) {
@@ -701,6 +702,7 @@ function personAvatarSrc(person, ctx) {
701
702
  const r = ctx.options.resolvePersonAvatar(person);
702
703
  if (r) return r;
703
704
  }
705
+ if (person.avatar_url) return person.avatar_url;
704
706
  if (!person.avatar_key) return null;
705
707
  const base = ctx.options.avatarBaseURL || PERSON_AVATAR_BASE;
706
708
  const suffix = ctx.options.avatarURLSuffix ?? PERSON_AVATAR_SUFFIX;
@@ -2017,6 +2019,55 @@ var BASE_STYLES_CSS = `/**
2017
2019
  }
2018
2020
  .universal-card-root.theme-dark .universal-card-markdown { color: #d6d6d6; }
2019
2021
  .universal-card-root.theme-dark .universal-card-hr { background-color: #2e2e30; }
2022
+
2023
+ /* ---- Toast \u5BBF\u4E3B\uFF08\u5BF9\u9F50\u5BA2\u6237\u7AEF .ud__toast.ud__notice\uFF1A\u9876\u90E8\u4E0B\u62C9\u3001\u6DF1\u8272\u80F6\u56CA\uFF09 ---- */
2024
+ .universal-card-toast-host {
2025
+ position: fixed;
2026
+ top: 32px; /* \u5BA2\u6237\u7AEF\u7EC4\u4EF6\u914D\u7F6E toast:{top:32} */
2027
+ left: 50%;
2028
+ transform: translateX(-50%);
2029
+ display: flex;
2030
+ flex-direction: column;
2031
+ align-items: center;
2032
+ z-index: 300;
2033
+ pointer-events: none;
2034
+ }
2035
+ .universal-card-toast {
2036
+ display: inline-flex;
2037
+ align-items: center;
2038
+ gap: 8px;
2039
+ max-width: 600px; /* \u5BA2\u6237\u7AEF .ud__toast max-width:600px */
2040
+ padding: 10px 16px;
2041
+ border-radius: 8px;
2042
+ background: rgba(31, 35, 41, 0.92); /* N900 \u6DF1\u8272\u534A\u900F\u660E\uFF0C\u767D\u5B57 */
2043
+ color: #fff;
2044
+ font-size: 14px;
2045
+ line-height: 1.5;
2046
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.16);
2047
+ margin-bottom: 16px; /* \u5BA2\u6237\u7AEF .ud__toast margin-bottom:16px */
2048
+ animation: universal-card-toast-in 0.3s cubic-bezier(0.2, 0, 0, 1);
2049
+ }
2050
+ .universal-card-toast--leave {
2051
+ animation: universal-card-toast-out 0.3s cubic-bezier(0.2, 0, 0, 1) forwards;
2052
+ }
2053
+ .universal-card-toast__icon {
2054
+ flex: none;
2055
+ display: inline-flex;
2056
+ }
2057
+ .universal-card-toast__text {
2058
+ display: -webkit-box;
2059
+ -webkit-box-orient: vertical;
2060
+ overflow: hidden;
2061
+ word-break: break-word;
2062
+ }
2063
+ @keyframes universal-card-toast-in {
2064
+ from { opacity: 0; transform: translateY(-100%); }
2065
+ to { opacity: 1; transform: translateY(0); }
2066
+ }
2067
+ @keyframes universal-card-toast-out {
2068
+ from { opacity: 1; transform: translateY(0); }
2069
+ to { opacity: 0; transform: translateY(-100%); margin-bottom: 0; }
2070
+ }
2020
2071
  `;
2021
2072
 
2022
2073
  // src/icon-font.ts
@@ -6816,6 +6867,60 @@ function feishuCardStyleTag() {
6816
6867
  return `<style data-feishu-card>${ALL_CSS}</style>`;
6817
6868
  }
6818
6869
 
6870
+ // src/toast.ts
6871
+ var DEFAULT_SHOW_TIME = 2e3;
6872
+ var TYPE_ICON = {
6873
+ success: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="#34c724"/><path d="M4.5 8.2l2.2 2.2 4.8-4.8" fill="none" stroke="#fff" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>',
6874
+ info: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="#3370ff"/><circle cx="8" cy="4.6" r="1" fill="#fff"/><rect x="7.2" y="7" width="1.6" height="4.2" rx="0.8" fill="#fff"/></svg>',
6875
+ warning: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="#fad355"/><rect x="7.2" y="3.8" width="1.6" height="4.6" rx="0.8" fill="#1f2329"/><circle cx="8" cy="11.8" r="1" fill="#1f2329"/></svg>',
6876
+ error: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="#f54a45"/><path d="M5.6 5.6l4.8 4.8M10.4 5.6l-4.8 4.8" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>'
6877
+ };
6878
+ function escapeHTML(s) {
6879
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6880
+ }
6881
+ function renderToastHTML(type, opts) {
6882
+ const maxLines = opts.maxLines ?? 1;
6883
+ const styleAttr = maxLines > 1 ? ` style="-webkit-line-clamp:${maxLines};"` : "";
6884
+ return `<div class="universal-card-toast universal-card-toast--${type}" role="status" aria-live="polite"><span class="universal-card-toast__icon">${TYPE_ICON[type]}</span><span class="universal-card-toast__text"${styleAttr}>${escapeHTML(opts.content)}</span></div>`;
6885
+ }
6886
+ function createToastHost(container) {
6887
+ const hostEl = typeof document === "undefined" ? null : container ?? document.body;
6888
+ const ensureHost = () => {
6889
+ if (!hostEl) return null;
6890
+ let el = hostEl.querySelector(".universal-card-toast-host");
6891
+ if (!el) {
6892
+ el = document.createElement("div");
6893
+ el.className = "universal-card-toast-host";
6894
+ hostEl.appendChild(el);
6895
+ }
6896
+ return el;
6897
+ };
6898
+ const push = (type) => (opts) => {
6899
+ const host = ensureHost();
6900
+ if (!host || typeof document === "undefined") return;
6901
+ const wrap = document.createElement("div");
6902
+ wrap.style.cssText = "display:contents";
6903
+ wrap.innerHTML = renderToastHTML(type, opts);
6904
+ const toastEl = wrap.firstElementChild;
6905
+ host.appendChild(toastEl);
6906
+ const showTime = opts.showTime ?? DEFAULT_SHOW_TIME;
6907
+ setTimeout(() => {
6908
+ toastEl.classList.add("universal-card-toast--leave");
6909
+ toastEl.addEventListener("animationend", () => toastEl.remove(), { once: true });
6910
+ setTimeout(() => toastEl.remove(), 400);
6911
+ }, showTime);
6912
+ };
6913
+ return {
6914
+ success: push("success"),
6915
+ info: push("info"),
6916
+ warning: push("warning"),
6917
+ error: push("error"),
6918
+ destroy: () => {
6919
+ hostEl?.querySelectorAll(".universal-card-toast-host .universal-card-toast").forEach((el) => el.remove());
6920
+ }
6921
+ };
6922
+ }
6923
+
6819
6924
  // src/remote.ts
6820
6925
  var DEFAULT_API_BASE = "https://open.feishu.cn/open-apis/im/v1";
6821
6926
  function getFetch(options) {
@@ -7072,6 +7177,7 @@ exports.HEADER_BG_LIGHT = HEADER_BG_LIGHT;
7072
7177
  exports.ICON_CODEPOINTS = ICON_CODEPOINTS;
7073
7178
  exports.ICON_FONT_CSS = ICON_FONT_CSS;
7074
7179
  exports.arrayBufferToBase64 = arrayBufferToBase64;
7180
+ exports.createToastHost = createToastHost;
7075
7181
  exports.feishuAudioHydrateJs = feishuAudioHydrateJs;
7076
7182
  exports.feishuAudioHydrateScript = feishuAudioHydrateScript;
7077
7183
  exports.feishuCardStyleTag = feishuCardStyleTag;
@@ -7095,3 +7201,4 @@ exports.renderCardToStringAsync = renderCardToStringAsync;
7095
7201
  exports.renderDsl = renderDsl;
7096
7202
  exports.renderDslToString = renderDslToString;
7097
7203
  exports.renderFeishuChart = renderFeishuChart;
7204
+ exports.renderToastHTML = renderToastHTML;
package/dist/index.d.cts CHANGED
@@ -81,6 +81,8 @@ interface ElementProperty {
81
81
  hoverTips?: CardElement;
82
82
  hasBorder?: boolean;
83
83
  imageID?: string;
84
+ /** DSL 公开字段;dslToRaw 常原样留在 property(未改名为 imageID) */
85
+ img_key?: string;
84
86
  origin_key?: string;
85
87
  src?: string;
86
88
  url?: string | LinkURL;
@@ -291,7 +293,7 @@ interface RenderOptions {
291
293
  /** 音视频地址前缀:最终 src = mediaBaseURL + fileID */
292
294
  mediaBaseURL?: string;
293
295
  /**
294
- * 人员表(open_id -> { content 姓名, avatar_key 头像 })。
296
+ * 人员表(open_id -> { content 姓名, avatar_key / avatar_url 头像 })。
295
297
  * person_v1 / person_list 元素只带 ID,姓名/头像在此反查。
296
298
  * 异步渲染(renderCardToStringAsync)会自动从 json_attachment.persons 填入;
297
299
  * 同步渲染若想出名字/头像,需手动传入。缺省时人员降级为固定头像 +「赵天雄」。
@@ -326,7 +328,8 @@ interface RenderOptions {
326
328
  * 人员信息:person_v1 / person_list / at 元素只带 ID,姓名与头像 key 在这里。
327
329
  * 取自飞书消息 content 的 json_attachment.persons(按 open_id「ou_...」索引);
328
330
  * fetchCardMessage 还会把信封 mentions 与 at_users 合并进来(含 @_user_N 别名键)。
329
- * avatar_key 是公开可访问的头像图 key(无需鉴权,见 PERSON_AVATAR_BASE)。
331
+ * avatar_key 是公开可访问的头像图 key(无需鉴权,见 PERSON_AVATAR_BASE);
332
+ * 本地预览也可用 avatar_url(含 data URI)直接给头像地址。
330
333
  */
331
334
  interface PersonInfo {
332
335
  person_id?: string;
@@ -335,6 +338,11 @@ interface PersonInfo {
335
338
  type?: number;
336
339
  /** 头像图 key(形如 v3_00te_...),拼 avatarBaseURL 取图 */
337
340
  avatar_key?: string;
341
+ /**
342
+ * 直接可用的头像 URL(含 data URI)。
343
+ * 本地预览虚拟用户没有飞书 avatar_key 时用这个;优先级低于 resolvePersonAvatar,高于 avatar_key。
344
+ */
345
+ avatar_url?: string;
338
346
  }
339
347
  /**
340
348
  * 消息信封上的 @ 提及(data.items[0].mentions),不在 body.content 内。
@@ -606,6 +614,45 @@ declare function feishuAudioHydrateJs(): string;
606
614
  */
607
615
  declare const CHART_PALETTE: string[];
608
616
 
617
+ /**
618
+ * Toast 宿主(对齐飞书客户端 kernel.toast)。
619
+ *
620
+ * 事实依据(feishu-card-extract,pcsdk 7.72.0):
621
+ * - 卡片 action 请求响应里带 `toast: { code, content, ... }`(如复制成功、表单校验失败、
622
+ * 错误码提示),Kernel 收到后调 `options.toast.success/info/warning/error` 弹出。
623
+ * - 调用形态:`{ type: "TOAST_SUCCESS", content, showTime: 2e3, maxLines: 1 }`。
624
+ * - DOM 为 `.ud__toast.ud__notice`:顶部下拉动画(translateY(-100%)→0)、
625
+ * max-width:600px、box-shadow、margin-bottom:16px、top:32px。
626
+ *
627
+ * renderer 是 SSR 字符串渲染器,此处提供浏览器侧 toast 宿主:预览交互里模拟
628
+ * action 响应(如按钮点击回调)时调用对应方法,视觉与客户端一致。
629
+ */
630
+ type ToastType = 'success' | 'info' | 'warning' | 'error';
631
+ interface ToastOptions {
632
+ /** toast 文本内容 */
633
+ content: string;
634
+ /** 显示时长 ms,默认 2000(客户端 showTime 常见值 2e3) */
635
+ showTime?: number;
636
+ /** 最大行数,超出截断省略,默认 1(客户端 maxLines:1) */
637
+ maxLines?: number;
638
+ }
639
+ interface ToastAPI {
640
+ success: (opts: ToastOptions) => void;
641
+ info: (opts: ToastOptions) => void;
642
+ warning: (opts: ToastOptions) => void;
643
+ error: (opts: ToastOptions) => void;
644
+ /** 清空当前所有 toast */
645
+ destroy: () => void;
646
+ }
647
+ /** 生成单个 toast 的 HTML 字符串(纯函数,SSR/测试可用) */
648
+ declare function renderToastHTML(type: ToastType, opts: ToastOptions): string;
649
+ /**
650
+ * 创建 toast 宿主(浏览器侧)。容器挂在 container(默认 document.body),
651
+ * fixed 顶部居中;每次调用追加一条 toast,showTime 后自动移除。
652
+ * Node/SSR 环境无 DOM 时返回 no-op API(不抛错),仅可用 renderToastHTML。
653
+ */
654
+ declare function createToastHost(container?: HTMLElement | null): ToastAPI;
655
+
609
656
  /**
610
657
  * 异步渲染:用飞书 access token 自动拉取卡片图片并内联(data URI),
611
658
  * 或直接按 message_id 把整条消息拉回来再渲染。
@@ -681,4 +728,4 @@ declare function renderDslToString(dsl: unknown, options?: RenderOptions): strin
681
728
  /** 输入公开 DSL,渲染为真实 DOM 节点(浏览器)。= renderCard(dslToRaw(dsl)) */
682
729
  declare function renderDsl(dsl: unknown, options?: RenderOptions): Node;
683
730
 
684
- export { ALL_CSS, AUDIO_HYDRATED_ATTR, AUDIO_PAUSE_ICON_SVG, AUDIO_PLAY_ICON_SVG, type AsyncRenderOptions, type AtUserInfo, type BoxSpacing, CHART_HYDRATED_ATTR, CHART_PALETTE, CHART_SPEC_ATTR, type CardElement, type CardInput, type CardMessageInput, type ComponentFn, EMOJI_KEY_TO_NAME, EMOJI_SPRITE_CSS, type ElementProperty, type FetchedCardMessage, HEADER_BG_DARK, HEADER_BG_LIGHT, type HeaderTemplate, ICON_CODEPOINTS, ICON_FONT_CSS, type JsonAttachment, type JsonCard, type LinkURL, type ListItem, type MessageMention, type NamedColor, type PersonInfo, type RenderContext, type RenderOptions, type SizeName, type TableColumn, type TableHeaderStyle, type TextAlign, type TextAttribute, type TextStyle, type VNode, type VNodeChild, type VNodeProps, arrayBufferToBase64, feishuAudioHydrateJs, feishuAudioHydrateScript, feishuCardStyleTag, feishuChartHydrateJs, feishuChartHydrateScript, fetchCardMessage, fetchImageDataUri, getComponent, h, hydrateFeishuAudios, hydrateFeishuCharts, mergeMentionsIntoPersons, parseInline, parseMarkdown, prefetchImages, registerComponent, renderCard, renderCardAsync, renderCardToString, renderCardToStringAsync, renderDsl, renderDslToString, renderFeishuChart };
731
+ export { ALL_CSS, AUDIO_HYDRATED_ATTR, AUDIO_PAUSE_ICON_SVG, AUDIO_PLAY_ICON_SVG, type AsyncRenderOptions, type AtUserInfo, type BoxSpacing, CHART_HYDRATED_ATTR, CHART_PALETTE, CHART_SPEC_ATTR, type CardElement, type CardInput, type CardMessageInput, type ComponentFn, EMOJI_KEY_TO_NAME, EMOJI_SPRITE_CSS, type ElementProperty, type FetchedCardMessage, HEADER_BG_DARK, HEADER_BG_LIGHT, type HeaderTemplate, ICON_CODEPOINTS, ICON_FONT_CSS, type JsonAttachment, type JsonCard, type LinkURL, type ListItem, type MessageMention, type NamedColor, type PersonInfo, type RenderContext, type RenderOptions, type SizeName, type TableColumn, type TableHeaderStyle, type TextAlign, type TextAttribute, type TextStyle, type ToastAPI, type ToastOptions, type ToastType, type VNode, type VNodeChild, type VNodeProps, arrayBufferToBase64, createToastHost, feishuAudioHydrateJs, feishuAudioHydrateScript, feishuCardStyleTag, feishuChartHydrateJs, feishuChartHydrateScript, fetchCardMessage, fetchImageDataUri, getComponent, h, hydrateFeishuAudios, hydrateFeishuCharts, mergeMentionsIntoPersons, parseInline, parseMarkdown, prefetchImages, registerComponent, renderCard, renderCardAsync, renderCardToString, renderCardToStringAsync, renderDsl, renderDslToString, renderFeishuChart, renderToastHTML };
package/dist/index.d.ts CHANGED
@@ -81,6 +81,8 @@ interface ElementProperty {
81
81
  hoverTips?: CardElement;
82
82
  hasBorder?: boolean;
83
83
  imageID?: string;
84
+ /** DSL 公开字段;dslToRaw 常原样留在 property(未改名为 imageID) */
85
+ img_key?: string;
84
86
  origin_key?: string;
85
87
  src?: string;
86
88
  url?: string | LinkURL;
@@ -291,7 +293,7 @@ interface RenderOptions {
291
293
  /** 音视频地址前缀:最终 src = mediaBaseURL + fileID */
292
294
  mediaBaseURL?: string;
293
295
  /**
294
- * 人员表(open_id -> { content 姓名, avatar_key 头像 })。
296
+ * 人员表(open_id -> { content 姓名, avatar_key / avatar_url 头像 })。
295
297
  * person_v1 / person_list 元素只带 ID,姓名/头像在此反查。
296
298
  * 异步渲染(renderCardToStringAsync)会自动从 json_attachment.persons 填入;
297
299
  * 同步渲染若想出名字/头像,需手动传入。缺省时人员降级为固定头像 +「赵天雄」。
@@ -326,7 +328,8 @@ interface RenderOptions {
326
328
  * 人员信息:person_v1 / person_list / at 元素只带 ID,姓名与头像 key 在这里。
327
329
  * 取自飞书消息 content 的 json_attachment.persons(按 open_id「ou_...」索引);
328
330
  * fetchCardMessage 还会把信封 mentions 与 at_users 合并进来(含 @_user_N 别名键)。
329
- * avatar_key 是公开可访问的头像图 key(无需鉴权,见 PERSON_AVATAR_BASE)。
331
+ * avatar_key 是公开可访问的头像图 key(无需鉴权,见 PERSON_AVATAR_BASE);
332
+ * 本地预览也可用 avatar_url(含 data URI)直接给头像地址。
330
333
  */
331
334
  interface PersonInfo {
332
335
  person_id?: string;
@@ -335,6 +338,11 @@ interface PersonInfo {
335
338
  type?: number;
336
339
  /** 头像图 key(形如 v3_00te_...),拼 avatarBaseURL 取图 */
337
340
  avatar_key?: string;
341
+ /**
342
+ * 直接可用的头像 URL(含 data URI)。
343
+ * 本地预览虚拟用户没有飞书 avatar_key 时用这个;优先级低于 resolvePersonAvatar,高于 avatar_key。
344
+ */
345
+ avatar_url?: string;
338
346
  }
339
347
  /**
340
348
  * 消息信封上的 @ 提及(data.items[0].mentions),不在 body.content 内。
@@ -606,6 +614,45 @@ declare function feishuAudioHydrateJs(): string;
606
614
  */
607
615
  declare const CHART_PALETTE: string[];
608
616
 
617
+ /**
618
+ * Toast 宿主(对齐飞书客户端 kernel.toast)。
619
+ *
620
+ * 事实依据(feishu-card-extract,pcsdk 7.72.0):
621
+ * - 卡片 action 请求响应里带 `toast: { code, content, ... }`(如复制成功、表单校验失败、
622
+ * 错误码提示),Kernel 收到后调 `options.toast.success/info/warning/error` 弹出。
623
+ * - 调用形态:`{ type: "TOAST_SUCCESS", content, showTime: 2e3, maxLines: 1 }`。
624
+ * - DOM 为 `.ud__toast.ud__notice`:顶部下拉动画(translateY(-100%)→0)、
625
+ * max-width:600px、box-shadow、margin-bottom:16px、top:32px。
626
+ *
627
+ * renderer 是 SSR 字符串渲染器,此处提供浏览器侧 toast 宿主:预览交互里模拟
628
+ * action 响应(如按钮点击回调)时调用对应方法,视觉与客户端一致。
629
+ */
630
+ type ToastType = 'success' | 'info' | 'warning' | 'error';
631
+ interface ToastOptions {
632
+ /** toast 文本内容 */
633
+ content: string;
634
+ /** 显示时长 ms,默认 2000(客户端 showTime 常见值 2e3) */
635
+ showTime?: number;
636
+ /** 最大行数,超出截断省略,默认 1(客户端 maxLines:1) */
637
+ maxLines?: number;
638
+ }
639
+ interface ToastAPI {
640
+ success: (opts: ToastOptions) => void;
641
+ info: (opts: ToastOptions) => void;
642
+ warning: (opts: ToastOptions) => void;
643
+ error: (opts: ToastOptions) => void;
644
+ /** 清空当前所有 toast */
645
+ destroy: () => void;
646
+ }
647
+ /** 生成单个 toast 的 HTML 字符串(纯函数,SSR/测试可用) */
648
+ declare function renderToastHTML(type: ToastType, opts: ToastOptions): string;
649
+ /**
650
+ * 创建 toast 宿主(浏览器侧)。容器挂在 container(默认 document.body),
651
+ * fixed 顶部居中;每次调用追加一条 toast,showTime 后自动移除。
652
+ * Node/SSR 环境无 DOM 时返回 no-op API(不抛错),仅可用 renderToastHTML。
653
+ */
654
+ declare function createToastHost(container?: HTMLElement | null): ToastAPI;
655
+
609
656
  /**
610
657
  * 异步渲染:用飞书 access token 自动拉取卡片图片并内联(data URI),
611
658
  * 或直接按 message_id 把整条消息拉回来再渲染。
@@ -681,4 +728,4 @@ declare function renderDslToString(dsl: unknown, options?: RenderOptions): strin
681
728
  /** 输入公开 DSL,渲染为真实 DOM 节点(浏览器)。= renderCard(dslToRaw(dsl)) */
682
729
  declare function renderDsl(dsl: unknown, options?: RenderOptions): Node;
683
730
 
684
- export { ALL_CSS, AUDIO_HYDRATED_ATTR, AUDIO_PAUSE_ICON_SVG, AUDIO_PLAY_ICON_SVG, type AsyncRenderOptions, type AtUserInfo, type BoxSpacing, CHART_HYDRATED_ATTR, CHART_PALETTE, CHART_SPEC_ATTR, type CardElement, type CardInput, type CardMessageInput, type ComponentFn, EMOJI_KEY_TO_NAME, EMOJI_SPRITE_CSS, type ElementProperty, type FetchedCardMessage, HEADER_BG_DARK, HEADER_BG_LIGHT, type HeaderTemplate, ICON_CODEPOINTS, ICON_FONT_CSS, type JsonAttachment, type JsonCard, type LinkURL, type ListItem, type MessageMention, type NamedColor, type PersonInfo, type RenderContext, type RenderOptions, type SizeName, type TableColumn, type TableHeaderStyle, type TextAlign, type TextAttribute, type TextStyle, type VNode, type VNodeChild, type VNodeProps, arrayBufferToBase64, feishuAudioHydrateJs, feishuAudioHydrateScript, feishuCardStyleTag, feishuChartHydrateJs, feishuChartHydrateScript, fetchCardMessage, fetchImageDataUri, getComponent, h, hydrateFeishuAudios, hydrateFeishuCharts, mergeMentionsIntoPersons, parseInline, parseMarkdown, prefetchImages, registerComponent, renderCard, renderCardAsync, renderCardToString, renderCardToStringAsync, renderDsl, renderDslToString, renderFeishuChart };
731
+ export { ALL_CSS, AUDIO_HYDRATED_ATTR, AUDIO_PAUSE_ICON_SVG, AUDIO_PLAY_ICON_SVG, type AsyncRenderOptions, type AtUserInfo, type BoxSpacing, CHART_HYDRATED_ATTR, CHART_PALETTE, CHART_SPEC_ATTR, type CardElement, type CardInput, type CardMessageInput, type ComponentFn, EMOJI_KEY_TO_NAME, EMOJI_SPRITE_CSS, type ElementProperty, type FetchedCardMessage, HEADER_BG_DARK, HEADER_BG_LIGHT, type HeaderTemplate, ICON_CODEPOINTS, ICON_FONT_CSS, type JsonAttachment, type JsonCard, type LinkURL, type ListItem, type MessageMention, type NamedColor, type PersonInfo, type RenderContext, type RenderOptions, type SizeName, type TableColumn, type TableHeaderStyle, type TextAlign, type TextAttribute, type TextStyle, type ToastAPI, type ToastOptions, type ToastType, type VNode, type VNodeChild, type VNodeProps, arrayBufferToBase64, createToastHost, feishuAudioHydrateJs, feishuAudioHydrateScript, feishuCardStyleTag, feishuChartHydrateJs, feishuChartHydrateScript, fetchCardMessage, fetchImageDataUri, getComponent, h, hydrateFeishuAudios, hydrateFeishuCharts, mergeMentionsIntoPersons, parseInline, parseMarkdown, prefetchImages, registerComponent, renderCard, renderCardAsync, renderCardToString, renderCardToStringAsync, renderDsl, renderDslToString, renderFeishuChart, renderToastHTML };
package/dist/index.js CHANGED
@@ -670,7 +670,8 @@ function resolveImageSrc(p, ctx) {
670
670
  if (p.src) return p.src;
671
671
  if (p.url) return resolveURL(p.url) || null;
672
672
  if (p.img_url) return p.img_url;
673
- if (p.imageID && ctx.options.imageBaseURL) return ctx.options.imageBaseURL + p.imageID;
673
+ const imageId = p.imageID || p.img_key;
674
+ if (imageId && ctx.options.imageBaseURL) return ctx.options.imageBaseURL + imageId;
674
675
  return null;
675
676
  }
676
677
  function resolveMediaSrc(p, kind, ctx) {
@@ -700,6 +701,7 @@ function personAvatarSrc(person, ctx) {
700
701
  const r = ctx.options.resolvePersonAvatar(person);
701
702
  if (r) return r;
702
703
  }
704
+ if (person.avatar_url) return person.avatar_url;
703
705
  if (!person.avatar_key) return null;
704
706
  const base = ctx.options.avatarBaseURL || PERSON_AVATAR_BASE;
705
707
  const suffix = ctx.options.avatarURLSuffix ?? PERSON_AVATAR_SUFFIX;
@@ -2016,6 +2018,55 @@ var BASE_STYLES_CSS = `/**
2016
2018
  }
2017
2019
  .universal-card-root.theme-dark .universal-card-markdown { color: #d6d6d6; }
2018
2020
  .universal-card-root.theme-dark .universal-card-hr { background-color: #2e2e30; }
2021
+
2022
+ /* ---- Toast \u5BBF\u4E3B\uFF08\u5BF9\u9F50\u5BA2\u6237\u7AEF .ud__toast.ud__notice\uFF1A\u9876\u90E8\u4E0B\u62C9\u3001\u6DF1\u8272\u80F6\u56CA\uFF09 ---- */
2023
+ .universal-card-toast-host {
2024
+ position: fixed;
2025
+ top: 32px; /* \u5BA2\u6237\u7AEF\u7EC4\u4EF6\u914D\u7F6E toast:{top:32} */
2026
+ left: 50%;
2027
+ transform: translateX(-50%);
2028
+ display: flex;
2029
+ flex-direction: column;
2030
+ align-items: center;
2031
+ z-index: 300;
2032
+ pointer-events: none;
2033
+ }
2034
+ .universal-card-toast {
2035
+ display: inline-flex;
2036
+ align-items: center;
2037
+ gap: 8px;
2038
+ max-width: 600px; /* \u5BA2\u6237\u7AEF .ud__toast max-width:600px */
2039
+ padding: 10px 16px;
2040
+ border-radius: 8px;
2041
+ background: rgba(31, 35, 41, 0.92); /* N900 \u6DF1\u8272\u534A\u900F\u660E\uFF0C\u767D\u5B57 */
2042
+ color: #fff;
2043
+ font-size: 14px;
2044
+ line-height: 1.5;
2045
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.16);
2046
+ margin-bottom: 16px; /* \u5BA2\u6237\u7AEF .ud__toast margin-bottom:16px */
2047
+ animation: universal-card-toast-in 0.3s cubic-bezier(0.2, 0, 0, 1);
2048
+ }
2049
+ .universal-card-toast--leave {
2050
+ animation: universal-card-toast-out 0.3s cubic-bezier(0.2, 0, 0, 1) forwards;
2051
+ }
2052
+ .universal-card-toast__icon {
2053
+ flex: none;
2054
+ display: inline-flex;
2055
+ }
2056
+ .universal-card-toast__text {
2057
+ display: -webkit-box;
2058
+ -webkit-box-orient: vertical;
2059
+ overflow: hidden;
2060
+ word-break: break-word;
2061
+ }
2062
+ @keyframes universal-card-toast-in {
2063
+ from { opacity: 0; transform: translateY(-100%); }
2064
+ to { opacity: 1; transform: translateY(0); }
2065
+ }
2066
+ @keyframes universal-card-toast-out {
2067
+ from { opacity: 1; transform: translateY(0); }
2068
+ to { opacity: 0; transform: translateY(-100%); margin-bottom: 0; }
2069
+ }
2019
2070
  `;
2020
2071
 
2021
2072
  // src/icon-font.ts
@@ -6815,6 +6866,60 @@ function feishuCardStyleTag() {
6815
6866
  return `<style data-feishu-card>${ALL_CSS}</style>`;
6816
6867
  }
6817
6868
 
6869
+ // src/toast.ts
6870
+ var DEFAULT_SHOW_TIME = 2e3;
6871
+ var TYPE_ICON = {
6872
+ success: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="#34c724"/><path d="M4.5 8.2l2.2 2.2 4.8-4.8" fill="none" stroke="#fff" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>',
6873
+ info: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="#3370ff"/><circle cx="8" cy="4.6" r="1" fill="#fff"/><rect x="7.2" y="7" width="1.6" height="4.2" rx="0.8" fill="#fff"/></svg>',
6874
+ warning: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="#fad355"/><rect x="7.2" y="3.8" width="1.6" height="4.6" rx="0.8" fill="#1f2329"/><circle cx="8" cy="11.8" r="1" fill="#1f2329"/></svg>',
6875
+ error: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="#f54a45"/><path d="M5.6 5.6l4.8 4.8M10.4 5.6l-4.8 4.8" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>'
6876
+ };
6877
+ function escapeHTML(s) {
6878
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6879
+ }
6880
+ function renderToastHTML(type, opts) {
6881
+ const maxLines = opts.maxLines ?? 1;
6882
+ const styleAttr = maxLines > 1 ? ` style="-webkit-line-clamp:${maxLines};"` : "";
6883
+ return `<div class="universal-card-toast universal-card-toast--${type}" role="status" aria-live="polite"><span class="universal-card-toast__icon">${TYPE_ICON[type]}</span><span class="universal-card-toast__text"${styleAttr}>${escapeHTML(opts.content)}</span></div>`;
6884
+ }
6885
+ function createToastHost(container) {
6886
+ const hostEl = typeof document === "undefined" ? null : container ?? document.body;
6887
+ const ensureHost = () => {
6888
+ if (!hostEl) return null;
6889
+ let el = hostEl.querySelector(".universal-card-toast-host");
6890
+ if (!el) {
6891
+ el = document.createElement("div");
6892
+ el.className = "universal-card-toast-host";
6893
+ hostEl.appendChild(el);
6894
+ }
6895
+ return el;
6896
+ };
6897
+ const push = (type) => (opts) => {
6898
+ const host = ensureHost();
6899
+ if (!host || typeof document === "undefined") return;
6900
+ const wrap = document.createElement("div");
6901
+ wrap.style.cssText = "display:contents";
6902
+ wrap.innerHTML = renderToastHTML(type, opts);
6903
+ const toastEl = wrap.firstElementChild;
6904
+ host.appendChild(toastEl);
6905
+ const showTime = opts.showTime ?? DEFAULT_SHOW_TIME;
6906
+ setTimeout(() => {
6907
+ toastEl.classList.add("universal-card-toast--leave");
6908
+ toastEl.addEventListener("animationend", () => toastEl.remove(), { once: true });
6909
+ setTimeout(() => toastEl.remove(), 400);
6910
+ }, showTime);
6911
+ };
6912
+ return {
6913
+ success: push("success"),
6914
+ info: push("info"),
6915
+ warning: push("warning"),
6916
+ error: push("error"),
6917
+ destroy: () => {
6918
+ hostEl?.querySelectorAll(".universal-card-toast-host .universal-card-toast").forEach((el) => el.remove());
6919
+ }
6920
+ };
6921
+ }
6922
+
6818
6923
  // src/remote.ts
6819
6924
  var DEFAULT_API_BASE = "https://open.feishu.cn/open-apis/im/v1";
6820
6925
  function getFetch(options) {
@@ -7053,4 +7158,4 @@ function renderDsl(dsl, options = {}) {
7053
7158
  return renderCard(dslToRaw(dsl), options);
7054
7159
  }
7055
7160
 
7056
- export { ALL_CSS, AUDIO_HYDRATED_ATTR, AUDIO_PAUSE_ICON_SVG, AUDIO_PLAY_ICON_SVG, CHART_HYDRATED_ATTR, CHART_PALETTE, CHART_SPEC_ATTR, EMOJI_KEY_TO_NAME, EMOJI_SPRITE_CSS, HEADER_BG_DARK, HEADER_BG_LIGHT, ICON_CODEPOINTS, ICON_FONT_CSS, arrayBufferToBase64, feishuAudioHydrateJs, feishuAudioHydrateScript, feishuCardStyleTag, feishuChartHydrateJs, feishuChartHydrateScript, fetchCardMessage, fetchImageDataUri, getComponent, h, hydrateFeishuAudios, hydrateFeishuCharts, mergeMentionsIntoPersons, parseInline, parseMarkdown, prefetchImages, registerComponent, renderCard, renderCardAsync, renderCardToString, renderCardToStringAsync, renderDsl, renderDslToString, renderFeishuChart };
7161
+ export { ALL_CSS, AUDIO_HYDRATED_ATTR, AUDIO_PAUSE_ICON_SVG, AUDIO_PLAY_ICON_SVG, CHART_HYDRATED_ATTR, CHART_PALETTE, CHART_SPEC_ATTR, EMOJI_KEY_TO_NAME, EMOJI_SPRITE_CSS, HEADER_BG_DARK, HEADER_BG_LIGHT, ICON_CODEPOINTS, ICON_FONT_CSS, arrayBufferToBase64, createToastHost, feishuAudioHydrateJs, feishuAudioHydrateScript, feishuCardStyleTag, feishuChartHydrateJs, feishuChartHydrateScript, fetchCardMessage, fetchImageDataUri, getComponent, h, hydrateFeishuAudios, hydrateFeishuCharts, mergeMentionsIntoPersons, parseInline, parseMarkdown, prefetchImages, registerComponent, renderCard, renderCardAsync, renderCardToString, renderCardToStringAsync, renderDsl, renderDslToString, renderFeishuChart, renderToastHTML };
package/dist/styles.css CHANGED
@@ -1049,3 +1049,52 @@
1049
1049
  }
1050
1050
  .universal-card-root.theme-dark .universal-card-markdown { color: #d6d6d6; }
1051
1051
  .universal-card-root.theme-dark .universal-card-hr { background-color: #2e2e30; }
1052
+
1053
+ /* ---- Toast 宿主(对齐客户端 .ud__toast.ud__notice:顶部下拉、深色胶囊) ---- */
1054
+ .universal-card-toast-host {
1055
+ position: fixed;
1056
+ top: 32px; /* 客户端组件配置 toast:{top:32} */
1057
+ left: 50%;
1058
+ transform: translateX(-50%);
1059
+ display: flex;
1060
+ flex-direction: column;
1061
+ align-items: center;
1062
+ z-index: 300;
1063
+ pointer-events: none;
1064
+ }
1065
+ .universal-card-toast {
1066
+ display: inline-flex;
1067
+ align-items: center;
1068
+ gap: 8px;
1069
+ max-width: 600px; /* 客户端 .ud__toast max-width:600px */
1070
+ padding: 10px 16px;
1071
+ border-radius: 8px;
1072
+ background: rgba(31, 35, 41, 0.92); /* N900 深色半透明,白字 */
1073
+ color: #fff;
1074
+ font-size: 14px;
1075
+ line-height: 1.5;
1076
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.16);
1077
+ margin-bottom: 16px; /* 客户端 .ud__toast margin-bottom:16px */
1078
+ animation: universal-card-toast-in 0.3s cubic-bezier(0.2, 0, 0, 1);
1079
+ }
1080
+ .universal-card-toast--leave {
1081
+ animation: universal-card-toast-out 0.3s cubic-bezier(0.2, 0, 0, 1) forwards;
1082
+ }
1083
+ .universal-card-toast__icon {
1084
+ flex: none;
1085
+ display: inline-flex;
1086
+ }
1087
+ .universal-card-toast__text {
1088
+ display: -webkit-box;
1089
+ -webkit-box-orient: vertical;
1090
+ overflow: hidden;
1091
+ word-break: break-word;
1092
+ }
1093
+ @keyframes universal-card-toast-in {
1094
+ from { opacity: 0; transform: translateY(-100%); }
1095
+ to { opacity: 1; transform: translateY(0); }
1096
+ }
1097
+ @keyframes universal-card-toast-out {
1098
+ from { opacity: 1; transform: translateY(0); }
1099
+ to { opacity: 0; transform: translateY(-100%); margin-bottom: 0; }
1100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/renderer",
3
- "version": "0.2.29",
3
+ "version": "0.2.31",
4
4
  "description": "零框架依赖的飞书卡片渲染引擎:Web / DOM / SSR,输入 RAW 或公开 DSL,输出真实 DOM 或 HTML 字符串",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",