diff --git a/src/main/services/FileStorage.ts b/src/main/services/FileStorage.ts index 00dda778be..18a11c6bd6 100644 --- a/src/main/services/FileStorage.ts +++ b/src/main/services/FileStorage.ts @@ -1,6 +1,7 @@ import { loggerService } from '@logger' import { checkName, + expandNotesPath, getFilesDir, getFileType, getName, @@ -741,7 +742,9 @@ class FileStorage { public getDirectoryStructure = async (_: Electron.IpcMainInvokeEvent, dirPath: string): Promise => { try { - return await scanDir(dirPath) + // Expand relative paths before scanning + const expandedPath = expandNotesPath(dirPath) + return await scanDir(expandedPath) } catch (error) { logger.error('Failed to get directory structure:', error as Error) throw error @@ -754,8 +757,8 @@ class FileStorage { return false } - // Normalize path - const normalizedPath = path.resolve(dirPath) + // Expand and normalize path (handles ~, ., and .. paths) + const normalizedPath = expandNotesPath(dirPath) // Check if directory exists if (!fs.existsSync(normalizedPath)) { @@ -1008,7 +1011,8 @@ class FileStorage { throw new Error('Directory path is required') } - const normalizedPath = path.resolve(dirPath.trim()) + // Expand relative paths before watching + const normalizedPath = expandNotesPath(dirPath.trim()) if (!fs.existsSync(normalizedPath)) { throw new Error(`Directory does not exist: ${normalizedPath}`) diff --git a/src/main/utils/file.ts b/src/main/utils/file.ts index 1432dccc8a..f9e44c0134 100644 --- a/src/main/utils/file.ts +++ b/src/main/utils/file.ts @@ -38,6 +38,33 @@ export function untildify(pathWithTilde: string) { return pathWithTilde } +/** + * Expand relative paths to absolute paths. + * Handles paths starting with ~, ., or .. + * @param pathString - The path to expand + * @param basePath - Optional base path for relative paths (defaults to userData directory) + * @returns Absolute path + */ +export function expandNotesPath(pathString: string, basePath?: string): string { + if (!pathString) { + return pathString + } + + // First handle tilde expansion + let expandedPath = untildify(pathString) + + // If it's already an absolute path, return it + if (path.isAbsolute(expandedPath)) { + return path.normalize(expandedPath) + } + + // For relative paths, resolve against the base path (default to userData) + const base = basePath || app.getPath('userData') + expandedPath = path.resolve(base, expandedPath) + + return path.normalize(expandedPath) +} + export async function hasWritePermission(dir: string) { try { logger.info(`Checking write permission for ${dir}`) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 0695075051..54682ac625 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -2102,8 +2102,8 @@ "select": "Select", "select_directory_failed": "Failed to select directory", "title": "Data Settings", - "work_directory_description": "Work directory is where all note files are stored. Changing the work directory won't move existing files, please migrate files manually.", - "work_directory_placeholder": "Select notes work directory" + "work_directory_description": "Work directory is where all note files are stored. Supports relative paths like ~/Notes or ./Notes for multi-device sync. Changing the work directory won't move existing files, please migrate files manually.", + "work_directory_placeholder": "Enter or select notes work directory (e.g., ~/Notes)" }, "display": { "compress_content": "Content Compression", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 37659a7dd7..5c27bb918b 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -2102,8 +2102,8 @@ "select": "选择", "select_directory_failed": "选择目录失败", "title": "数据设置", - "work_directory_description": "工作目录是存储所有笔记文件的位置。更改工作目录不会移动现有文件,请手动迁移文件。", - "work_directory_placeholder": "选择笔记工作目录" + "work_directory_description": "工作目录是存储所有笔记文件的位置。支持相对路径如 ~/笔记 或 ./笔记 以实现多设备同步。更改工作目录不会移动现有文件,请手动迁移文件。", + "work_directory_placeholder": "输入或选择笔记工作目录(例如:~/笔记)" }, "display": { "compress_content": "缩减栏宽", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 5016bcfe1d..ab365b669b 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -2102,8 +2102,8 @@ "select": "選擇", "select_directory_failed": "選擇目錄失敗", "title": "數據設置", - "work_directory_description": "工作目錄是存儲所有筆記文件的位置。\n更改工作目錄不會移動現有文件,請手動遷移文件。", - "work_directory_placeholder": "選擇筆記工作目錄" + "work_directory_description": "工作目錄是存儲所有筆記文件的位置。支持相對路徑如 ~/筆記 或 ./筆記 以實現多設備同步。\n更改工作目錄不會移動現有文件,請手動遷移文件。", + "work_directory_placeholder": "輸入或選擇筆記工作目錄(例如:~/筆記)" }, "display": { "compress_content": "縮減欄寬", diff --git a/src/renderer/src/pages/settings/NotesSettings.tsx b/src/renderer/src/pages/settings/NotesSettings.tsx index be36c0fe6e..d5533abb67 100644 --- a/src/renderer/src/pages/settings/NotesSettings.tsx +++ b/src/renderer/src/pages/settings/NotesSettings.tsx @@ -105,7 +105,6 @@ const NotesSettings: FC = () => { value={tempPath} onChange={(e) => setTempPath(e.target.value)} placeholder={t('notes.settings.data.work_directory_placeholder')} - readOnly />