import { readDir, readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
import { open, save } from '@tauri-apps/plugin-dialog';
import type { DirectoryNode, FileOperationResult, DirectoryStructureResult } from './types';
import { SUPPORTED_FILE_EXTENSIONS, MAX_DIRECTORY_DEPTH, DEFAULT_FILE_CONTENT } from '../constants/files';
const joinPath = (base: string, name: string): string => {
const trimmed = base.replace(/[\\/]+$/, '');
return `${trimmed}/${name}`;
};
export const checkPathExists = async (path: string): Promise<boolean> => {
try {
await readDir(path);
return true;
} catch (error) {
return false;
}
};
export const isSupportedFile = (fileName: string): boolean => {
const fileExtension = fileName.split('.').pop()?.toLowerCase();
return SUPPORTED_FILE_EXTENSIONS.includes(fileExtension || '');
};
export const loadDirectoryStructure = async (
dirPath: string,
depth: number = 0,
maxDepth: number = MAX_DIRECTORY_DEPTH,
): Promise<DirectoryStructureResult> => {
if (depth > maxDepth) {
return { success: true, data: [] };
}
try {
const entries = await readDir(dirPath);
const children: DirectoryNode[] = [];
for (const entry of entries) {
const fullPath = joinPath(dirPath, entry.name || '');
if (entry.isDirectory) {
const node: DirectoryNode = {
path: fullPath,
name: entry.name || '',
type: 'directory',
expanded: false,
children: [],
};
const nested = await loadDirectoryStructure(fullPath, depth + 1, maxDepth);
if (nested.success && nested.data) {
node.children = nested.data;
}
children.push(node);
} else {
if (isSupportedFile(entry.name || '')) {
children.push({
path: fullPath,
name: entry.name || '',
type: 'file',
});
}
}
}
children.sort((a, b) => {
if (a.type === b.type) {
return a.name.localeCompare(b.name);
}
return a.type === 'directory' ? -1 : 1;
});
return { success: true, data: children };
} catch (error) {
console.error('加载目录结构失败:', error);
return {
success: false,
error: `加载目录结构失败: ${error instanceof Error ? error.message : String(error)}`,
};
}
};
export const createNewFile = async (): Promise<FileOperationResult> => {
try {
const selected = await save({
filters: [
{
name: 'Markdown',
extensions: SUPPORTED_FILE_EXTENSIONS,
},
],
});
if (!selected) {
return { success: false, error: '用户取消操作' };
}
const filePath = Array.isArray(selected) ? selected[0] : selected;
await writeTextFile(filePath, DEFAULT_FILE_CONTENT);
return { success: true, data: filePath };
} catch (error) {
console.error('创建新文件失败:', error);
return {
success: false,
error: `创建新文件失败: ${error instanceof Error ? error.message : String(error)}`,
};
}
};
export const openExistingFile = async (): Promise<FileOperationResult> => {
try {
const selected = await open({
filters: [
{
name: 'Markdown',
extensions: SUPPORTED_FILE_EXTENSIONS,
},
],
multiple: false,
});
if (!selected) {
return { success: false, error: '用户取消操作' };
}
const filePath = Array.isArray(selected) ? selected[0] : selected;
return { success: true, data: filePath };
} catch (error) {
console.error('打开文件失败:', error);
return {
success: false,
error: `打开文件失败: ${error instanceof Error ? error.message : String(error)}`,
};
}
};
export const openDirectoryDialog = async (): Promise<FileOperationResult> => {
try {
const selected = await open({
directory: true,
multiple: false,
});
if (!selected) {
return { success: false, error: '用户取消操作' };
}
const dirPath = Array.isArray(selected) ? selected[0] : selected;
return { success: true, data: dirPath };
} catch (error) {
console.error('打开目录失败:', error);
return {
success: false,
error: `打开目录失败: ${error instanceof Error ? error.message : String(error)}`,
};
}
};
export const readFileContent = async (filePath: string): Promise<FileOperationResult> => {
try {
const content = await readTextFile(filePath);
return { success: true, data: content };
} catch (error) {
console.error('读取文件失败:', error);
return {
success: false,
error: `读取文件失败: ${error instanceof Error ? error.message : String(error)}`,
};
}
};
export const extractDirectoryPath = (filePath: string): string => {
const lastSlashIndex = filePath.lastIndexOf('/');
return lastSlashIndex !== -1 ? filePath.substring(0, lastSlashIndex) : '';
};
export const extractFileName = (filePath: string): string => {
const lastSlashIndex = filePath.lastIndexOf('/');
return lastSlashIndex !== -1 ? filePath.substring(lastSlashIndex + 1) : filePath;
};
* 格式化时间戳为相对时间或绝对日期
*/
export const formatTimestamp = (timestamp: number): string => {
const now = Date.now();
const diff = now - timestamp;
const MINUTE = 60 * 1000;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const WEEK = 7 * DAY;
if (diff < MINUTE) {
return '刚刚';
}
if (diff < HOUR) {
return `${Math.floor(diff / MINUTE)}分钟前`;
}
if (diff < DAY) {
return `${Math.floor(diff / HOUR)}小时前`;
}
if (diff < WEEK) {
return `${Math.floor(diff / DAY)}天前`;
}
return new Date(timestamp).toLocaleDateString();
};
export const debounce = <T extends (...args: any[]) => any>(
func: T,
wait: number,
): ((...args: Parameters<T>) => void) => {
let timeout: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
};