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]]
name = "oxicloud"
version = "0.3.2"
version = "0.3.3"
dependencies = [
"anyhow",
"argon2",
+5 -5
View File
@@ -15,7 +15,7 @@ use crate::{
ports::share_ports::ShareUseCase
},
common::errors::ErrorKind,
interfaces::middleware::auth::AuthUser,
interfaces::middleware::auth::OptionalAuthUser,
};
#[derive(Debug, Deserialize)]
@@ -32,10 +32,10 @@ pub struct VerifyPasswordRequest {
/// Create a new shared link
pub async fn create_shared_link(
State(share_use_case): State<Arc<dyn ShareUseCase>>,
auth_user: AuthUser,
auth_user: OptionalAuthUser,
Json(dto): Json<CreateShareDto>,
) -> 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 {
Ok(share) => (StatusCode::CREATED, Json(share)).into_response(),
Err(err) => {
@@ -69,10 +69,10 @@ pub async fn get_shared_link(
/// Get all shared links created by the current user
pub async fn get_user_shares(
State(share_use_case): State<Arc<dyn ShareUseCase>>,
auth_user: AuthUser,
auth_user: OptionalAuthUser,
Query(query): Query<GetSharesQuery>,
) -> 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 per_page = query.per_page.unwrap_or(20);
+104 -45
View File
@@ -62,8 +62,9 @@ const contextMenus = {
});
document.getElementById('share-folder-option').addEventListener('click', () => {
if (window.app.contextMenuTargetFolder) {
this.showShareDialog(window.app.contextMenuTargetFolder, 'folder');
const folder = window.app.contextMenuTargetFolder;
if (folder) {
this.showShareDialog(folder, 'folder');
}
window.ui.closeContextMenu();
});
@@ -165,8 +166,9 @@ const contextMenus = {
});
document.getElementById('share-file-option').addEventListener('click', () => {
if (window.app.contextMenuTargetFile) {
this.showShareDialog(window.app.contextMenuTargetFile, 'file');
const file = window.app.contextMenuTargetFile;
if (file) {
this.showShareDialog(file, 'file');
}
window.ui.closeFileContextMenu();
});
@@ -433,23 +435,42 @@ const contextMenus = {
* @param {string} itemType - 'file' or 'folder'
*/
showShareDialog(item, itemType) {
// Update dialog title based on item type
const dialogHeader = document.getElementById('share-dialog').querySelector('.share-dialog-header');
try {
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');
// 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;
if (itemName) itemName.textContent = item.name;
// Reset form
document.getElementById('share-password').value = '';
document.getElementById('share-expiration').value = '';
document.getElementById('share-permission-read').checked = true;
document.getElementById('share-permission-write').checked = false;
document.getElementById('share-permission-reshare').checked = false;
const pwField = document.getElementById('share-password');
const expField = document.getElementById('share-expiration');
if (pwField) pwField.value = '';
if (expField) expField.value = '';
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
window.app.shareDialogItem = item;
@@ -526,14 +547,23 @@ const contextMenus = {
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
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
*/
createSharedLink() {
async createSharedLink() {
if (!window.app.shareDialogItem || !window.app.shareDialogItemType) {
window.ui.showNotification('Error', 'Could not share the item');
return;
@@ -546,46 +576,74 @@ const contextMenus = {
const permissionWrite = document.getElementById('share-permission-write').checked;
const permissionReshare = document.getElementById('share-permission-reshare').checked;
// Prepare options
const options = {
const item = window.app.shareDialogItem;
const itemType = window.app.shareDialogItemType;
// Build DTO for backend API
const createDto = {
item_id: item.id,
item_type: itemType,
password: password || null,
expirationDate: expirationDate || null,
expires_at: expirationDate ? Math.floor(new Date(expirationDate).getTime() / 1000) : null,
permissions: {
read: permissionRead,
write: permissionWrite,
reshare: permissionReshare
}
};
try {
const item = window.app.shareDialogItem;
const itemType = window.app.shareDialogItemType;
// Create share
const shareInfo = window.fileSharing.generateSharedLink(
item.id,
itemType,
options
);
const token = localStorage.getItem('oxicloud_token');
const headers = { 'Content-Type': 'application/json' };
if (token) headers['Authorization'] = `Bearer ${token}`;
const response = await fetch('/api/shares', {
method: 'POST',
headers,
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
const shareUrl = document.getElementById('generated-share-url');
shareUrl.value = shareInfo.url;
document.getElementById('new-share-section').style.display = 'block';
// Focus and select for easy copying
shareUrl.focus();
shareUrl.select();
if (shareUrl) {
shareUrl.value = shareInfo.url;
document.getElementById('new-share-section').style.display = 'block';
shareUrl.focus();
shareUrl.select();
}
// Show success message
window.ui.showNotification('Link created', 'Shared link created successfully');
// Reload existing shares
this.showShareDialog(item, itemType);
window.ui.showNotification(
window.i18n ? window.i18n.t('notifications.link_created') : 'Link created',
window.i18n ? window.i18n.t('notifications.share_success') : 'Shared link created successfully'
);
} catch (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
*/
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.shareDialogItemType = null;
},
+2 -2
View File
@@ -204,8 +204,8 @@ const ui = {
contextMenus.closeShareDialog();
});
document.getElementById('share-confirm-btn').addEventListener('click', () => {
contextMenus.createSharedLink();
document.getElementById('share-confirm-btn').addEventListener('click', async () => {
await contextMenus.createSharedLink();
});
document.getElementById('copy-share-btn').addEventListener('click', async () => {