Merge pull request #309 from EdouardVanbelle/refactor/keep-badge-order

This commit is contained in:
Dionisio Pozo
2026-04-25 21:20:58 +02:00
committed by GitHub
14 changed files with 198 additions and 178 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
padding: 6px; padding: 6px;
min-width: 200px; min-width: 200px;
z-index: 2000; z-index: 2000;
display: none; display: block;
animation: contextMenuIn 0.15s ease-out; animation: contextMenuIn 0.15s ease-out;
} }
+5 -13
View File
@@ -27,7 +27,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: var(--color-overlay-light); background-color: var(--color-overlay-light);
display: none; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 3000; z-index: 3000;
@@ -106,7 +106,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: var(--color-overlay-light); background-color: var(--color-overlay-light);
display: none; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 3000; z-index: 3000;
@@ -205,7 +205,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: var(--color-overlay-light); background-color: var(--color-overlay-light);
display: none; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 3000; z-index: 3000;
@@ -213,10 +213,6 @@
animation: modalFadeIn 0.2s ease; animation: modalFadeIn 0.2s ease;
} }
.shared-dialog.active {
display: flex;
}
.shared-dialog-content { .shared-dialog-content {
background-color: var(--color-bg-surface); background-color: var(--color-bg-surface);
border-radius: 16px; border-radius: 16px;
@@ -512,7 +508,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: var(--color-overlay-light); background-color: var(--color-overlay-light);
display: none; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 4000; z-index: 4000;
@@ -593,16 +589,12 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: var(--color-overlay); background-color: var(--color-overlay);
display: none; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 1000; z-index: 1000;
} }
.dialog.active {
display: flex;
}
.dialog-content { .dialog-content {
background-color: var(--color-bg-surface); background-color: var(--color-bg-surface);
border-radius: 10px; border-radius: 10px;
+2 -6
View File
@@ -1,6 +1,6 @@
/* About Modal */ /* About Modal */
.about-modal-overlay { .about-modal-overlay {
display: none; display: flex;
position: fixed; position: fixed;
inset: 0; inset: 0;
background: var(--color-overlay); background: var(--color-overlay);
@@ -10,10 +10,6 @@
align-items: center; align-items: center;
} }
.about-modal-overlay.show {
display: flex;
}
.about-modal { .about-modal {
background: var(--color-bg-surface); background: var(--color-bg-surface);
border-radius: 20px; border-radius: 20px;
@@ -130,7 +126,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: var(--color-overlay); background-color: var(--color-overlay);
display: none; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 3000; z-index: 3000;
+2 -2
View File
@@ -37,7 +37,7 @@
text-align: center; text-align: center;
margin: 20px 0; margin: 20px 0;
color: var(--color-text-medium); color: var(--color-text-medium);
display: none; display: block;
} }
.dropzone-icon { .dropzone-icon {
@@ -53,7 +53,7 @@
.upload-progress { .upload-progress {
margin-top: 15px; margin-top: 15px;
width: 100%; width: 100%;
display: none; display: block;
} }
.progress-bar { .progress-bar {
+1 -5
View File
@@ -11,7 +11,7 @@
} }
.upload-dropdown-menu { .upload-dropdown-menu {
display: none; display: block;
position: absolute; position: absolute;
top: calc(100% + 6px); top: calc(100% + 6px);
left: 0; left: 0;
@@ -25,10 +25,6 @@
animation: dropdownFadeIn 0.15s ease-out; animation: dropdownFadeIn 0.15s ease-out;
} }
.upload-dropdown-menu.show {
display: block;
}
@keyframes dropdownFadeIn { @keyframes dropdownFadeIn {
from { from {
opacity: 0; opacity: 0;
+3 -3
View File
@@ -216,12 +216,12 @@
<span class="breadcrumb-item" data-i18n="breadcrumb.home">Home</span> <span class="breadcrumb-item" data-i18n="breadcrumb.home">Home</span>
</div> </div>
<div class="dropzone" id="dropzone"> <div class="dropzone hidden" id="dropzone">
<i class="fas fa-cloud-upload-alt dropzone-icon"></i> <i class="fas fa-cloud-upload-alt dropzone-icon"></i>
<p data-i18n="dropzone.drag_files">Drag files here or click to select</p> <p data-i18n="dropzone.drag_files">Drag files here or click to select</p>
<input type="file" id="file-input" class="hidden" multiple> <input type="file" id="file-input" class="hidden" multiple>
<input type="file" id="folder-input" class="hidden" webkitdirectory directory multiple> <input type="file" id="folder-input" class="hidden" webkitdirectory directory multiple>
<div class="upload-progress"> <div class="upload-progress hidden">
<div class="progress-bar"> <div class="progress-bar">
<div class="progress-fill"></div> <div class="progress-fill"></div>
</div> </div>
@@ -266,7 +266,7 @@
</div> </div>
<!-- About Modal --> <!-- About Modal -->
<div class="about-modal-overlay" id="about-modal-overlay"> <div class="about-modal-overlay hidden" id="about-modal-overlay">
<div class="about-modal"> <div class="about-modal">
<div class="about-modal-logo"> <div class="about-modal-logo">
<i class="fas fa-cloud"></i> <i class="fas fa-cloud"></i>
+12 -13
View File
@@ -89,7 +89,7 @@ const ACTIONS_BAR_TEMPLATES = {
<span data-i18n="actions.upload">Upload</span> <span data-i18n="actions.upload">Upload</span>
<i class="fas fa-caret-down icon-ml"></i> <i class="fas fa-caret-down icon-ml"></i>
</button> </button>
<div class="upload-dropdown-menu" id="upload-dropdown-menu"> <div class="upload-dropdown-menu hidden" id="upload-dropdown-menu">
<button class="upload-dropdown-item" id="upload-files-btn"> <button class="upload-dropdown-item" id="upload-files-btn">
<i class="fas fa-file"></i> <i class="fas fa-file"></i>
<span data-i18n="actions.upload_files">Upload files</span> <span data-i18n="actions.upload_files">Upload files</span>
@@ -187,14 +187,14 @@ function setupActionsBarDelegation() {
case 'upload-files-btn': { case 'upload-files-btn': {
e.stopPropagation(); e.stopPropagation();
const menu = document.getElementById('upload-dropdown-menu'); const menu = document.getElementById('upload-dropdown-menu');
if (menu) menu.classList.remove('show'); if (menu) menu.classList.add('hidden');
if (elements.fileInput) elements.fileInput.click(); if (elements.fileInput) elements.fileInput.click();
break; break;
} }
case 'upload-folder-btn': { case 'upload-folder-btn': {
e.stopPropagation(); e.stopPropagation();
const menu = document.getElementById('upload-dropdown-menu'); const menu = document.getElementById('upload-dropdown-menu');
if (menu) menu.classList.remove('show'); if (menu) menu.classList.add('hidden');
const folderInput = document.getElementById('folder-input'); const folderInput = document.getElementById('folder-input');
if (folderInput) folderInput.click(); if (folderInput) folderInput.click();
break; break;
@@ -456,13 +456,13 @@ function setupUploadDropdown() {
'click', 'click',
(e) => { (e) => {
e.stopPropagation(); e.stopPropagation();
const isOpen = menu.classList.contains('show'); const isOpen = !menu.classList.contains('hidden');
// Close any other open dropdowns // Close any other open dropdowns
document.querySelectorAll('.upload-dropdown-menu.show').forEach((m) => { document.querySelectorAll('.upload-dropdown-menu').forEach((m) => {
m.classList.remove('show'); m.classList.add('hidden');
}); });
if (!isOpen) { if (!isOpen) {
menu.classList.add('show'); menu.classList.remove('hidden');
} }
}, },
{ signal } { signal }
@@ -475,8 +475,8 @@ function setupUploadDropdown() {
} }
uploadDropdownDocumentClickHandler = (e) => { uploadDropdownDocumentClickHandler = (e) => {
if (e.target.closest('#upload-dropdown')) return; if (e.target.closest('#upload-dropdown')) return;
document.querySelectorAll('.upload-dropdown-menu.show').forEach((m) => { document.querySelectorAll('.upload-dropdown-menu').forEach((m) => {
m.classList.remove('show'); m.classList.add('hidden');
}); });
}; };
document.addEventListener('click', uploadDropdownDocumentClickHandler); document.addEventListener('click', uploadDropdownDocumentClickHandler);
@@ -663,13 +663,12 @@ function setupEventListeners() {
// Global events to close context menus and deselect cards // Global events to close context menus and deselect cards
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
const folderMenu = document.getElementById('folder-context-menu'); const folderMenu = document.getElementById('folder-context-menu');
const fileMenu = document.getElementById('file-context-menu'); if (folderMenu && !folderMenu.classList.contains('hidden') && !folderMenu.contains(e.target)) {
if (folderMenu && folderMenu.style.display === 'block' && !folderMenu.contains(e.target)) {
ui.closeContextMenu(); ui.closeContextMenu();
} }
if (fileMenu && fileMenu.style.display === 'block' && !fileMenu.contains(e.target)) { const fileMenu = document.getElementById('file-context-menu');
if (fileMenu && !fileMenu.classList.contains('hidden') && !fileMenu.contains(e.target)) {
ui.closeFileContextMenu(); ui.closeFileContextMenu();
} }
}); });
+93 -52
View File
@@ -7,7 +7,7 @@
import { escapeHtml, formatDateTime, formatFileSize } from '../core/formatters.js'; import { escapeHtml, formatDateTime, formatFileSize } from '../core/formatters.js';
import { i18n } from '../core/i18n.js'; import { i18n } from '../core/i18n.js';
import { OxiIcons, replaceIconsInElement } from '../core/icons.js'; import { OxiIcons } from '../core/icons.js';
import { contextMenus } from '../features/files/contextMenus.js'; import { contextMenus } from '../features/files/contextMenus.js';
import { fileOps } from '../features/files/fileOperations.js'; import { fileOps } from '../features/files/fileOperations.js';
import { inlineViewer } from '../features/files/inlineViewer.js'; import { inlineViewer } from '../features/files/inlineViewer.js';
@@ -38,7 +38,7 @@ const ui = {
// Folder context menu // Folder context menu
if (!document.getElementById('folder-context-menu')) { if (!document.getElementById('folder-context-menu')) {
const folderMenu = document.createElement('div'); const folderMenu = document.createElement('div');
folderMenu.className = 'context-menu'; folderMenu.classList.add('context-menu', 'hidden');
folderMenu.id = 'folder-context-menu'; folderMenu.id = 'folder-context-menu';
folderMenu.innerHTML = ` folderMenu.innerHTML = `
<div class="context-menu-item" id="download-folder-option"> <div class="context-menu-item" id="download-folder-option">
@@ -68,7 +68,7 @@ const ui = {
// File context menu // File context menu
if (!document.getElementById('file-context-menu')) { if (!document.getElementById('file-context-menu')) {
const fileMenu = document.createElement('div'); const fileMenu = document.createElement('div');
fileMenu.className = 'context-menu'; fileMenu.classList.add('context-menu', 'hidden');
fileMenu.id = 'file-context-menu'; fileMenu.id = 'file-context-menu';
fileMenu.innerHTML = ` fileMenu.innerHTML = `
<div class="context-menu-item" id="view-file-option"> <div class="context-menu-item" id="view-file-option">
@@ -111,7 +111,7 @@ const ui = {
// Rename dialog — modern // Rename dialog — modern
if (!document.getElementById('rename-dialog')) { if (!document.getElementById('rename-dialog')) {
const renameDialog = document.createElement('div'); const renameDialog = document.createElement('div');
renameDialog.className = 'rename-dialog'; renameDialog.classList.add('rename-dialog', 'hidden');
renameDialog.id = 'rename-dialog'; renameDialog.id = 'rename-dialog';
renameDialog.innerHTML = ` renameDialog.innerHTML = `
<div class="rename-dialog-content"> <div class="rename-dialog-content">
@@ -134,7 +134,7 @@ const ui = {
// Move dialog — modern with navigation // Move dialog — modern with navigation
if (!document.getElementById('move-file-dialog')) { if (!document.getElementById('move-file-dialog')) {
const moveDialog = document.createElement('div'); const moveDialog = document.createElement('div');
moveDialog.className = 'rename-dialog'; moveDialog.classList.add('rename-dialog', 'hidden');
moveDialog.id = 'move-file-dialog'; moveDialog.id = 'move-file-dialog';
moveDialog.innerHTML = ` moveDialog.innerHTML = `
<div class="rename-dialog-content"> <div class="rename-dialog-content">
@@ -161,7 +161,7 @@ const ui = {
// Share dialog // Share dialog
if (!document.getElementById('share-dialog')) { if (!document.getElementById('share-dialog')) {
const shareDialog = document.createElement('div'); const shareDialog = document.createElement('div');
shareDialog.className = 'share-dialog'; shareDialog.classList.add('share-dialog', 'hidden');
shareDialog.id = 'share-dialog'; shareDialog.id = 'share-dialog';
shareDialog.innerHTML = ` shareDialog.innerHTML = `
<div class="share-dialog-content"> <div class="share-dialog-content">
@@ -208,6 +208,7 @@ const ui = {
</div> </div>
</div> </div>
</div> </div>
<button class="btn btn-primary btn-small" id="share-confirm-btn" data-i18n="actions.share">Share</button>
</div> </div>
<div id="new-share-section" class="share-section hidden"> <div id="new-share-section" class="share-section hidden">
@@ -226,15 +227,15 @@ const ui = {
</div> </div>
<div class="share-dialog-buttons"> <div class="share-dialog-buttons">
<button class="btn btn-secondary" id="share-cancel-btn" data-i18n="actions.cancel">Cancel</button> <button class="btn btn-secondary" id="share-close-btn" data-i18n="actions.close">Close</button>
<button class="btn btn-primary" id="share-confirm-btn" data-i18n="actions.share">Share</button>
</div> </div>
</div> </div>
`; `;
i18n.translateElement(shareDialog);
document.body.appendChild(shareDialog); document.body.appendChild(shareDialog);
// Add event listeners for share dialog // Add event listeners for share dialog
document.getElementById('share-cancel-btn').addEventListener('click', () => { document.getElementById('share-close-btn').addEventListener('click', () => {
contextMenus.closeShareDialog(); contextMenus.closeShareDialog();
}); });
@@ -251,12 +252,26 @@ const ui = {
const shareUrl = document.getElementById('generated-share-url').value; const shareUrl = document.getElementById('generated-share-url').value;
contextMenus.showEmailNotificationDialog(shareUrl); contextMenus.showEmailNotificationDialog(shareUrl);
}); });
// FIXME make generic function (close all dialog / etc)
document.addEventListener('keydown', (e) => {
const dialog = document.getElementById('share-dialog');
if (e.key === 'Escape' && !dialog?.classList.contains('hidden')) {
contextMenus.closeShareDialog();
}
});
shareDialog.addEventListener('click', (e) => {
if (e.target === shareDialog) {
contextMenus.closeShareDialog();
}
});
} }
// Notification dialog // Notification dialog
if (!document.getElementById('notification-dialog')) { if (!document.getElementById('notification-dialog')) {
const notificationDialog = document.createElement('div'); const notificationDialog = document.createElement('div');
notificationDialog.className = 'share-dialog'; notificationDialog.classList.add('share-dialog', 'hidden');
notificationDialog.id = 'notification-dialog'; notificationDialog.id = 'notification-dialog';
notificationDialog.innerHTML = ` notificationDialog.innerHTML = `
<div class="share-dialog-content"> <div class="share-dialog-content">
@@ -298,7 +313,7 @@ const ui = {
// Playlist selection dialog // Playlist selection dialog
if (!document.getElementById('playlist-dialog')) { if (!document.getElementById('playlist-dialog')) {
const playlistDialog = document.createElement('div'); const playlistDialog = document.createElement('div');
playlistDialog.className = 'share-dialog'; playlistDialog.classList.add('share-dialog', 'hidden');
playlistDialog.id = 'playlist-dialog'; playlistDialog.id = 'playlist-dialog';
playlistDialog.innerHTML = ` playlistDialog.innerHTML = `
<div class="share-dialog-content"> <div class="share-dialog-content">
@@ -417,7 +432,7 @@ const ui = {
fileOps.uploadFiles(droppedEntries.map((x) => x.file)); fileOps.uploadFiles(droppedEntries.map((x) => x.file));
} }
setTimeout(() => { setTimeout(() => {
dropzone.style.display = 'none'; dropzone?.classList.add('hidden');
}, 500); }, 500);
return; return;
} }
@@ -431,7 +446,7 @@ const ui = {
} }
} }
setTimeout(() => { setTimeout(() => {
dropzone.style.display = 'none'; dropzone?.classList.add('hidden');
}, 500); }, 500);
}); });
@@ -439,8 +454,8 @@ const ui = {
document.addEventListener('dragover', (e) => { document.addEventListener('dragover', (e) => {
e.preventDefault(); e.preventDefault();
if (e.dataTransfer.types.includes('Files')) { if (e.dataTransfer.types.includes('Files')) {
dropzone.style.display = 'block'; dropzone?.classList.remove('hidden');
dropzone.classList.add('active'); dropzone?.classList.add('active');
} }
}); });
@@ -449,7 +464,7 @@ const ui = {
dropzone.classList.remove('active'); dropzone.classList.remove('active');
setTimeout(() => { setTimeout(() => {
if (!dropzone.classList.contains('active')) { if (!dropzone.classList.contains('active')) {
dropzone.style.display = 'none'; dropzone?.classList.add('hidden');
} }
}, 100); }, 100);
} }
@@ -473,7 +488,7 @@ const ui = {
fileOps.uploadFiles(droppedEntries.map((x) => x.file)); fileOps.uploadFiles(droppedEntries.map((x) => x.file));
} }
setTimeout(() => { setTimeout(() => {
dropzone.style.display = 'none'; dropzone?.classList.add('hidden');
}, 500); }, 500);
return; return;
} }
@@ -488,7 +503,7 @@ const ui = {
} }
setTimeout(() => { setTimeout(() => {
dropzone.style.display = 'none'; dropzone?.classList.add('hidden');
}, 500); }, 500);
}); });
}, },
@@ -667,7 +682,7 @@ const ui = {
closeContextMenu() { closeContextMenu() {
const menu = document.getElementById('folder-context-menu'); const menu = document.getElementById('folder-context-menu');
if (menu) { if (menu) {
menu.style.display = 'none'; menu.classList.add('hidden');
app.contextMenuTargetFolder = null; app.contextMenuTargetFolder = null;
} }
}, },
@@ -678,7 +693,7 @@ const ui = {
closeFileContextMenu() { closeFileContextMenu() {
const menu = document.getElementById('file-context-menu'); const menu = document.getElementById('file-context-menu');
if (menu) { if (menu) {
menu.style.display = 'none'; menu.classList.add('hidden');
app.contextMenuTargetFile = null; app.contextMenuTargetFile = null;
} }
}, },
@@ -994,7 +1009,7 @@ const ui = {
} }
menu.style.left = `${e.pageX}px`; menu.style.left = `${e.pageX}px`;
menu.style.top = `${e.pageY}px`; menu.style.top = `${e.pageY}px`;
menu.style.display = 'block'; menu?.classList.remove('hidden');
}); });
// dragstart // dragstart
@@ -1119,18 +1134,19 @@ const ui = {
* ================================================================ */ * ================================================================ */
_bindStarClick(el) { _bindStarClick(el) {
const star = el.querySelector('.favorite-star'); const star = el.querySelector('.favorite-star');
if (!star) return; star?.addEventListener('click', (e) => {
star.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
e.preventDefault(); e.preventDefault();
if (!favorites) return; if (!favorites) return;
const itemId = star.dataset.itemId; // FIXME: make a function
const itemType = star.dataset.itemType; const itemElement = shared?.closest('.file-item');
const itemName = star.dataset.itemName;
const itemId = itemElement.dataset.fileId ? itemElement.dataset.fileId : itemElement.dataset.folderId;
const itemType = itemElement.dataset.fileId ? 'file' : 'folder';
const itemName = itemElement.dataset.fileId ? itemElement.dataset.fileName : itemElement.dataset.folderName;
const isActive = star.classList.contains('active'); const isActive = star.classList.contains('active');
@@ -1147,6 +1163,30 @@ const ui = {
contextMenus.syncFavoriteOptionLabels(); contextMenus.syncFavoriteOptionLabels();
} }
}); });
const shared = el.querySelector('.file-badge-shared');
shared?.addEventListener('click', (e) => {
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
// FIXME: make a function
const itemElement = shared?.closest('.file-item');
const itemId = itemElement.dataset.fileId ? itemElement.dataset.fileId : itemElement.dataset.folderId;
const itemType = itemElement.dataset.fileId ? 'file' : 'folder';
const itemName = itemElement.dataset.fileId ? itemElement.dataset.fileName : itemElement.dataset.folderName;
// TODO corrently dirty
const item = {
id: itemId,
item_id: itemId,
item_type: itemType,
item_name: itemName
};
contextMenus.showShareDialog(item, itemType);
});
}, },
/** /**
@@ -1155,9 +1195,10 @@ const ui = {
setFavoriteVisualState(itemId, itemType, isFavorite) { setFavoriteVisualState(itemId, itemType, isFavorite) {
const selector = itemType === 'folder' ? `#files-list .file-item[data-folder-id="${itemId}"]` : `#files-list .file-item[data-file-id="${itemId}"]`; const selector = itemType === 'folder' ? `#files-list .file-item[data-folder-id="${itemId}"]` : `#files-list .file-item[data-file-id="${itemId}"]`;
const card = document.querySelector(selector); const item = document.querySelector(selector);
const starBtn = card ? card.querySelector('.favorite-star') : null; const starBtn = item?.querySelector('.favorite-star');
// chzn
if (starBtn) { if (starBtn) {
starBtn.classList.toggle('active', !!isFavorite); starBtn.classList.toggle('active', !!isFavorite);
@@ -1180,20 +1221,21 @@ const ui = {
} }
} }
const listItem = document.querySelector(selector); // toggle favorite's badge
if (listItem) { if (item) {
const nameCell = listItem.querySelector('.name-cell'); const badgeFavorite = item.querySelector('.file-badge-favorite');
if (nameCell) { badgeFavorite?.classList.toggle('hidden', !isFavorite);
let inlineStar = nameCell.querySelector('.favorite-star-inline'); }
if (isFavorite && !inlineStar) { },
inlineStar = document.createElement('i');
inlineStar.className = 'fas fa-star favorite-star-inline'; setSharedVisualState(itemId, itemType, isShared) {
nameCell.appendChild(inlineStar); console.log(`setSharedVisual call for ${itemId} ${itemType} to ${isShared}`);
replaceIconsInElement(nameCell); const selector = itemType === 'folder' ? `#files-list .file-item[data-folder-id="${itemId}"]` : `#files-list .file-item[data-file-id="${itemId}"]`;
} else if (!isFavorite && inlineStar) { // toggle favorite's badge
inlineStar.remove(); const item = document.querySelector(selector);
} if (item) {
} const badgeShared = item.querySelector('.file-badge-shared');
badgeShared?.classList.toggle('hidden', !isShared);
} }
}, },
@@ -1220,14 +1262,14 @@ const ui = {
<i class="fas fa-folder"></i> <i class="fas fa-folder"></i>
</div> </div>
<span>${escapeHtml(folder.name)}</span> <span>${escapeHtml(folder.name)}</span>
${isFav ? '<i class="fas fa-star favorite-star-inline"></i>' : ''} <div class="file-badge file-badge-favorite ${isFav ? '' : 'hidden'}"><i class="fas fa-star favorite-star-inline"></i></div>
${isShared ? '<div class="file-badge-shared"><i class="fas fa-share-alt"></i></div>' : ''} <div class="file-badge file-badge-shared ${isShared ? '' : 'hidden'}"><i class="fas fa-share-alt"></i></div>
</div> </div>
<div class="type-cell">${i18n ? i18n.t('files.file_types.folder') : 'Folder'}</div> <div class="type-cell">${i18n ? i18n.t('files.file_types.folder') : 'Folder'}</div>
<div class="size-cell">--</div> <div class="size-cell">--</div>
<div class="date-cell">${formattedDate}</div> <div class="date-cell">${formattedDate}</div>
<div class="action-cell"> <div class="action-cell">
<button class="favorite-star${isFav ? ' active' : ''}" data-item-id="${folder.id}" data-item-type="folder" data-item-name="${escapeHtml(folder.name)}"> <button class="favorite-star${isFav ? ' active' : ''}">
<i class="${isFav ? 'fas' : 'far'} fa-star"></i> <i class="${isFav ? 'fas' : 'far'} fa-star"></i>
</button> </button>
<button class="file-actions"><i class="fas fa-ellipsis-v"></i></button> <button class="file-actions"><i class="fas fa-ellipsis-v"></i></button>
@@ -1268,15 +1310,14 @@ const ui = {
<i class="${iconClass}"></i> <i class="${iconClass}"></i>
</div> </div>
<span>${escapeHtml(file.name)}</span> <span>${escapeHtml(file.name)}</span>
${isFav ? '<i class="fas fa-star favorite-star-inline"></i>' : ''} <div class="file-badge file-badge-favorite ${isFav ? '' : 'hidden'}"><i class="fas fa-star favorite-star-inline"></i></div>
${isShared ? '<div class="file-badge-shared"><i class="fas fa-share-alt"></i></div>' : ''} <div class="file-badge file-badge-shared ${isShared ? '' : 'hidden'}"><i class="fas fa-share-alt"></i></div>
</div> </div>
<div class="type-cell">${typeLabel}</div> <div class="type-cell">${typeLabel}</div>
<div class="size-cell">${fileSize}</div> <div class="size-cell">${fileSize}</div>
<div class="date-cell">${formattedDate}</div> <div class="date-cell">${formattedDate}</div>
<div class="action-cell"> <div class="action-cell">
<button class="favorite-star${isFav ? ' active' : ''}" data-item-id="${file.id}" data-item-type="file" data-item-name="${escapeHtml(file.name)}"> <button class="favorite-star${isFav ? ' active' : ''}">
<i class="${isFav ? 'fas' : 'far'} fa-star"></i> <i class="${isFav ? 'fas' : 'far'} fa-star"></i>
</button> </button>
<button class="file-actions"><i class="fas fa-ellipsis-v"></i></button> <button class="file-actions"><i class="fas fa-ellipsis-v"></i></button>
@@ -1434,7 +1475,7 @@ function toggleCardSelection(card, event) {
function showContextMenuAtElement(triggerElement, menuId) { function showContextMenuAtElement(triggerElement, menuId) {
// Hide any open menus first // Hide any open menus first
document.querySelectorAll('.context-menu').forEach((m) => { document.querySelectorAll('.context-menu').forEach((m) => {
m.style.display = 'none'; m.classList.add('hidden');
}); });
const menu = document.getElementById(menuId); const menu = document.getElementById(menuId);
@@ -1465,7 +1506,7 @@ function showContextMenuAtElement(triggerElement, menuId) {
menu.style.left = `${left}px`; menu.style.left = `${left}px`;
menu.style.top = `${top}px`; menu.style.top = `${top}px`;
menu.style.display = 'block'; menu.classList.remove('hidden');
} }
/** /**
+25 -47
View File
@@ -7,56 +7,34 @@ import { notifications } from '../core/notifications.js';
const uiNotifications = { const uiNotifications = {
show(title, message) { show(title, message) {
if (notifications && typeof notifications.addNotification === 'function') { const normalizedTitle = String(title || '').toLowerCase();
const normalizedTitle = String(title || '').toLowerCase(); let icon = 'fa-info-circle';
let icon = 'fa-info-circle'; let iconClass = 'upload';
let iconClass = 'upload';
if (normalizedTitle.includes('error') || normalizedTitle.includes('failed') || normalizedTitle.includes('fail')) { if (normalizedTitle.includes('error') || normalizedTitle.includes('failed') || normalizedTitle.includes('fail')) {
icon = 'fa-exclamation-circle'; icon = 'fa-exclamation-circle';
iconClass = 'error'; iconClass = 'error';
} else if (normalizedTitle.includes('favorite') || normalizedTitle.includes('favorit') || normalizedTitle.includes('fav')) { } else if (normalizedTitle.includes('favorite') || normalizedTitle.includes('favorit') || normalizedTitle.includes('fav')) {
icon = 'fa-star'; icon = 'fa-star';
iconClass = 'success'; iconClass = 'success';
} else if ( } else if (
normalizedTitle.includes('delete') || normalizedTitle.includes('delete') ||
normalizedTitle.includes('removed') || normalizedTitle.includes('removed') ||
normalizedTitle.includes('trash') || normalizedTitle.includes('trash') ||
normalizedTitle.includes('rename') || normalizedTitle.includes('rename') ||
normalizedTitle.includes('complete') normalizedTitle.includes('complete')
) { ) {
icon = 'fa-check-circle'; icon = 'fa-check-circle';
iconClass = 'success'; iconClass = 'success';
}
notifications.addNotification({
icon,
iconClass,
title: title || '',
text: message || ''
});
return;
} }
let notification = document.querySelector('.notification'); notifications.addNotification({
if (!notification) { icon,
notification = document.createElement('div'); iconClass,
notification.className = 'notification'; title: title || '',
notification.innerHTML = ` text: message || ''
<div class="notification-title">${title}</div> });
<div class="notification-message">${message}</div> return;
`;
document.body.appendChild(notification);
} else {
notification.querySelector('.notification-title').textContent = title;
notification.querySelector('.notification-message').textContent = message;
}
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 5000);
} }
}; };
+6 -6
View File
@@ -129,7 +129,7 @@ function setupUserMenu() {
aboutBtn.addEventListener('click', () => { aboutBtn.addEventListener('click', () => {
wrapper.classList.remove('open'); wrapper.classList.remove('open');
const overlay = document.getElementById('about-modal-overlay'); const overlay = document.getElementById('about-modal-overlay');
if (overlay) overlay.classList.add('show'); if (overlay) overlay.classList.remove('hidden');
}); });
} }
@@ -137,18 +137,18 @@ function setupUserMenu() {
const aboutOverlay = document.getElementById('about-modal-overlay'); const aboutOverlay = document.getElementById('about-modal-overlay');
if (aboutCloseBtn) { if (aboutCloseBtn) {
aboutCloseBtn.addEventListener('click', () => { aboutCloseBtn.addEventListener('click', () => {
aboutOverlay.classList.remove('show'); aboutOverlay.classList.add('hidden');
}); });
} }
if (aboutOverlay) { if (aboutOverlay) {
aboutOverlay.addEventListener('click', (e) => { aboutOverlay.addEventListener('click', (e) => {
if (e.target === aboutOverlay) { if (e.target === aboutOverlay) {
aboutOverlay.classList.remove('show'); aboutOverlay.classList.add('hidden');
} }
}); });
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && aboutOverlay.classList.contains('show')) { if (e.key === 'Escape' && !aboutOverlay.classList.contains('hidden')) {
aboutOverlay.classList.remove('show'); aboutOverlay.classList.add('hidden');
} }
}); });
} }
@@ -218,7 +218,7 @@ function showUserProfileModal() {
const overlay = document.createElement('div'); const overlay = document.createElement('div');
overlay.id = 'profile-modal-overlay'; overlay.id = 'profile-modal-overlay';
overlay.className = 'about-modal-overlay'; overlay.classList.add('about-modal-overlay', 'hidden');
overlay.innerHTML = ` overlay.innerHTML = `
<div class="about-modal about-modal-body"> <div class="about-modal about-modal-body">
<div class="about-modal-header"> <div class="about-modal-header">
+11 -2
View File
@@ -8,14 +8,23 @@ import { replaceIconsInElement } from './icons.js';
const Modal = { const Modal = {
// Modal element references // Modal element references
/** @private @type {HTMLElement | null} */
overlay: null, overlay: null,
// FIXME: unused ?
container: null, container: null,
/** @private @type {HTMLElement | null} */
icon: null, icon: null,
/** @private @type {HTMLElement | null} */
title: null, title: null,
/** @private @type {HTMLElement | null} */
label: null, label: null,
/** @private @type {HTMLElement | null} */
input: null, input: null,
/** @private @type {HTMLElement | null} */
cancelBtn: null, cancelBtn: null,
/** @private @type {HTMLElement | null} */
confirmBtn: null, confirmBtn: null,
/** @private @type {HTMLElement | null} */
closeBtn: null, closeBtn: null,
// Current callback // Current callback
@@ -169,7 +178,7 @@ const Modal = {
if (!this.overlay) return; if (!this.overlay) return;
// Show overlay // Show overlay
this.overlay.style.display = 'flex'; this.overlay.classList.remove('hidden');
// Trigger animation // Trigger animation
requestAnimationFrame(() => { requestAnimationFrame(() => {
@@ -206,7 +215,7 @@ const Modal = {
this.overlay.classList.remove('active'); this.overlay.classList.remove('active');
setTimeout(() => { setTimeout(() => {
this.overlay.style.display = 'none'; this.overlay.classList.add('hidden');
if (!confirmed && this.onCancel) { if (!confirmed && this.onCancel) {
this.onCancel(); this.onCancel();
+17 -13
View File
@@ -294,7 +294,7 @@ const contextMenus = {
// Each handler checks its own state, so multiple dialogs can be closed with multiple Escape presses // Each handler checks its own state, so multiple dialogs can be closed with multiple Escape presses
if (!_moveDialogEscapeHandler) { if (!_moveDialogEscapeHandler) {
_moveDialogEscapeHandler = (e) => { _moveDialogEscapeHandler = (e) => {
if (e.key === 'Escape' && moveFileDialog.style.display === 'flex') { if (e.key === 'Escape' && !moveFileDialog?.classList.contains('hidden')) {
this.closeMoveDialog(); this.closeMoveDialog();
} }
}; };
@@ -383,7 +383,7 @@ const contextMenus = {
// Update header text // Update header text
const headerSpan = renameDialog.querySelector('.rename-dialog-header span'); const headerSpan = renameDialog.querySelector('.rename-dialog-header span');
if (headerSpan) headerSpan.textContent = i18n ? i18n.t('dialogs.rename_folder') : 'Rename folder'; if (headerSpan) headerSpan.textContent = i18n ? i18n.t('dialogs.rename_folder') : 'Rename folder';
renameDialog.style.display = 'flex'; renameDialog?.classList.remove('hidden');
renameInput.focus(); renameInput.focus();
renameInput.select(); renameInput.select();
}, },
@@ -403,7 +403,7 @@ const contextMenus = {
// Update header text // Update header text
const headerSpan = renameDialog.querySelector('.rename-dialog-header span'); const headerSpan = renameDialog.querySelector('.rename-dialog-header span');
if (headerSpan) headerSpan.textContent = i18n ? i18n.t('dialogs.rename_file') : 'Rename file'; if (headerSpan) headerSpan.textContent = i18n ? i18n.t('dialogs.rename_file') : 'Rename file';
renameDialog.style.display = 'flex'; renameDialog?.classList.remove('hidden');
renameInput.focus(); renameInput.focus();
renameInput.select(); renameInput.select();
}, },
@@ -412,7 +412,7 @@ const contextMenus = {
* Close rename dialog * Close rename dialog
*/ */
closeRenameDialog() { closeRenameDialog() {
document.getElementById('rename-dialog').style.display = 'none'; document.getElementById('rename-dialog')?.classList.add('hidden');
app.contextMenuTargetFolder = null; app.contextMenuTargetFolder = null;
app.renameTarget = null; app.renameTarget = null;
}, },
@@ -478,14 +478,14 @@ const contextMenus = {
await this.loadMoveDialogFolders(startFolderId); await this.loadMoveDialogFolders(startFolderId);
// Show dialog // Show dialog
document.getElementById('move-file-dialog').style.display = 'flex'; document.getElementById('move-file-dialog')?.classList.remove('hidden');
}, },
/** /**
* Close move dialog * Close move dialog
*/ */
closeMoveDialog() { closeMoveDialog() {
document.getElementById('move-file-dialog').style.display = 'none'; document.getElementById('move-file-dialog')?.classList.add('hidden');
app.contextMenuTargetFile = null; app.contextMenuTargetFile = null;
app.contextMenuTargetFolder = null; app.contextMenuTargetFolder = null;
}, },
@@ -909,6 +909,7 @@ const contextMenus = {
btn.closest('.existing-share-item').remove(); btn.closest('.existing-share-item').remove();
if (existingSharesContainer.children.length === 0) { if (existingSharesContainer.children.length === 0) {
document.getElementById('existing-shares-section').classList.add('hidden'); document.getElementById('existing-shares-section').classList.add('hidden');
ui.setSharedVisualState(item.id, item.type, false);
} }
} }
}); });
@@ -923,7 +924,7 @@ const contextMenus = {
if (newShareSection) newShareSection.classList.add('hidden'); if (newShareSection) newShareSection.classList.add('hidden');
// Show dialog // Show dialog
shareDialog.style.display = 'flex'; shareDialog.classList.remove('hidden');
console.log('Share dialog opened for', itemType, item.name); console.log('Share dialog opened for', itemType, item.name);
} catch (error) { } catch (error) {
console.error('Error opening share dialog:', error); console.error('Error opening share dialog:', error);
@@ -992,6 +993,9 @@ const contextMenus = {
shareUrl.select(); shareUrl.select();
} }
// Update Item's shared badge
ui.setSharedVisualState(item.id, item.type, true);
// Show success message // Show success message
ui.showNotification( ui.showNotification(
i18n ? i18n.t('notifications.link_created') : 'Link created', i18n ? i18n.t('notifications.link_created') : 'Link created',
@@ -1017,7 +1021,7 @@ const contextMenus = {
app.notificationShareUrl = shareUrl; app.notificationShareUrl = shareUrl;
// Show dialog // Show dialog
document.getElementById('notification-dialog').style.display = 'flex'; document.getElementById('notification-dialog')?.classList.remove('hidden');
}, },
/** /**
@@ -1042,7 +1046,7 @@ const contextMenus = {
try { try {
fileSharing.sendShareNotification(shareUrl, email, message); fileSharing.sendShareNotification(shareUrl, email, message);
document.getElementById('notification-dialog').style.display = 'none'; document.getElementById('notification-dialog')?.classList.add('hidden');
} catch (error) { } catch (error) {
console.error('Error sending notification:', error); console.error('Error sending notification:', error);
ui.showNotification('Error', 'Could not send notification'); ui.showNotification('Error', 'Could not send notification');
@@ -1054,7 +1058,7 @@ const contextMenus = {
*/ */
closeShareDialog() { closeShareDialog() {
const dialog = document.getElementById('share-dialog'); const dialog = document.getElementById('share-dialog');
if (dialog) dialog.style.display = 'none'; if (dialog) dialog.classList.add('hidden');
app.shareDialogItem = null; app.shareDialogItem = null;
app.shareDialogItemType = null; app.shareDialogItemType = null;
}, },
@@ -1063,7 +1067,7 @@ const contextMenus = {
* Close notification dialog * Close notification dialog
*/ */
closeNotificationDialog() { closeNotificationDialog() {
document.getElementById('notification-dialog').style.display = 'none'; document.getElementById('notification-dialog')?.classList.add('hidden');
app.notificationShareUrl = null; app.notificationShareUrl = null;
}, },
@@ -1096,7 +1100,7 @@ const contextMenus = {
if (addBtn) addBtn.disabled = true; if (addBtn) addBtn.disabled = true;
// Show dialog // Show dialog
dialog.style.display = 'flex'; dialog.classList.remove('hidden');
requestAnimationFrame(() => dialog.classList.add('active')); requestAnimationFrame(() => dialog.classList.add('active'));
// Load playlists // Load playlists
@@ -1195,7 +1199,7 @@ const contextMenus = {
if (dialog) { if (dialog) {
dialog.classList.remove('active'); dialog.classList.remove('active');
setTimeout(() => { setTimeout(() => {
dialog.style.display = 'none'; dialog.classList.add('hidden');
}, 200); }, 200);
} }
app.playlistDialogFiles = null; app.playlistDialogFiles = null;
+8 -8
View File
@@ -289,7 +289,7 @@ const fileOps = {
const progressBar = document.querySelector('.progress-fill'); const progressBar = document.querySelector('.progress-fill');
const uploadProgressDiv = document.querySelector('.upload-progress'); const uploadProgressDiv = document.querySelector('.upload-progress');
if (uploadProgressDiv) { if (uploadProgressDiv) {
uploadProgressDiv.style.display = 'block'; uploadProgressDiv.classList.remove('hidden');
} }
if (progressBar) { if (progressBar) {
progressBar.style.width = '0%'; progressBar.style.width = '0%';
@@ -322,7 +322,7 @@ const fileOps = {
} }
if (totalFiles === 0) { if (totalFiles === 0) {
if (uploadProgressDiv) uploadProgressDiv.style.display = 'none'; if (uploadProgressDiv) uploadProgressDiv.classList.add('hidden');
this._isUploading = false; this._isUploading = false;
return; return;
} }
@@ -412,8 +412,8 @@ const fileOps = {
} }
const dropzone = document.getElementById('dropzone'); const dropzone = document.getElementById('dropzone');
if (dropzone) dropzone.style.display = 'none'; if (dropzone) dropzone.classList.add('hidden');
if (uploadProgressDiv) uploadProgressDiv.style.display = 'none'; if (uploadProgressDiv) uploadProgressDiv.classList.add('hidden');
} finally { } finally {
this._isUploading = false; this._isUploading = false;
} }
@@ -450,7 +450,7 @@ const fileOps = {
const progressBar = document.querySelector('.progress-fill'); const progressBar = document.querySelector('.progress-fill');
const uploadProgressDiv = document.querySelector('.upload-progress'); const uploadProgressDiv = document.querySelector('.upload-progress');
if (uploadProgressDiv) { if (uploadProgressDiv) {
uploadProgressDiv.style.display = 'block'; uploadProgressDiv.classList.remove('hidden');
} }
if (progressBar) { if (progressBar) {
progressBar.style.width = '0%'; progressBar.style.width = '0%';
@@ -468,7 +468,7 @@ const fileOps = {
const totalFiles = validEntries.length; const totalFiles = validEntries.length;
if (totalFiles === 0) { if (totalFiles === 0) {
if (uploadProgressDiv) uploadProgressDiv.style.display = 'none'; if (uploadProgressDiv) uploadProgressDiv.classList.add('hidden');
return; return;
} }
@@ -678,8 +678,8 @@ const fileOps = {
} }
const dropzone = document.getElementById('dropzone'); const dropzone = document.getElementById('dropzone');
if (dropzone) dropzone.style.display = 'none'; if (dropzone) dropzone.classList.add('hidden');
if (uploadProgressDiv) uploadProgressDiv.style.display = 'none'; if (uploadProgressDiv) uploadProgressDiv.classList.add('hidden');
} finally { } finally {
this._isUploading = false; this._isUploading = false;
} }
+12 -7
View File
@@ -157,7 +157,7 @@ const sharedView = {
</div> </div>
<!-- Share Edit Dialog (sharedView-specific) --> <!-- Share Edit Dialog (sharedView-specific) -->
<div id="shared-view-edit-dialog" class="shared-dialog"> <div id="shared-view-edit-dialog" class="shared-dialog hidden">
<div class="shared-dialog-content"> <div class="shared-dialog-content">
<div class="shared-dialog-header"> <div class="shared-dialog-header">
<span id="sv-dialog-icon">📄</span> <span id="sv-dialog-icon">📄</span>
@@ -196,7 +196,7 @@ const sharedView = {
</div> </div>
<!-- Notification Dialog (sharedView-specific) --> <!-- Notification Dialog (sharedView-specific) -->
<div id="sv-notification-dialog" class="shared-dialog"> <div id="sv-notification-dialog" class="shared-dialog hidden">
<div class="shared-dialog-content"> <div class="shared-dialog-content">
<div class="shared-dialog-header"> <div class="shared-dialog-header">
<span id="sv-notify-dialog-icon">📧</span> <span id="sv-notify-dialog-icon">📧</span>
@@ -493,12 +493,12 @@ const sharedView = {
} }
} }
shareDialog.classList.add('active'); shareDialog.classList.remove('hidden');
}, },
closeShareDialog() { closeShareDialog() {
const d = document.getElementById('shared-view-edit-dialog'); const d = document.getElementById('shared-view-edit-dialog');
if (d) d.classList.remove('active'); if (d) d.classList.add('hidden');
this.currentItem = null; this.currentItem = null;
}, },
@@ -516,12 +516,12 @@ const sharedView = {
if (nameEl) nameEl.textContent = dn; if (nameEl) nameEl.textContent = dn;
if (emailEl) emailEl.value = ''; if (emailEl) emailEl.value = '';
if (msgEl) msgEl.value = ''; if (msgEl) msgEl.value = '';
d.classList.add('active'); d.classList.remove('hidden');
}, },
closeNotificationDialog() { closeNotificationDialog() {
const d = document.getElementById('sv-notification-dialog'); const d = document.getElementById('sv-notification-dialog');
if (d) d.classList.remove('active'); if (d) d.classList.add('hidden');
this.currentItem = null; this.currentItem = null;
}, },
@@ -575,6 +575,7 @@ const sharedView = {
}; };
try { try {
// FIXME: redundance with fileSharing
const res = await fetch(`/api/shares/${this.currentItem.id}`, { const res = await fetch(`/api/shares/${this.currentItem.id}`, {
method: 'PUT', method: 'PUT',
headers: this._headers(true), headers: this._headers(true),
@@ -589,7 +590,8 @@ const sharedView = {
console.error('Error updating share:', err); console.error('Error updating share:', err);
this.showNotification(err.message || 'Error updating share', 'error'); this.showNotification(err.message || 'Error updating share', 'error');
} }
// update UI
ui.setSharedVisualState(this.currentItem.item_id, this.currentItem.item_type, true);
this.closeShareDialog(); this.closeShareDialog();
await this.loadItems(true); await this.loadItems(true);
this.filterAndSortItems(); this.filterAndSortItems();
@@ -600,6 +602,7 @@ const sharedView = {
if (!this.currentItem) return; if (!this.currentItem) return;
try { try {
// FIXME: redundance with fileSharing
const res = await fetch(`/api/shares/${this.currentItem.id}`, { const res = await fetch(`/api/shares/${this.currentItem.id}`, {
method: 'DELETE', method: 'DELETE',
headers: this._headers() headers: this._headers()
@@ -614,6 +617,8 @@ const sharedView = {
this.closeShareDialog(); this.closeShareDialog();
await this.loadItems(true); await this.loadItems(true);
this.filterAndSortItems(); this.filterAndSortItems();
// update UI
ui.setSharedVisualState(this.currentItem.item_id, this.currentItem.item_type, this.isShared(this.currentItem.item_id, this.currentItem.item_type));
}, },
// Send notification (stub) // Send notification (stub)