diff --git a/frontend/src/components/FileExplorer.tsx b/frontend/src/components/FileExplorer.tsx
index 135fd3a..bf750e6 100644
--- a/frontend/src/components/FileExplorer.tsx
+++ b/frontend/src/components/FileExplorer.tsx
@@ -46,6 +46,20 @@ interface UploadProgress {
error?: string;
}
+function getFileDisplayName(name: string, isDir: boolean) {
+ if (isDir) return name;
+ const dotIndex = name.lastIndexOf('.');
+ if (dotIndex > 0) return name.substring(0, dotIndex);
+ return name;
+}
+
+function getFileExtension(name: string, isDir: boolean) {
+ if (isDir) return '';
+ const dotIndex = name.lastIndexOf('.');
+ if (dotIndex > 0) return name.substring(dotIndex);
+ return '';
+}
+
export default function FileExplorer({ onLogout }: FileExplorerProps) {
const [currentPath, setCurrentPath] = useState(() => {
if (typeof window !== 'undefined') {
@@ -324,6 +338,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
}
function handleFileClick(file: FileItem, e: React.MouseEvent) {
+ if (renaming === file.path) return;
if (e.shiftKey) {
// Multi-select with shift
setSelectedFiles(prev => {
@@ -406,9 +421,20 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
}
}
- async function handleRename(path: string) {
- if (!newName.trim()) return;
- await api.rename(path, newName);
+ async function handleRename(path: string, originalName: string, isDir: boolean) {
+ if (!newName.trim()) {
+ setRenaming(null);
+ setNewName('');
+ return;
+ }
+ const ext = getFileExtension(originalName, isDir);
+ const finalName = newName.trim() + ext;
+ if (finalName === originalName) {
+ setRenaming(null);
+ setNewName('');
+ return;
+ }
+ await api.rename(path, finalName);
setRenaming(null);
setNewName('');
loadFiles();
@@ -785,7 +811,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
{getFileIcon(file)}
- {file.name}
+ {getFileDisplayName(file.name, file.is_dir)}
))
)}
@@ -1141,10 +1167,11 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
type="text"
value={newName}
onChange={(e) => setNewName(e.target.value)}
- onKeyDown={(e) => { if (e.key === 'Enter') handleRename(file.path); if (e.key === 'Escape') setRenaming(null); }}
- onBlur={() => handleRename(file.path)}
+ onKeyDown={(e) => { if (e.key === 'Enter') handleRename(file.path, file.name, file.is_dir); if (e.key === 'Escape') setRenaming(null); }}
+ onBlur={() => handleRename(file.path, file.name, file.is_dir)}
autoFocus
onFocus={(e) => e.target.select()}
+ onClick={(e) => e.stopPropagation()}
className="w-full text-xs text-center bg-transparent outline-none"
style={{
color: 'var(--color-text-primary)',
@@ -1155,9 +1182,9 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
- {file.name}
+ {getFileDisplayName(file.name, file.is_dir)}
)}
@@ -1221,15 +1248,16 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
type="text"
value={newName}
onChange={(e) => setNewName(e.target.value)}
- onKeyDown={(e) => { if (e.key === 'Enter') handleRename(file.path); if (e.key === 'Escape') setRenaming(null); }}
- onBlur={() => handleRename(file.path)}
+ onKeyDown={(e) => { if (e.key === 'Enter') handleRename(file.path, file.name, file.is_dir); if (e.key === 'Escape') setRenaming(null); }}
+ onBlur={() => handleRename(file.path, file.name, file.is_dir)}
autoFocus
onFocus={(e) => e.target.select()}
+ onClick={(e) => e.stopPropagation()}
className="flex-1 text-sm bg-transparent outline-none"
style={{ color: 'var(--color-text-primary)', borderBottom: '1px solid var(--color-accent)' }}
/>
) : (
- {file.name}
+ {getFileDisplayName(file.name, file.is_dir)}
)}
{file.is_pinned && }
@@ -1376,7 +1404,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
label="Rename"
onClick={() => {
setRenaming(contextMenu.file.path);
- setNewName(contextMenu.file.name);
+ setNewName(getFileDisplayName(contextMenu.file.name, contextMenu.file.is_dir));
setContextMenu(null);
}}
/>
@@ -1490,9 +1518,9 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
- {upload.name}
+ {getFileDisplayName(upload.name, false)}
{upload.status === 'complete' ? '✓' : upload.status === 'error' ? '✗' : `${Math.round(upload.progress)}%`}
diff --git a/frontend/src/components/FilePreview.tsx b/frontend/src/components/FilePreview.tsx
index 28caa5c..c5ba0a8 100644
--- a/frontend/src/components/FilePreview.tsx
+++ b/frontend/src/components/FilePreview.tsx
@@ -18,6 +18,12 @@ interface FilePreviewProps {
onClose: () => void;
}
+function getFileDisplayName(name: string) {
+ const dotIndex = name.lastIndexOf('.');
+ if (dotIndex > 0) return name.substring(0, dotIndex);
+ return name;
+}
+
export default function FilePreview({ file, onClose }: FilePreviewProps) {
const [content, setContent] = useState(null);
const [loading, setLoading] = useState(false);
@@ -87,8 +93,8 @@ export default function FilePreview({ file, onClose }: FilePreviewProps) {
{getIcon(previewType)}
-
- {file.name}
+
+ {getFileDisplayName(file.name)}
{formatSize(file.size)}
@@ -139,7 +145,7 @@ export default function FilePreview({ file, onClose }: FilePreviewProps) {
{previewType === 'audio' && (
-
{file.name}
+
{getFileDisplayName(file.name)}
)}