fix: share dialog not opening + connect to backend API
- Fix showShareDialog: add try-catch, null checks, prevent textContent from destroying header icon (use span child instead) - Capture file/folder target before closeContextMenu to prevent race - createSharedLink now calls real backend POST /api/shares instead of localStorage-only mock (still caches locally for offline compat) - Fix share_handler.rs: use OptionalAuthUser instead of AuthUser to prevent 401 when auth is disabled (same pattern as delete/trash) - Add null-safety to closeShareDialog - Reset new-share-section on dialog open
This commit is contained in:
Generated
+1
-1
@@ -1686,7 +1686,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxicloud"
|
name = "oxicloud"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{
|
|||||||
ports::share_ports::ShareUseCase
|
ports::share_ports::ShareUseCase
|
||||||
},
|
},
|
||||||
common::errors::ErrorKind,
|
common::errors::ErrorKind,
|
||||||
interfaces::middleware::auth::AuthUser,
|
interfaces::middleware::auth::OptionalAuthUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -32,10 +32,10 @@ pub struct VerifyPasswordRequest {
|
|||||||
/// Create a new shared link
|
/// Create a new shared link
|
||||||
pub async fn create_shared_link(
|
pub async fn create_shared_link(
|
||||||
State(share_use_case): State<Arc<dyn ShareUseCase>>,
|
State(share_use_case): State<Arc<dyn ShareUseCase>>,
|
||||||
auth_user: AuthUser,
|
auth_user: OptionalAuthUser,
|
||||||
Json(dto): Json<CreateShareDto>,
|
Json(dto): Json<CreateShareDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let user_id = &auth_user.id;
|
let user_id = auth_user.0.map(|u| u.id).unwrap_or_else(|| "anonymous".to_string());
|
||||||
match share_use_case.create_shared_link(&user_id, dto).await {
|
match share_use_case.create_shared_link(&user_id, dto).await {
|
||||||
Ok(share) => (StatusCode::CREATED, Json(share)).into_response(),
|
Ok(share) => (StatusCode::CREATED, Json(share)).into_response(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -69,10 +69,10 @@ pub async fn get_shared_link(
|
|||||||
/// Get all shared links created by the current user
|
/// Get all shared links created by the current user
|
||||||
pub async fn get_user_shares(
|
pub async fn get_user_shares(
|
||||||
State(share_use_case): State<Arc<dyn ShareUseCase>>,
|
State(share_use_case): State<Arc<dyn ShareUseCase>>,
|
||||||
auth_user: AuthUser,
|
auth_user: OptionalAuthUser,
|
||||||
Query(query): Query<GetSharesQuery>,
|
Query(query): Query<GetSharesQuery>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let user_id = &auth_user.id;
|
let user_id = auth_user.0.map(|u| u.id).unwrap_or_else(|| "anonymous".to_string());
|
||||||
let page = query.page.unwrap_or(1);
|
let page = query.page.unwrap_or(1);
|
||||||
let per_page = query.per_page.unwrap_or(20);
|
let per_page = query.per_page.unwrap_or(20);
|
||||||
|
|
||||||
|
|||||||
+104
-45
@@ -62,8 +62,9 @@ const contextMenus = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('share-folder-option').addEventListener('click', () => {
|
document.getElementById('share-folder-option').addEventListener('click', () => {
|
||||||
if (window.app.contextMenuTargetFolder) {
|
const folder = window.app.contextMenuTargetFolder;
|
||||||
this.showShareDialog(window.app.contextMenuTargetFolder, 'folder');
|
if (folder) {
|
||||||
|
this.showShareDialog(folder, 'folder');
|
||||||
}
|
}
|
||||||
window.ui.closeContextMenu();
|
window.ui.closeContextMenu();
|
||||||
});
|
});
|
||||||
@@ -165,8 +166,9 @@ const contextMenus = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('share-file-option').addEventListener('click', () => {
|
document.getElementById('share-file-option').addEventListener('click', () => {
|
||||||
if (window.app.contextMenuTargetFile) {
|
const file = window.app.contextMenuTargetFile;
|
||||||
this.showShareDialog(window.app.contextMenuTargetFile, 'file');
|
if (file) {
|
||||||
|
this.showShareDialog(file, 'file');
|
||||||
}
|
}
|
||||||
window.ui.closeFileContextMenu();
|
window.ui.closeFileContextMenu();
|
||||||
});
|
});
|
||||||
@@ -433,23 +435,42 @@ const contextMenus = {
|
|||||||
* @param {string} itemType - 'file' or 'folder'
|
* @param {string} itemType - 'file' or 'folder'
|
||||||
*/
|
*/
|
||||||
showShareDialog(item, itemType) {
|
showShareDialog(item, itemType) {
|
||||||
// Update dialog title based on item type
|
try {
|
||||||
const dialogHeader = document.getElementById('share-dialog').querySelector('.share-dialog-header');
|
const shareDialog = document.getElementById('share-dialog');
|
||||||
|
if (!shareDialog) {
|
||||||
|
console.error('Share dialog element not found in DOM');
|
||||||
|
window.ui.showNotification('Error', 'Share dialog not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update dialog title — use the <span> inside header to preserve <i> icon
|
||||||
|
const dialogHeader = shareDialog.querySelector('.share-dialog-header');
|
||||||
|
if (dialogHeader) {
|
||||||
|
const headerSpan = dialogHeader.querySelector('span');
|
||||||
|
const titleText = itemType === 'file' ?
|
||||||
|
(window.i18n ? window.i18n.t('dialogs.share_file') : 'Share file') :
|
||||||
|
(window.i18n ? window.i18n.t('dialogs.share_folder') : 'Share folder');
|
||||||
|
if (headerSpan) {
|
||||||
|
headerSpan.textContent = titleText;
|
||||||
|
} else {
|
||||||
|
dialogHeader.textContent = titleText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const itemName = document.getElementById('shared-item-name');
|
const itemName = document.getElementById('shared-item-name');
|
||||||
|
if (itemName) itemName.textContent = item.name;
|
||||||
// Update dialog content
|
|
||||||
dialogHeader.textContent = itemType === 'file' ?
|
|
||||||
(window.i18n ? window.i18n.t('dialogs.share_file') : 'Share file') :
|
|
||||||
(window.i18n ? window.i18n.t('dialogs.share_folder') : 'Share folder');
|
|
||||||
|
|
||||||
itemName.textContent = item.name;
|
|
||||||
|
|
||||||
// Reset form
|
// Reset form
|
||||||
document.getElementById('share-password').value = '';
|
const pwField = document.getElementById('share-password');
|
||||||
document.getElementById('share-expiration').value = '';
|
const expField = document.getElementById('share-expiration');
|
||||||
document.getElementById('share-permission-read').checked = true;
|
if (pwField) pwField.value = '';
|
||||||
document.getElementById('share-permission-write').checked = false;
|
if (expField) expField.value = '';
|
||||||
document.getElementById('share-permission-reshare').checked = false;
|
const permRead = document.getElementById('share-permission-read');
|
||||||
|
const permWrite = document.getElementById('share-permission-write');
|
||||||
|
const permReshare = document.getElementById('share-permission-reshare');
|
||||||
|
if (permRead) permRead.checked = true;
|
||||||
|
if (permWrite) permWrite.checked = false;
|
||||||
|
if (permReshare) permReshare.checked = false;
|
||||||
|
|
||||||
// Store the current item and type for use when creating the share
|
// Store the current item and type for use when creating the share
|
||||||
window.app.shareDialogItem = item;
|
window.app.shareDialogItem = item;
|
||||||
@@ -526,14 +547,23 @@ const contextMenus = {
|
|||||||
document.getElementById('existing-shares-section').style.display = 'none';
|
document.getElementById('existing-shares-section').style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide new-share section from previous use
|
||||||
|
const newShareSection = document.getElementById('new-share-section');
|
||||||
|
if (newShareSection) newShareSection.style.display = 'none';
|
||||||
|
|
||||||
// Show dialog
|
// Show dialog
|
||||||
document.getElementById('share-dialog').style.display = 'flex';
|
shareDialog.style.display = 'flex';
|
||||||
|
console.log('Share dialog opened for', itemType, item.name);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error opening share dialog:', error);
|
||||||
|
window.ui.showNotification('Error', 'Could not open share dialog');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a shared link with the configured options
|
* Create a shared link with the configured options
|
||||||
*/
|
*/
|
||||||
createSharedLink() {
|
async createSharedLink() {
|
||||||
if (!window.app.shareDialogItem || !window.app.shareDialogItemType) {
|
if (!window.app.shareDialogItem || !window.app.shareDialogItemType) {
|
||||||
window.ui.showNotification('Error', 'Could not share the item');
|
window.ui.showNotification('Error', 'Could not share the item');
|
||||||
return;
|
return;
|
||||||
@@ -546,46 +576,74 @@ const contextMenus = {
|
|||||||
const permissionWrite = document.getElementById('share-permission-write').checked;
|
const permissionWrite = document.getElementById('share-permission-write').checked;
|
||||||
const permissionReshare = document.getElementById('share-permission-reshare').checked;
|
const permissionReshare = document.getElementById('share-permission-reshare').checked;
|
||||||
|
|
||||||
// Prepare options
|
const item = window.app.shareDialogItem;
|
||||||
const options = {
|
const itemType = window.app.shareDialogItemType;
|
||||||
|
|
||||||
|
// Build DTO for backend API
|
||||||
|
const createDto = {
|
||||||
|
item_id: item.id,
|
||||||
|
item_type: itemType,
|
||||||
password: password || null,
|
password: password || null,
|
||||||
expirationDate: expirationDate || null,
|
expires_at: expirationDate ? Math.floor(new Date(expirationDate).getTime() / 1000) : null,
|
||||||
permissions: {
|
permissions: {
|
||||||
read: permissionRead,
|
read: permissionRead,
|
||||||
write: permissionWrite,
|
write: permissionWrite,
|
||||||
reshare: permissionReshare
|
reshare: permissionReshare
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item = window.app.shareDialogItem;
|
const token = localStorage.getItem('oxicloud_token');
|
||||||
const itemType = window.app.shareDialogItemType;
|
const headers = { 'Content-Type': 'application/json' };
|
||||||
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||||
// Create share
|
|
||||||
const shareInfo = window.fileSharing.generateSharedLink(
|
const response = await fetch('/api/shares', {
|
||||||
item.id,
|
method: 'POST',
|
||||||
itemType,
|
headers,
|
||||||
options
|
body: JSON.stringify(createDto)
|
||||||
);
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errBody = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errBody.error || `Server error ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const shareInfo = await response.json();
|
||||||
|
|
||||||
|
// Also save to localStorage for offline / shared-view compatibility
|
||||||
|
window.fileSharing.saveSharedLink({
|
||||||
|
id: shareInfo.id,
|
||||||
|
type: shareInfo.item_type,
|
||||||
|
itemId: shareInfo.item_id,
|
||||||
|
url: shareInfo.url,
|
||||||
|
token: shareInfo.token,
|
||||||
|
password_protected: shareInfo.has_password,
|
||||||
|
expires_at: shareInfo.expires_at ? new Date(shareInfo.expires_at * 1000).toISOString() : null,
|
||||||
|
permissions: shareInfo.permissions,
|
||||||
|
created_at: new Date(shareInfo.created_at * 1000).toISOString(),
|
||||||
|
access_count: shareInfo.access_count || 0,
|
||||||
|
name: item.name,
|
||||||
|
dateShared: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
// Update UI with new share
|
// Update UI with new share
|
||||||
const shareUrl = document.getElementById('generated-share-url');
|
const shareUrl = document.getElementById('generated-share-url');
|
||||||
shareUrl.value = shareInfo.url;
|
if (shareUrl) {
|
||||||
document.getElementById('new-share-section').style.display = 'block';
|
shareUrl.value = shareInfo.url;
|
||||||
|
document.getElementById('new-share-section').style.display = 'block';
|
||||||
// Focus and select for easy copying
|
shareUrl.focus();
|
||||||
shareUrl.focus();
|
shareUrl.select();
|
||||||
shareUrl.select();
|
}
|
||||||
|
|
||||||
// Show success message
|
// Show success message
|
||||||
window.ui.showNotification('Link created', 'Shared link created successfully');
|
window.ui.showNotification(
|
||||||
|
window.i18n ? window.i18n.t('notifications.link_created') : 'Link created',
|
||||||
// Reload existing shares
|
window.i18n ? window.i18n.t('notifications.share_success') : 'Shared link created successfully'
|
||||||
this.showShareDialog(item, itemType);
|
);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating shared link:', error);
|
console.error('Error creating shared link:', error);
|
||||||
window.ui.showNotification('Error', 'Could not create shared link');
|
window.ui.showNotification('Error', error.message || 'Could not create shared link');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -639,7 +697,8 @@ const contextMenus = {
|
|||||||
* Close share dialog
|
* Close share dialog
|
||||||
*/
|
*/
|
||||||
closeShareDialog() {
|
closeShareDialog() {
|
||||||
document.getElementById('share-dialog').style.display = 'none';
|
const dialog = document.getElementById('share-dialog');
|
||||||
|
if (dialog) dialog.style.display = 'none';
|
||||||
window.app.shareDialogItem = null;
|
window.app.shareDialogItem = null;
|
||||||
window.app.shareDialogItemType = null;
|
window.app.shareDialogItemType = null;
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-2
@@ -204,8 +204,8 @@ const ui = {
|
|||||||
contextMenus.closeShareDialog();
|
contextMenus.closeShareDialog();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('share-confirm-btn').addEventListener('click', () => {
|
document.getElementById('share-confirm-btn').addEventListener('click', async () => {
|
||||||
contextMenus.createSharedLink();
|
await contextMenus.createSharedLink();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('copy-share-btn').addEventListener('click', async () => {
|
document.getElementById('copy-share-btn').addEventListener('click', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user