a5d86a92c2
feat: 升级项目 Logo、完善 toast 提示及优化工程化配置 ### Features - **logo**: 替换全套项目图标,更新 wxt.config.ts 浏览器扩展图标配置。 ### Bug Fixes - **ui/toast**: 引入 <Toaster> 组件修复 toast 无法显示问题,并调整位置为正下方。 - **i18n**: 统一将 t() 函数中的 `.` 和 `:` 转换为 `_`,修复国际化文案读取失败问题。 - **context-menu**: 限制 srcUrl 检查仅在二维码图片菜单项生效,避免误判。 - **hooks**: 修复 pre-push 钩子,仅对变更文件进行 eslint 检查,并支持无 upstream 时的回退对比。 - **components**: 修复 CopyButton 动画与组件导入方式,并补齐无障碍属性与测试。 - **async**: 延迟生成二维码以正确渲染 loading 状态,并适配相关异步测试。 ### Refactor & Cleans - **clean**: 清理未使用的组件(ToolCard)、Hook(useDebounce)及多个未使用的工具函数导出。 - **structure**: 统一移动测试文件至 __tests__ 目录,移除不必要的类型导出。 ### Documentation - **docs**: 在 AGENTS.md 中明确规范 Git Commit 必须使用中文描述。
162 lines
4.2 KiB
TypeScript
162 lines
4.2 KiB
TypeScript
/**
|
|
* JSON 转 YAML 转换结果
|
|
*/
|
|
interface JsonToYamlResult {
|
|
/** 转换后的 YAML 字符串 */
|
|
output: string;
|
|
/** 原始输入的字节大小 */
|
|
originalBytes: number;
|
|
/** 转换后的字节大小 */
|
|
outputBytes: number;
|
|
}
|
|
|
|
/**
|
|
* 将 JSON 值转换为 YAML 字符串表示
|
|
*
|
|
* @param value - 已解析的 JSON 值
|
|
* @param indent - 当前缩进级别
|
|
* @returns YAML 字符串
|
|
*/
|
|
function toYamlString(value: unknown, indent: number): string {
|
|
if (value === null) {
|
|
return 'null';
|
|
}
|
|
if (value === true) {
|
|
return 'true';
|
|
}
|
|
if (value === false) {
|
|
return 'false';
|
|
}
|
|
if (typeof value === 'number') {
|
|
return String(value);
|
|
}
|
|
if (typeof value === 'string') {
|
|
return stringifyYamlString(value);
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return arrayToYaml(value, indent);
|
|
}
|
|
if (typeof value === 'object') {
|
|
return objectToYaml(value as Record<string, unknown>, indent);
|
|
}
|
|
return String(value);
|
|
}
|
|
|
|
/**
|
|
* 将字符串转为 YAML 安全的表示
|
|
* 对于包含特殊字符的字符串使用双引号包裹
|
|
*/
|
|
function stringifyYamlString(str: string): string {
|
|
// 需要引号包裹的情况:空字符串、以特殊字符开头、包含特殊字符
|
|
const needsQuoting =
|
|
str === '' ||
|
|
str === 'null' ||
|
|
str === 'true' ||
|
|
str === 'false' ||
|
|
/[:#{}[\],&*?|>\-!%@`]/.test(str) ||
|
|
str.includes(' ') ||
|
|
str.includes('\n') ||
|
|
/^\d/.test(str);
|
|
|
|
if (!needsQuoting) {
|
|
return str;
|
|
}
|
|
|
|
// 转义双引号和反斜杠,然后用双引号包裹
|
|
const escaped = str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
return `"${escaped}"`;
|
|
}
|
|
|
|
/**
|
|
* 将 JSON 对象转换为 YAML 映射
|
|
*/
|
|
function objectToYaml(obj: Record<string, unknown>, indent: number): string {
|
|
const prefix = ' '.repeat(indent);
|
|
const keys = Object.keys(obj);
|
|
|
|
if (keys.length === 0) {
|
|
return '{}';
|
|
}
|
|
|
|
const lines: string[] = [];
|
|
for (const key of keys) {
|
|
const value = obj[key];
|
|
const safeKey = /^[a-zA-Z0-9_-]+$/.test(key) ? key : stringifyYamlString(key);
|
|
|
|
if (
|
|
value !== null &&
|
|
typeof value === 'object' &&
|
|
!Array.isArray(value) &&
|
|
Object.keys(value as Record<string, unknown>).length > 0
|
|
) {
|
|
// 非空嵌套对象
|
|
lines.push(`${prefix}${safeKey}:`);
|
|
lines.push(toYamlString(value, indent + 1));
|
|
} else if (Array.isArray(value) && value.length > 0) {
|
|
// 非空数组
|
|
lines.push(`${prefix}${safeKey}:`);
|
|
lines.push(arrayToYaml(value, indent + 1));
|
|
} else {
|
|
// 基本值、空对象、空数组
|
|
lines.push(`${prefix}${safeKey}: ${toYamlString(value, indent + 1)}`);
|
|
}
|
|
}
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
/**
|
|
* 将 JSON 数组转换为 YAML 序列
|
|
*/
|
|
function arrayToYaml(arr: unknown[], indent: number): string {
|
|
const prefix = ' '.repeat(indent);
|
|
|
|
if (arr.length === 0) {
|
|
return `${prefix}[]`;
|
|
}
|
|
|
|
const lines: string[] = [];
|
|
for (const item of arr) {
|
|
if (
|
|
item !== null &&
|
|
typeof item === 'object' &&
|
|
!Array.isArray(item) &&
|
|
Object.keys(item as Record<string, unknown>).length > 0
|
|
) {
|
|
// 数组中的非空对象
|
|
const objLines = objectToYaml(item as Record<string, unknown>, indent + 1);
|
|
lines.push(`${prefix}- ${objLines.trimStart()}`);
|
|
} else if (Array.isArray(item) && item.length > 0) {
|
|
// 数组中的非空数组
|
|
lines.push(`${prefix}-`);
|
|
lines.push(arrayToYaml(item, indent + 1));
|
|
} else {
|
|
// 基本值、空对象、空数组
|
|
lines.push(`${prefix}- ${toYamlString(item, indent + 1)}`);
|
|
}
|
|
}
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
/**
|
|
* 将 JSON 字符串转换为 YAML 格式
|
|
*
|
|
* @param text - 输入的 JSON 字符串
|
|
* @returns 转换结果
|
|
* @throws {SyntaxError} 当输入不是有效的 JSON 时抛出语法错误
|
|
*/
|
|
export function jsonToYaml(text: string): JsonToYamlResult {
|
|
const trimmed = text.trim();
|
|
if (!trimmed) {
|
|
return { output: '', originalBytes: 0, outputBytes: 0 };
|
|
}
|
|
|
|
const parsed: unknown = JSON.parse(trimmed);
|
|
const output = toYamlString(parsed, 0);
|
|
const originalBytes = new TextEncoder().encode(trimmed).length;
|
|
const outputBytes = new TextEncoder().encode(output).length;
|
|
|
|
return { output, originalBytes, outputBytes };
|
|
}
|