diff --git a/backend/handlers/files.go b/backend/handlers/files.go
index 530ad71..1af5bec 100644
--- a/backend/handlers/files.go
+++ b/backend/handlers/files.go
@@ -1026,7 +1026,12 @@ func (h *FSHandler) Download(c *fiber.Ctx) error {
// Set appropriate headers for range requests
c.Set("Accept-Ranges", "bytes")
c.Set("Content-Type", mime.TypeByExtension(filepath.Ext(resolvedPath)))
- c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", info.Name()))
+
+ if c.Query("download") == "true" {
+ c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", info.Name()))
+ } else {
+ c.Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", info.Name()))
+ }
// Use Fiber's built-in SendFile which supports Range requests
return c.SendFile(resolvedPath)
diff --git a/backend/middleware/auth.go b/backend/middleware/auth.go
index 557d6f3..c905e85 100644
--- a/backend/middleware/auth.go
+++ b/backend/middleware/auth.go
@@ -141,7 +141,7 @@ func GenerateEditToken(userID int, username, secret, filePath string) (string, e
claims := jwt.MapClaims{
"sub": userID,
"username": username,
- "exp": time.Now().Add(24 * time.Hour).Unix(),
+ "exp": time.Now().Add(7 * 24 * time.Hour).Unix(),
"iat": time.Now().Unix(),
"type": "edit",
}
diff --git a/frontend/src/components/FileExplorer.tsx b/frontend/src/components/FileExplorer.tsx
index 0087548..f064d5f 100644
--- a/frontend/src/components/FileExplorer.tsx
+++ b/frontend/src/components/FileExplorer.tsx
@@ -1395,7 +1395,27 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
{getFileIcon(file)}
- {getFileDisplayName(file.name, file.is_dir)}
+ {renaming === file.path ? (
+ setNewName(e.target.value)}
+ 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 w-full"
+ style={{ color: 'var(--color-text-primary)', borderBottom: '1px solid var(--color-accent)' }}
+ />
+ ) : (
+
+ {getFileDisplayName(file.name, file.is_dir)}
+
+ )}
))
)}
@@ -2274,23 +2294,27 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
className="p-2 hover:bg-white/5 rounded-lg transition-colors flex items-center gap-2 text-sm"
style={{ color: 'var(--color-text-primary)' }}
title="Download Selected"
- onClick={() => {
- if (!downloadToken) return;
- const form = document.createElement('form');
- form.method = 'POST';
- form.action = api.getBulkDownloadUrl(downloadToken);
- form.target = '_blank';
-
- const input = document.createElement('input');
- input.type = 'hidden';
- input.name = 'paths';
- input.value = JSON.stringify(Array.from(selectedFiles));
- form.appendChild(input);
-
- document.body.appendChild(form);
- form.submit();
- document.body.removeChild(form);
- setSelectedFiles(new Set());
+ onClick={async () => {
+ try {
+ const token = await api.createDownloadToken();
+ const form = document.createElement('form');
+ form.method = 'POST';
+ form.action = api.getBulkDownloadUrl(token);
+ form.target = '_blank';
+
+ const input = document.createElement('input');
+ input.type = 'hidden';
+ input.name = 'paths';
+ input.value = JSON.stringify(Array.from(selectedFiles));
+ form.appendChild(input);
+
+ document.body.appendChild(form);
+ form.submit();
+ document.body.removeChild(form);
+ setSelectedFiles(new Set());
+ } catch (err) {
+ console.error("Failed to download selected files", err);
+ }
}}
>
@@ -2385,20 +2409,28 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
}
label="Download Folder"
- onClick={() => {
- if (!downloadToken) return;
- window.location.href = api.getDownloadFolderUrl(contextMenu.file.path, downloadToken);
- setContextMenu(null);
+ onClick={async () => {
+ try {
+ const token = await api.createDownloadToken();
+ window.location.href = api.getDownloadFolderUrl(contextMenu.file.path, token);
+ setContextMenu(null);
+ } catch (err) {
+ console.error("Failed to download folder", err);
+ }
}}
/>
) : (
}
label="Download"
- onClick={() => {
- if (!downloadToken) return;
- window.location.href = api.getDownloadUrl(contextMenu.file.path, downloadToken);
- setContextMenu(null);
+ onClick={async () => {
+ try {
+ const token = await api.createDownloadToken();
+ window.location.href = api.getDownloadUrl(contextMenu.file.path, token, true);
+ setContextMenu(null);
+ } catch (err) {
+ console.error("Failed to download file", err);
+ }
}}
/>
)}
diff --git a/frontend/src/components/FilePreview.tsx b/frontend/src/components/FilePreview.tsx
index 4c7677b..d0a3946 100644
--- a/frontend/src/components/FilePreview.tsx
+++ b/frontend/src/components/FilePreview.tsx
@@ -67,6 +67,7 @@ export default function FilePreview({ file, onClose, downloadToken }: FilePrevie
const previewType = getPreviewType(file);
const downloadUrl = downloadToken ? api.getDownloadUrl(file.path, downloadToken) : '';
+ const forceDownloadUrl = downloadToken ? api.getDownloadUrl(file.path, downloadToken, true) : '';
return (
@@ -103,7 +104,7 @@ export default function FilePreview({ file, onClose, downloadToken }: FilePrevie
console.error('Failed to load PDF:', error)}
loading={
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index b1b92ad..4de9a85 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -80,6 +80,7 @@ class ApiClient {
if (!retryResponse.ok && retryResponse.status === 401) {
if (typeof window !== 'undefined') window.dispatchEvent(new Event('auth_error'));
+ this.clearTokens();
throw new Error(`HTTP 401`);
}
@@ -90,6 +91,7 @@ class ApiClient {
if (!response.ok && endpoint !== '/api/auth/refresh') {
if (response.status === 401) {
if (typeof window !== 'undefined') window.dispatchEvent(new Event('auth_error'));
+ this.clearTokens();
}
let errorMsg = `HTTP ${response.status}`;
try {
@@ -318,8 +320,9 @@ class ApiClient {
return data.token;
}
- getDownloadUrl(path: string, token: string) {
- return `${API_BASE}/api/files/download?path=${encodeURIComponent(path)}&token=${token}`;
+ getDownloadUrl(path: string, token: string, forceDownload: boolean = false) {
+ const url = `${API_BASE}/api/files/download?path=${encodeURIComponent(path)}&token=${token}`;
+ return forceDownload ? `${url}&download=true` : url;
}
getDownloadFolderUrl(path: string, token: string) {