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:
Dionisio
2026-02-13 12:29:21 +01:00
parent 1dec88eb59
commit ea234bc6a1
4 changed files with 112 additions and 53 deletions
Generated
+1 -1
View File
@@ -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",
+5 -5
View File
@@ -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);
+102 -43
View File
@@ -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,10 +576,15 @@ 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,
@@ -558,34 +593,57 @@ const contextMenus = {
}; };
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 response = await fetch('/api/shares', {
const shareInfo = window.fileSharing.generateSharedLink( method: 'POST',
item.id, headers,
itemType, body: JSON.stringify(createDto)
options });
);
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
View File
@@ -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 () => {