deerflow2/frontend/src/lib/utils.ts
mt 1637a0e71c fix(copy): copyToClipboard 始终发送 postMessage
- 移除 copyToClipboard 内独立的 isInIframe 判断
- 改为始终调用 sendToParent,由 sendToParent 内部统一判断 iframe 环境
- 与 openSkillDialog 等其他 iframe 通信保持一致
2026-06-11 09:50:15 +08:00

89 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/** Shared class for external links (underline by default). */
export const externalLinkClass =
"text-primary underline underline-offset-2 hover:no-underline";
/** Link style without underline by default (e.g. for streaming/loading). */
export const externalLinkClassNoUnderline = "text-primary hover:underline";
/**
* Copy text to clipboard, using postMessage when in iframe.
* In iframe context, sends message to parent window to handle clipboard operation.
*/
export async function copyToClipboard(text: string): Promise<void> {
const message = {
type: POST_MESSAGE_TYPES.COPY_TO_CLIPBOARD,
text,
} as const;
console.log("[copyToClipboard] called, text length:", text.length);
// 始终发送 postMessage由 sendToParent 内部判断是否为 iframe 环境
// 与 openSkillDialog 等其他 iframe 通信保持一致
try {
sendToParent(message);
} catch {
// no-op
}
// 同时也尝试直接写剪贴板(非 iframe 场景兜底)
try {
await navigator.clipboard.writeText(text);
} catch {
// no-op: 在 iframe 环境下由父窗口处理
}
}
/**
* 计算字符串的视觉宽度中文算2英文算1
*/
export function getVisualWidth(text: string): number {
let width = 0;
for (const char of text) {
// 中文字符范围:\u4e00-\u9fff基本汉字
width += /[\u4e00-\u9fff]/.test(char) ? 2 : 1;
}
return width;
}
/**
* 截断字符串中间部分,保留开头和结尾
* 例如: "very-long-file-name.txt" -> "very-lon...me.txt"
* 中文按视觉宽度计算中文算2英文算1
*/
export function truncateMiddle(text: string, maxVisualWidth = 30): string {
const visualWidth = getVisualWidth(text);
if (visualWidth <= maxVisualWidth) return text;
const startWidth = Math.ceil(maxVisualWidth * 0.6);
const endWidth = Math.floor(maxVisualWidth * 0.4) - 3; // -3 for "..."
let startPart = "";
let currentWidth = 0;
for (const char of text) {
const charWidth = /[\u4e00-\u9fff]/.test(char) ? 2 : 1;
if (currentWidth + charWidth > startWidth) break;
startPart += char;
currentWidth += charWidth;
}
let endPart = "";
currentWidth = 0;
for (let i = text.length - 1; i >= 0; i--) {
const char = text[i]!;
const charWidth = /[\u4e00-\u9fff]/.test(char) ? 2 : 1;
if (currentWidth + charWidth > endWidth) break;
endPart = char + endPart;
currentWidth += charWidth;
}
return `${startPart}...${endPart}`;
}