style: apply biome CSS/JS format
This commit is contained in:
@@ -25,7 +25,6 @@ async function refreshUserData() {
|
||||
|
||||
localStorage.setItem(USER_DATA_KEY, JSON.stringify(userData));
|
||||
window.updateStorageUsageDisplay(userData);
|
||||
|
||||
return userData;
|
||||
} catch (error) {
|
||||
console.error('Error refreshing user data:', error);
|
||||
@@ -82,7 +81,7 @@ async function checkAuthentication() {
|
||||
if (userData.username) {
|
||||
// We have cached user data — render immediately, refresh in background
|
||||
const userInitials = userData.username.substring(0, 2).toUpperCase();
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach(el => {
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach((el) => {
|
||||
el.textContent = userInitials;
|
||||
});
|
||||
const menuName = document.getElementById('user-menu-name');
|
||||
@@ -103,7 +102,10 @@ async function checkAuthentication() {
|
||||
const r = await fetch('/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', ...getCsrfHeaders() },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...getCsrfHeaders()
|
||||
},
|
||||
body: '{}'
|
||||
});
|
||||
if (r.ok) {
|
||||
@@ -128,7 +130,7 @@ async function checkAuthentication() {
|
||||
const freshData = await refreshUserData();
|
||||
if (freshData && freshData.username) {
|
||||
const userInitials = freshData.username.substring(0, 2).toUpperCase();
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach(el => el.textContent = userInitials);
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach((el) => (el.textContent = userInitials));
|
||||
window.updateStorageUsageDisplay(freshData);
|
||||
resolveHomeFolder().then(() => window.loadFiles());
|
||||
} else {
|
||||
@@ -154,7 +156,9 @@ async function resolveHomeFolder() {
|
||||
|
||||
if (app.userHomeFolderId) return;
|
||||
try {
|
||||
const response = await fetch('/api/folders', { credentials: 'same-origin' });
|
||||
const response = await fetch('/api/folders', {
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.warn(`Could not fetch home folder: ${response.status}`);
|
||||
return;
|
||||
|
||||
+31
-30
@@ -21,12 +21,11 @@
|
||||
* @param {string} id the id of the folder
|
||||
* @returns {Promise<FolderInfo>}
|
||||
*/
|
||||
async function getFolder( id) {
|
||||
|
||||
async function getFolder(id) {
|
||||
/** @type {HeadersInit} */
|
||||
const headers = {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache'
|
||||
Pragma: 'no-cache'
|
||||
};
|
||||
|
||||
/** @type {RequestInit} */
|
||||
@@ -35,12 +34,11 @@ async function getFolder( id) {
|
||||
credentials: 'same-origin',
|
||||
cache: 'no-store'
|
||||
};
|
||||
|
||||
let folderInformations = await fetch( `/api/folders/${id}`, requestOptions);
|
||||
|
||||
let folderInformations = await fetch(`/api/folders/${id}`, requestOptions);
|
||||
if (folderInformations.ok) {
|
||||
return folderInformations.json()
|
||||
}
|
||||
else {
|
||||
return folderInformations.json();
|
||||
} else {
|
||||
console.warn(`Error fetching folder ${id}`);
|
||||
return Promise.reject(null);
|
||||
}
|
||||
@@ -50,15 +48,15 @@ async function getFolder( id) {
|
||||
* rebuild breadcrumb from selected folder (iterate up to root)
|
||||
*/
|
||||
async function rebuildBreadCrumb() {
|
||||
|
||||
const app = window.app;
|
||||
/**
|
||||
|
||||
/**
|
||||
* Store the leaf (this is the current displayed folder)
|
||||
* @type {FolderInfo | null}
|
||||
* @type {FolderInfo | null}
|
||||
*/
|
||||
let currentFolderInfo = null;
|
||||
|
||||
// rebuild full breadcrumb,
|
||||
// rebuild full breadcrumb,
|
||||
// TODO: to optimize, data may already be known / or ETAG could be interesting to reduce load
|
||||
app.breadcrumbPath = [];
|
||||
|
||||
@@ -77,61 +75,64 @@ async function rebuildBreadCrumb() {
|
||||
}
|
||||
|
||||
// XXX do not enter root into bread crumb updateBreadcrumb() method always display it
|
||||
if(!folderInfo.is_root) {
|
||||
app.breadcrumbPath.unshift( {id: folderInfo.id, name: folderInfo.name});
|
||||
if (!folderInfo.is_root) {
|
||||
app.breadcrumbPath.unshift({
|
||||
id: folderInfo.id,
|
||||
name: folderInfo.name
|
||||
});
|
||||
}
|
||||
|
||||
// iterate to parent folder
|
||||
id = folderInfo.parent_id;
|
||||
}
|
||||
catch( e) {
|
||||
} catch (e) {
|
||||
console.log(`Error loading information from folder ${app.currentPath}, falling back to ${app.userHomeFolderId}`);
|
||||
// fallback of root
|
||||
window.uiNotifications.show(
|
||||
'error: folder not found or permission denied',
|
||||
'the given folder is not available or you do not have sufficient rights'
|
||||
'error: folder not found or permission denied',
|
||||
'the given folder is not available or you do not have sufficient rights'
|
||||
);
|
||||
app.breadcrumbPath = [];
|
||||
id = app.userHomeFolderId;
|
||||
app.currentPath = id;
|
||||
}
|
||||
}
|
||||
// store informations on the current folder
|
||||
|
||||
// store informations on the current folder
|
||||
app.currentFolderInfo = currentFolderInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Files view loading logic
|
||||
*
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {boolean} [options.insertHistory] add browser history (default true)
|
||||
* @param {boolean} [options.forceRefresh] force refresh of content
|
||||
*/
|
||||
async function loadFiles(options = { insertHistory: true}) {
|
||||
async function loadFiles(options = { insertHistory: true }) {
|
||||
const app = window.app;
|
||||
const elements = window.appElements;
|
||||
|
||||
try {
|
||||
console.log("Starting loadFiles() - loading files...", options);
|
||||
console.log('Starting loadFiles() - loading files...', options);
|
||||
|
||||
const forceRefresh = options.forceRefresh || false;
|
||||
|
||||
if (window.isLoadingFiles) {
|
||||
console.log("A file load is already in progress, ignoring request");
|
||||
console.log('A file load is already in progress, ignoring request');
|
||||
return;
|
||||
}
|
||||
|
||||
window.isLoadingFiles = true;
|
||||
|
||||
// This to avoid blinking page, a better solution would be to put loading on an overlay and remove timeout
|
||||
let loadingFiles = setTimeout( () => {
|
||||
let loadingFiles = setTimeout(() => {
|
||||
// display loader after few delay (will be canceled if result take less time)
|
||||
window.ui.showError(`
|
||||
<div class="files-loading-spinner">
|
||||
<div class="spinner"></div>
|
||||
<span>${window.i18n ? window.i18n.t('files.loading') : 'Loading files…'}</span>
|
||||
</div>
|
||||
`)
|
||||
`);
|
||||
}, 100);
|
||||
|
||||
if (!app.userHomeFolderId) {
|
||||
@@ -145,7 +146,7 @@ async function loadFiles(options = { insertHistory: true}) {
|
||||
// request a breadcrumb paint
|
||||
window.ui.updateBreadcrumb();
|
||||
|
||||
window.updateHistory( options.insertHistory || false);
|
||||
window.updateHistory(options.insertHistory || false);
|
||||
|
||||
let url;
|
||||
|
||||
@@ -158,7 +159,7 @@ async function loadFiles(options = { insertHistory: true}) {
|
||||
console.log(`Loading user folder: ${app.userHomeFolderName} (${app.userHomeFolderId})`);
|
||||
} else {
|
||||
url = `/api/folders?t=${timestamp}`;
|
||||
console.warn("Emergency fallback to root folder - this should not normally happen");
|
||||
console.warn('Emergency fallback to root folder - this should not normally happen');
|
||||
}
|
||||
} else {
|
||||
url = `/api/folders/${app.currentPath}/listing?t=${timestamp}`;
|
||||
@@ -168,7 +169,7 @@ async function loadFiles(options = { insertHistory: true}) {
|
||||
/** @type {HeadersInit} */
|
||||
const headers = {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache'
|
||||
Pragma: 'no-cache'
|
||||
};
|
||||
|
||||
/** @type {RequestInit} */
|
||||
@@ -189,10 +190,10 @@ async function loadFiles(options = { insertHistory: true}) {
|
||||
const response = await fetch(url, requestOptions);
|
||||
|
||||
// not required anymore
|
||||
clearTimeout( loadingFiles);
|
||||
clearTimeout(loadingFiles);
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
console.warn("Auth error when loading files, showing empty list");
|
||||
console.warn('Auth error when loading files, showing empty list');
|
||||
// FIXME: i18n
|
||||
window.ui.showError(`<p>Could not load files</p>`);
|
||||
return;
|
||||
|
||||
+47
-49
@@ -103,9 +103,9 @@ function setActionsBarMode(mode, force = false) {
|
||||
if (!elements.actionsBar) return;
|
||||
|
||||
if (mode === 'hidden') {
|
||||
elements.actionsBar.classList.add("hidden");
|
||||
elements.actionsBar.classList.add('hidden');
|
||||
elements.actionsBar.dataset.mode = 'hidden';
|
||||
console.log("......setup actions bar to hidden");
|
||||
console.log('......setup actions bar to hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ function setActionsBarMode(mode, force = false) {
|
||||
if (!html) return;
|
||||
|
||||
elements.actionsBar.innerHTML = html;
|
||||
elements.actionsBar.classList.remove("hidden");
|
||||
elements.actionsBar.classList.remove('hidden');
|
||||
elements.actionsBar.dataset.mode = mode;
|
||||
|
||||
// Refresh cached action elements after rebuild
|
||||
@@ -220,7 +220,7 @@ function deserializeHash() {
|
||||
// FIXME rename files into drive ?
|
||||
hashContext.section = 'files';
|
||||
|
||||
const hash_elements = window.location.hash.split("/");
|
||||
const hash_elements = window.location.hash.split('/');
|
||||
|
||||
const section = hash_elements[1];
|
||||
|
||||
@@ -245,13 +245,13 @@ function deserializeHash() {
|
||||
*
|
||||
* @param {boolean} insertHistory true to change url and browser's history, false to change url only
|
||||
*/
|
||||
function updateHistory( insertHistory) {
|
||||
function updateHistory(insertHistory) {
|
||||
const app = window.app;
|
||||
|
||||
let historyData = {
|
||||
section: app.currentSection,
|
||||
id: app.currentFolder,
|
||||
file: app.viewFile,
|
||||
file: app.viewFile
|
||||
};
|
||||
|
||||
let historyUrl = `#/${app.currentSection}`;
|
||||
@@ -268,12 +268,11 @@ function updateHistory( insertHistory) {
|
||||
}
|
||||
|
||||
if (insertHistory) {
|
||||
console.log(`adding history with ${historyUrl}`)
|
||||
window.history.pushState(historyData, "", historyUrl);
|
||||
}
|
||||
else {
|
||||
console.log(`replace history with ${historyUrl}`)
|
||||
window.history.replaceState(historyData, "", historyUrl);
|
||||
console.log(`adding history with ${historyUrl}`);
|
||||
window.history.pushState(historyData, '', historyUrl);
|
||||
} else {
|
||||
console.log(`replace history with ${historyUrl}`);
|
||||
window.history.replaceState(historyData, '', historyUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,33 +282,32 @@ function updateHistory( insertHistory) {
|
||||
* @returns
|
||||
*/
|
||||
function switchSectionTo(section) {
|
||||
|
||||
if (window.app.currentSection === section)
|
||||
// no change ...
|
||||
return;
|
||||
|
||||
switch (section) {
|
||||
case "files":
|
||||
case 'files':
|
||||
switchToFilesSection();
|
||||
break;
|
||||
|
||||
case "shared":
|
||||
case 'shared':
|
||||
switchToSharedSection();
|
||||
break;
|
||||
|
||||
case "recent":
|
||||
case 'recent':
|
||||
switchToRecentFilesSection();
|
||||
break;
|
||||
|
||||
case "favorites":
|
||||
case 'favorites':
|
||||
switchToFavoritesSection();
|
||||
break;
|
||||
|
||||
case "photos":
|
||||
case 'photos':
|
||||
switchToPhotosSection();
|
||||
break;
|
||||
|
||||
case "trash":
|
||||
case 'trash':
|
||||
switchToTrashSection();
|
||||
break;
|
||||
|
||||
@@ -375,8 +373,8 @@ function initApp() {
|
||||
window.addEventListener('authenticationDone', () => {
|
||||
// Check if a context was provided in the URL
|
||||
let hashContext = deserializeHash();
|
||||
switchSectionTo( hashContext.section);
|
||||
if (hashContext.section === "files") {
|
||||
switchSectionTo(hashContext.section);
|
||||
if (hashContext.section === 'files') {
|
||||
if (hashContext.path) {
|
||||
console.log(`init: reusing folder from hash URL: ${hashContext.path}`);
|
||||
window.app.currentPath = hashContext.path;
|
||||
@@ -387,7 +385,6 @@ function initApp() {
|
||||
}
|
||||
window.loadFiles();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Wait for translations to load before checking authentication
|
||||
@@ -448,15 +445,19 @@ function setupUploadDropdown() {
|
||||
const signal = uploadDropdownBindingsController.signal;
|
||||
|
||||
// Toggle dropdown on button click
|
||||
uploadBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const isOpen = menu.classList.contains('show');
|
||||
// Close any other open dropdowns
|
||||
document.querySelectorAll('.upload-dropdown-menu.show').forEach(m => m.classList.remove('show'));
|
||||
if (!isOpen) {
|
||||
menu.classList.add('show');
|
||||
}
|
||||
}, { signal });
|
||||
uploadBtn.addEventListener(
|
||||
'click',
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
const isOpen = menu.classList.contains('show');
|
||||
// Close any other open dropdowns
|
||||
document.querySelectorAll('.upload-dropdown-menu.show').forEach((m) => m.classList.remove('show'));
|
||||
if (!isOpen) {
|
||||
menu.classList.add('show');
|
||||
}
|
||||
},
|
||||
{ signal }
|
||||
);
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
// remove+add stable handler: guarantees exactly one global listener
|
||||
@@ -466,7 +467,7 @@ function setupUploadDropdown() {
|
||||
}
|
||||
uploadDropdownDocumentClickHandler = (e) => {
|
||||
if (e.target.closest('#upload-dropdown')) return;
|
||||
document.querySelectorAll('.upload-dropdown-menu.show').forEach(m => m.classList.remove('show'));
|
||||
document.querySelectorAll('.upload-dropdown-menu.show').forEach((m) => m.classList.remove('show'));
|
||||
};
|
||||
// @ts-ignore
|
||||
document.addEventListener('click', uploadDropdownDocumentClickHandler);
|
||||
@@ -485,21 +486,20 @@ function setupEventListeners() {
|
||||
const SEARCH_MIN_CHARS = 3;
|
||||
|
||||
// handle history / url change
|
||||
window.addEventListener("popstate", (e) => {
|
||||
window.addEventListener('popstate', (e) => {
|
||||
if (e.state === null) {
|
||||
// change is from user (url explicitely change, read information from hash)
|
||||
let hashContext = deserializeHash();
|
||||
switchSectionTo( hashContext.section);
|
||||
const hashContext = deserializeHash();
|
||||
switchSectionTo(hashContext.section);
|
||||
if (hashContext.path) {
|
||||
window.app.currentPath = hashContext.path;
|
||||
window.loadFiles({insertHistory: false});
|
||||
window.loadFiles({ insertHistory: false });
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// change is from history, data provided in event
|
||||
switchSectionTo( e.state.section);
|
||||
switchSectionTo(e.state.section);
|
||||
window.app.currentPath = e.state.id;
|
||||
window.loadFiles({insertHistory: false});
|
||||
window.loadFiles({ insertHistory: false });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -584,17 +584,17 @@ function setupEventListeners() {
|
||||
}
|
||||
|
||||
// Sidebar navigation
|
||||
elements.navItems.forEach(item => {
|
||||
elements.navItems.forEach((item) => {
|
||||
item.addEventListener('click', () => {
|
||||
// Remove active class from all nav items
|
||||
elements.navItems.forEach(navItem => navItem.classList.remove('active'));
|
||||
elements.navItems.forEach((navItem) => navItem.classList.remove('active'));
|
||||
|
||||
// Add active class to clicked item
|
||||
item.classList.add('active');
|
||||
let _updateHistory = true;
|
||||
|
||||
let itemI18nKey=item.querySelector('span').getAttribute('data-i18n')
|
||||
switch(itemI18nKey) {
|
||||
let itemI18nKey = item.querySelector('span').getAttribute('data-i18n');
|
||||
switch (itemI18nKey) {
|
||||
case 'nav.shared':
|
||||
// Switch to shared view
|
||||
switchToSharedSection();
|
||||
@@ -628,7 +628,7 @@ function setupEventListeners() {
|
||||
document.title = `OxiCloud: ${window.i18n.t(itemI18nKey)}`;
|
||||
|
||||
if (_updateHistory) {
|
||||
updateHistory( true);
|
||||
updateHistory(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -649,13 +649,11 @@ function setupEventListeners() {
|
||||
const folderMenu = document.getElementById('folder-context-menu');
|
||||
const fileMenu = document.getElementById('file-context-menu');
|
||||
|
||||
if (folderMenu && folderMenu.style.display === 'block' &&
|
||||
!folderMenu.contains(e.target)) {
|
||||
if (folderMenu && folderMenu.style.display === 'block' && !folderMenu.contains(e.target)) {
|
||||
ui.closeContextMenu();
|
||||
}
|
||||
|
||||
if (fileMenu && fileMenu.style.display === 'block' &&
|
||||
!fileMenu.contains(e.target)) {
|
||||
if (fileMenu && fileMenu.style.display === 'block' && !fileMenu.contains(e.target)) {
|
||||
ui.closeFileContextMenu();
|
||||
}
|
||||
});
|
||||
|
||||
+70
-78
@@ -14,15 +14,14 @@ function syncViewContainers() {
|
||||
|
||||
const isGrid = window.app.currentView === 'grid';
|
||||
if (isGrid) {
|
||||
filesList.classList.remove("files-list-view");
|
||||
filesList.classList.add("files-grid-view");
|
||||
filesList.classList.remove('files-list-view');
|
||||
filesList.classList.add('files-grid-view');
|
||||
|
||||
gridViewBtn?.classList.add('active');
|
||||
listViewBtn?.classList.remove('active');
|
||||
}
|
||||
else {
|
||||
filesList.classList.add("files-list-view");
|
||||
filesList.classList.remove("files-grid-view");
|
||||
} else {
|
||||
filesList.classList.add('files-list-view');
|
||||
filesList.classList.remove('files-grid-view');
|
||||
|
||||
gridViewBtn?.classList.remove('active');
|
||||
listViewBtn?.classList.add('active');
|
||||
@@ -42,61 +41,61 @@ function toggleFileContainer(show) {
|
||||
* Mobile sidebar toggle functionality
|
||||
*/
|
||||
function initSidebarToggle() {
|
||||
const sidebarToggle = document.getElementById('sidebar-toggle');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
const sidebarToggle = document.getElementById('sidebar-toggle');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
|
||||
if (!sidebarToggle || !sidebar || !sidebarOverlay) return;
|
||||
if (!sidebarToggle || !sidebar || !sidebarOverlay) return;
|
||||
|
||||
function openSidebar() {
|
||||
sidebar.classList.add('open');
|
||||
sidebarOverlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function openSidebar() {
|
||||
sidebar.classList.add('open');
|
||||
sidebarOverlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
sidebar.classList.remove('open');
|
||||
sidebarOverlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
function closeSidebar() {
|
||||
sidebar.classList.remove('open');
|
||||
sidebarOverlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
if (sidebar.classList.contains('open')) {
|
||||
closeSidebar();
|
||||
} else {
|
||||
openSidebar();
|
||||
}
|
||||
}
|
||||
function toggleSidebar() {
|
||||
if (sidebar.classList.contains('open')) {
|
||||
closeSidebar();
|
||||
} else {
|
||||
openSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle button click
|
||||
sidebarToggle.addEventListener('click', toggleSidebar);
|
||||
// Toggle button click
|
||||
sidebarToggle.addEventListener('click', toggleSidebar);
|
||||
|
||||
// Close sidebar when clicking overlay
|
||||
sidebarOverlay.addEventListener('click', closeSidebar);
|
||||
// Close sidebar when clicking overlay
|
||||
sidebarOverlay.addEventListener('click', closeSidebar);
|
||||
|
||||
// Close sidebar on escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && sidebar.classList.contains('open')) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
// Close sidebar on escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && sidebar.classList.contains('open')) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// Close sidebar when navigating (nav item click on mobile)
|
||||
const navItems = sidebar.querySelectorAll('.nav-item');
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
if (window.innerWidth <= 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
// Close sidebar when navigating (nav item click on mobile)
|
||||
const navItems = sidebar.querySelectorAll('.nav-item');
|
||||
navItems.forEach((item) => {
|
||||
item.addEventListener('click', () => {
|
||||
if (window.innerWidth <= 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Expose functions globally
|
||||
window.sidebarToggle = {
|
||||
open: openSidebar,
|
||||
close: closeSidebar,
|
||||
toggle: toggleSidebar
|
||||
};
|
||||
// Expose functions globally
|
||||
window.sidebarToggle = {
|
||||
open: openSidebar,
|
||||
close: closeSidebar,
|
||||
toggle: toggleSidebar
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize sidebar toggle when DOM is ready
|
||||
@@ -104,12 +103,12 @@ document.addEventListener('DOMContentLoaded', initSidebarToggle);
|
||||
|
||||
// Mapping of section names to their corresponding view flags
|
||||
const VIEW_FLAGS = {
|
||||
'files': 'isFilesView',
|
||||
'shared': 'isSharedView',
|
||||
'recent': 'isRecentView',
|
||||
'favorites': 'isFavoritesView',
|
||||
'trash': 'isTrashView',
|
||||
'photos': 'isPhotosView'
|
||||
files: 'isFilesView',
|
||||
shared: 'isSharedView',
|
||||
recent: 'isRecentView',
|
||||
favorites: 'isFavoritesView',
|
||||
trash: 'isTrashView',
|
||||
photos: 'isPhotosView'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -128,18 +127,17 @@ function getSectionFromNavItem(navItem) {
|
||||
* @returns {boolean} true if the section changed
|
||||
*/
|
||||
function setCurrentSection(section) {
|
||||
|
||||
if (window.app.currentSection == section) return false;
|
||||
|
||||
// Set all view flags - true for active section, false for others
|
||||
Object.entries(VIEW_FLAGS).forEach(([key, flag]) => {
|
||||
window.app[flag] = (key === section);
|
||||
window.app[flag] = key === section;
|
||||
});
|
||||
|
||||
window.app.currentSection = section;
|
||||
|
||||
// Update nav item active classes by finding matching item from DOM
|
||||
window.appElements.navItems.forEach(item => {
|
||||
window.appElements.navItems.forEach((item) => {
|
||||
const itemSection = getSectionFromNavItem(item);
|
||||
item.classList.toggle('active', itemSection === section);
|
||||
});
|
||||
@@ -164,12 +162,11 @@ function setCurrentSection(section) {
|
||||
}
|
||||
|
||||
function switchToSharedSection() {
|
||||
|
||||
if (!setCurrentSection('shared')) return;
|
||||
|
||||
// Hide breadcrumb (only shown in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
breadcrumb?.classList.add("hidden");
|
||||
breadcrumb?.classList.add('hidden');
|
||||
|
||||
// Hide actions-bar for shared view
|
||||
window.setActionsBarMode('hidden');
|
||||
@@ -178,14 +175,13 @@ function switchToSharedSection() {
|
||||
|
||||
//reset files view + remove any error
|
||||
window.ui.resetFilesList();
|
||||
|
||||
|
||||
// Show shared view
|
||||
if (window.sharedView) {
|
||||
window.sharedView.init();
|
||||
window.sharedView.show();
|
||||
}
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
|
||||
}
|
||||
|
||||
function switchToFilesSection() {
|
||||
@@ -196,7 +192,7 @@ function switchToFilesSection() {
|
||||
|
||||
// Show breadcrumb (only in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
breadcrumb?.classList.remove("hidden");
|
||||
breadcrumb?.classList.remove('hidden');
|
||||
|
||||
// show files container
|
||||
toggleFileContainer(true);
|
||||
@@ -224,14 +220,14 @@ function switchToFavoritesSection() {
|
||||
|
||||
// Hide breadcrumb (only shown in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
breadcrumb?.classList.add("hidden");
|
||||
breadcrumb?.classList.add('hidden');
|
||||
|
||||
// show files container
|
||||
toggleFileContainer(true);
|
||||
|
||||
// ensure correct view
|
||||
syncViewContainers();
|
||||
|
||||
|
||||
//reset files view + remove any error
|
||||
window.ui.resetFilesList();
|
||||
|
||||
@@ -242,12 +238,10 @@ function switchToFavoritesSection() {
|
||||
window.ui.showError(`
|
||||
<i class="fas fa-exclamation-circle empty-state-icon error"></i>
|
||||
<p>Error loading the favorites module</p>
|
||||
`
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
}
|
||||
|
||||
function switchToRecentFilesSection() {
|
||||
@@ -258,7 +252,7 @@ function switchToRecentFilesSection() {
|
||||
|
||||
// Hide breadcrumb (only shown in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
breadcrumb?.classList.add("hidden");
|
||||
breadcrumb?.classList.add('hidden');
|
||||
|
||||
// show files container
|
||||
toggleFileContainer(true);
|
||||
@@ -276,8 +270,7 @@ function switchToRecentFilesSection() {
|
||||
window.ui.showError(`
|
||||
<i class="fas fa-exclamation-circle empty-state-icon error"></i>
|
||||
<p>Error loading the recent module</p>
|
||||
`
|
||||
);
|
||||
`);
|
||||
}
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
}
|
||||
@@ -287,7 +280,7 @@ function switchToPhotosSection() {
|
||||
|
||||
// Hide breadcrumb
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
breadcrumb?.classList.add("hidden");
|
||||
breadcrumb?.classList.add('hidden');
|
||||
|
||||
// Hide file containers
|
||||
toggleFileContainer(false);
|
||||
@@ -303,7 +296,6 @@ function switchToPhotosSection() {
|
||||
window.photosView.show();
|
||||
}
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
|
||||
}
|
||||
|
||||
function switchToTrashSection() {
|
||||
@@ -311,7 +303,7 @@ function switchToTrashSection() {
|
||||
|
||||
// Hide breadcrumb (only shown in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
breadcrumb?.classList.add("hidden");
|
||||
breadcrumb?.classList.add('hidden');
|
||||
|
||||
// Show files containers (to be filled with trash)
|
||||
const filesList = document.getElementById('files-list');
|
||||
|
||||
@@ -11,10 +11,7 @@ async function performSearch(query, sortBy) {
|
||||
app.isSearchMode = true;
|
||||
window.ui.updateBreadcrumb(`Search: "${query}"`);
|
||||
|
||||
window.ui.showError(
|
||||
`<h3><i class="fas fa-spinner fa-spin search-spinner"></i> Searching for "${query}"...</h3>`
|
||||
);
|
||||
|
||||
window.ui.showError(`<h3><i class="fas fa-spinner fa-spin search-spinner"></i> Searching for "${query}"...</h3>`);
|
||||
|
||||
const options = {
|
||||
recursive: true,
|
||||
@@ -37,7 +34,6 @@ async function performSearch(query, sortBy) {
|
||||
|
||||
const searchResults = await window.search.searchFiles(query, options);
|
||||
window.search.displaySearchResults(searchResults);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
window.ui.showNotification('Error', 'Error performing search');
|
||||
|
||||
@@ -29,5 +29,4 @@ window.app = {
|
||||
viewFile: null // current file in inline view
|
||||
};
|
||||
|
||||
window.appElements = {
|
||||
};
|
||||
window.appElements = {};
|
||||
|
||||
+11
-14
@@ -8,7 +8,7 @@ async function loadTrashItems() {
|
||||
try {
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
window.ui.resetFilesList(); // ensure also list visible & error hidden
|
||||
const _tt = (window.i18n && window.i18n.t) ? window.i18n.t : k => k.split('.').pop();
|
||||
const _tt = window.i18n && window.i18n.t ? window.i18n.t : (k) => k.split('.').pop();
|
||||
elements.filesList.innerHTML = `
|
||||
<div class="list-header trash-header">
|
||||
<div data-i18n="files.name">${_tt('files.name')}</div>
|
||||
@@ -31,10 +31,9 @@ async function loadTrashItems() {
|
||||
return;
|
||||
}
|
||||
|
||||
trashItems.forEach(item => {
|
||||
trashItems.forEach((item) => {
|
||||
addTrashItemToView(item);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading trash items:', error);
|
||||
window.ui.showNotification('Error', 'Error loading trash items');
|
||||
@@ -54,22 +53,20 @@ function addTrashItemToView(item) {
|
||||
iconClass = item.icon_class || 'fas fa-folder';
|
||||
typeLabel = window.i18n ? window.i18n.t('files.file_types.folder') : 'Folder';
|
||||
} else {
|
||||
iconClass = item.icon_class || (window.ui && window.ui.getIconClass
|
||||
? window.ui.getIconClass(item.name)
|
||||
: 'fas fa-file');
|
||||
iconSpecialClass = (window.ui && window.ui.getIconSpecialClass)
|
||||
? window.ui.getIconSpecialClass(item.name)
|
||||
: '';
|
||||
iconClass = item.icon_class || (window.ui && window.ui.getIconClass ? window.ui.getIconClass(item.name) : 'fas fa-file');
|
||||
iconSpecialClass = window.ui && window.ui.getIconSpecialClass ? window.ui.getIconSpecialClass(item.name) : '';
|
||||
const cat = item.category || '';
|
||||
typeLabel = cat
|
||||
? (window.i18n ? window.i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat : cat)
|
||||
: (window.i18n ? window.i18n.t('files.file_types.document') : 'Document');
|
||||
? window.i18n
|
||||
? window.i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat
|
||||
: cat
|
||||
: window.i18n
|
||||
? window.i18n.t('files.file_types.document')
|
||||
: 'Document';
|
||||
}
|
||||
|
||||
const isFolder = !isFile;
|
||||
const iconWrapClass = isFolder
|
||||
? 'file-icon folder-icon'
|
||||
: `file-icon ${iconSpecialClass}`.trim();
|
||||
const iconWrapClass = isFolder ? 'file-icon folder-icon' : `file-icon ${iconSpecialClass}`.trim();
|
||||
|
||||
const listElement = document.createElement('div');
|
||||
listElement.className = 'file-item trash-item';
|
||||
|
||||
+155
-144
@@ -7,7 +7,6 @@
|
||||
|
||||
// UI Module
|
||||
const ui = {
|
||||
|
||||
/** @type {HTMLDListElement | null} */
|
||||
//dragPreview,
|
||||
|
||||
@@ -285,8 +284,8 @@ const ui = {
|
||||
*/
|
||||
setupDragAndDrop() {
|
||||
// prepare area to build dragged elements
|
||||
this.dragPreview = document.createElement("div");
|
||||
this.dragPreview.className="drag-preview";
|
||||
this.dragPreview = document.createElement('div');
|
||||
this.dragPreview.className = 'drag-preview';
|
||||
document.body.appendChild(this.dragPreview);
|
||||
this.draggedItems = null;
|
||||
|
||||
@@ -294,9 +293,7 @@ const ui = {
|
||||
|
||||
const collectDroppedEntries = async (dataTransfer) => {
|
||||
const items = Array.from(dataTransfer?.items || []);
|
||||
const rootEntries = items
|
||||
.map(it => (typeof it.webkitGetAsEntry === 'function' ? it.webkitGetAsEntry() : null))
|
||||
.filter(Boolean);
|
||||
const rootEntries = items.map((it) => (typeof it.webkitGetAsEntry === 'function' ? it.webkitGetAsEntry() : null)).filter(Boolean);
|
||||
|
||||
if (rootEntries.length === 0) return null;
|
||||
|
||||
@@ -356,17 +353,17 @@ const ui = {
|
||||
dropzone.addEventListener('drop', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Prevent bubbling to document's drop handler (avoids double upload)
|
||||
e._oxiHandled = true; // Mark as handled for document-level fallback
|
||||
e._oxiHandled = true; // Mark as handled for document-level fallback
|
||||
dropzone.classList.remove('active');
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
// First try directory-aware extraction (Finder folder drag & drop)
|
||||
const droppedEntries = await collectDroppedEntries(e.dataTransfer);
|
||||
if (droppedEntries && droppedEntries.length > 0) {
|
||||
const hasFolderStructure = droppedEntries.some(x => x.relativePath && x.relativePath.includes('/'));
|
||||
const hasFolderStructure = droppedEntries.some((x) => x.relativePath && x.relativePath.includes('/'));
|
||||
if (hasFolderStructure) {
|
||||
fileOps.uploadFolderEntries(droppedEntries);
|
||||
} else {
|
||||
fileOps.uploadFiles(droppedEntries.map(x => x.file));
|
||||
fileOps.uploadFiles(droppedEntries.map((x) => x.file));
|
||||
}
|
||||
setTimeout(() => {
|
||||
dropzone.style.display = 'none';
|
||||
@@ -375,9 +372,7 @@ const ui = {
|
||||
}
|
||||
|
||||
// Detect folder drops: files from folder drops have webkitRelativePath set
|
||||
const hasRelativePaths = Array.from(e.dataTransfer.files).some(
|
||||
f => f.webkitRelativePath && f.webkitRelativePath.includes('/')
|
||||
);
|
||||
const hasRelativePaths = Array.from(e.dataTransfer.files).some((f) => f.webkitRelativePath && f.webkitRelativePath.includes('/'));
|
||||
if (hasRelativePaths) {
|
||||
fileOps.uploadFolderFiles(e.dataTransfer.files);
|
||||
} else {
|
||||
@@ -399,8 +394,7 @@ const ui = {
|
||||
});
|
||||
|
||||
document.addEventListener('dragleave', (e) => {
|
||||
if (e.clientX <= 0 || e.clientY <= 0 ||
|
||||
e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) {
|
||||
if (e.clientX <= 0 || e.clientY <= 0 || e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) {
|
||||
dropzone.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
if (!dropzone.classList.contains('active')) {
|
||||
@@ -421,11 +415,11 @@ const ui = {
|
||||
// First try directory-aware extraction (Finder folder drag & drop)
|
||||
const droppedEntries = await collectDroppedEntries(e.dataTransfer);
|
||||
if (droppedEntries && droppedEntries.length > 0) {
|
||||
const hasFolderStructure = droppedEntries.some(x => x.relativePath && x.relativePath.includes('/'));
|
||||
const hasFolderStructure = droppedEntries.some((x) => x.relativePath && x.relativePath.includes('/'));
|
||||
if (hasFolderStructure) {
|
||||
fileOps.uploadFolderEntries(droppedEntries);
|
||||
} else {
|
||||
fileOps.uploadFiles(droppedEntries.map(x => x.file));
|
||||
fileOps.uploadFiles(droppedEntries.map((x) => x.file));
|
||||
}
|
||||
setTimeout(() => {
|
||||
dropzone.style.display = 'none';
|
||||
@@ -434,9 +428,7 @@ const ui = {
|
||||
}
|
||||
|
||||
// Detect folder drops: files from folder drops have webkitRelativePath set
|
||||
const hasRelativePaths = Array.from(e.dataTransfer.files).some(
|
||||
f => f.webkitRelativePath && f.webkitRelativePath.includes('/')
|
||||
);
|
||||
const hasRelativePaths = Array.from(e.dataTransfer.files).some((f) => f.webkitRelativePath && f.webkitRelativePath.includes('/'));
|
||||
if (hasRelativePaths) {
|
||||
fileOps.uploadFolderFiles(e.dataTransfer.files);
|
||||
} else {
|
||||
@@ -514,7 +506,10 @@ const ui = {
|
||||
// TODO clarify the difference between homeIcon & this first element
|
||||
if (window.app.userHomeFolderName) {
|
||||
if (path.length === 0 || path[0].id !== window.app.userHomeFolderId) {
|
||||
path.unshift({ name: window.app.userHomeFolderName, id: window.app.userHomeFolderId});
|
||||
path.unshift({
|
||||
name: window.app.userHomeFolderName,
|
||||
id: window.app.userHomeFolderId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,7 +542,7 @@ const ui = {
|
||||
// can drag files on this folder
|
||||
// dragover – only folders are valid drop targets
|
||||
item.addEventListener('dragover', (e) => {
|
||||
const card = e.target.closest("span");
|
||||
const card = e.target.closest('span');
|
||||
if (!card || !card.dataset.folderId) return;
|
||||
e.preventDefault();
|
||||
card.classList.add('drop-target');
|
||||
@@ -555,15 +550,15 @@ const ui = {
|
||||
|
||||
// dragleave
|
||||
item.addEventListener('dragleave', (e) => {
|
||||
console.log( "dragleave ",e );
|
||||
const card = e.target.closest("span");
|
||||
console.log('dragleave ', e);
|
||||
const card = e.target.closest('span');
|
||||
if (!card || !card.dataset.folderId) return;
|
||||
card.classList.remove('drop-target');
|
||||
});
|
||||
|
||||
// drop – only folders accept drops
|
||||
item.addEventListener('drop', async (e) => {
|
||||
const card = e.target.closest("span");
|
||||
const card = e.target.closest('span');
|
||||
if (!card) return;
|
||||
const targetFolderId = card.dataset.folderId;
|
||||
if (!targetFolderId) return;
|
||||
@@ -572,7 +567,7 @@ const ui = {
|
||||
card.classList.remove('drop-target');
|
||||
|
||||
const action = e.dataTransfer?.dropEffect;
|
||||
await self._dropToFolder( action, targetFolderId, e.dataTransfer );
|
||||
await self._dropToFolder(action, targetFolderId, e.dataTransfer);
|
||||
});
|
||||
} else {
|
||||
// Last segment: current location, not clickable
|
||||
@@ -639,8 +634,6 @@ const ui = {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
/* ================================================================
|
||||
* Data store + event delegation (replaces per-item listeners)
|
||||
* ================================================================ */
|
||||
@@ -666,42 +659,42 @@ const ui = {
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object[]} folders
|
||||
* @returns
|
||||
*
|
||||
* @param {Object[]} folders
|
||||
* @returns
|
||||
*/
|
||||
_renderFoldersToView(folders) {
|
||||
if (!Array.isArray(folders) || folders.length === 0) return;
|
||||
const target = document.getElementById('files-list')
|
||||
const target = document.getElementById('files-list');
|
||||
if (!target) return;
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
for (const folder of folders) {
|
||||
frag.appendChild( this._createFolderItem(folder));
|
||||
frag.appendChild(this._createFolderItem(folder));
|
||||
}
|
||||
target.appendChild(frag);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object[]} files
|
||||
* @returns
|
||||
/**
|
||||
*
|
||||
* @param {Object[]} files
|
||||
* @returns
|
||||
*/
|
||||
_renderFilesToView(files) {
|
||||
if (!Array.isArray(files) || files.length === 0) return;
|
||||
const target = document.getElementById('files-list')
|
||||
const target = document.getElementById('files-list');
|
||||
if (!target) return;
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
for (const file of files) {
|
||||
frag.appendChild( this._createFileItem(file))
|
||||
frag.appendChild(this._createFileItem(file));
|
||||
}
|
||||
target.appendChild(frag);
|
||||
},
|
||||
|
||||
_upsertById(arr, item) {
|
||||
if (!Array.isArray(arr) || !item || !item.id) return;
|
||||
const idx = arr.findIndex(x => x && x.id === item.id);
|
||||
const idx = arr.findIndex((x) => x && x.id === item.id);
|
||||
if (idx >= 0) {
|
||||
arr[idx] = item;
|
||||
} else {
|
||||
@@ -721,21 +714,19 @@ const ui = {
|
||||
window.multiSelect.clear();
|
||||
|
||||
if (selection.fileIds.length == 0 && selection.folderIds.length == 0) {
|
||||
|
||||
// try to use dataTransfer (direct move without selection)
|
||||
const id = dataTransfer.getData('text/plain');
|
||||
const isFolder = dataTransfer.getData('application/oxicloud-folder') === 'true';
|
||||
|
||||
if (isFolder && id === targetFolderId) {
|
||||
console.log("nothing to do");
|
||||
console.log('nothing to do');
|
||||
return; //nothing to do
|
||||
}
|
||||
// append current item to selection
|
||||
if (isFolder) {
|
||||
console.log(`adding ${id} as folder`);
|
||||
selection.folderIds.push(id);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
console.log(`adding ${id} as file`);
|
||||
selection.fileIds.push(id);
|
||||
}
|
||||
@@ -750,15 +741,14 @@ const ui = {
|
||||
|
||||
let result;
|
||||
switch (action) {
|
||||
case "copy":
|
||||
case 'copy':
|
||||
result = await window.fileOps.batchCopy(selection.fileIds, selection.folderIds, targetFolderId);
|
||||
break;
|
||||
|
||||
case "move":
|
||||
case 'move':
|
||||
result = await window.fileOps.batchMove(selection.fileIds, selection.folderIds, targetFolderId);
|
||||
// redraw directory
|
||||
if (result.success > 0)
|
||||
window.loadFiles();
|
||||
if (result.success > 0) window.loadFiles();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -766,7 +756,7 @@ const ui = {
|
||||
return;
|
||||
}
|
||||
window.multiSelect.showBatchResult(action, result);
|
||||
console.log( result);
|
||||
console.log(result);
|
||||
},
|
||||
|
||||
_hydrateViewIfNeeded() {
|
||||
@@ -801,9 +791,21 @@ const ui = {
|
||||
const itemInfo = (card) => {
|
||||
if (!card) return null;
|
||||
const fileId = card.dataset.fileId;
|
||||
if (fileId) return { type: 'file', id: fileId, name: card.dataset.fileName, data: self._items.get(fileId) };
|
||||
if (fileId)
|
||||
return {
|
||||
type: 'file',
|
||||
id: fileId,
|
||||
name: card.dataset.fileName,
|
||||
data: self._items.get(fileId)
|
||||
};
|
||||
const folderId = card.dataset.folderId;
|
||||
if (folderId) return { type: 'folder', id: folderId, name: card.dataset.folderName, data: self._items.get(folderId) };
|
||||
if (folderId)
|
||||
return {
|
||||
type: 'folder',
|
||||
id: folderId,
|
||||
name: card.dataset.folderName,
|
||||
data: self._items.get(folderId)
|
||||
};
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -815,7 +817,7 @@ const ui = {
|
||||
// WOPI editor intercept: open Office documents in the WOPI editor
|
||||
// But NOT image files - those should be previewed in the inline viewer
|
||||
const ext = (file.name || '').split('.').pop().toLowerCase();
|
||||
const imageExts = ['jpg','jpeg','png','gif','svg','webp','bmp','ico','heic','heif','avif','tiff'];
|
||||
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'ico', 'heic', 'heif', 'avif', 'tiff'];
|
||||
const isImage = (file.mime_type && file.mime_type.startsWith('image/')) || imageExts.includes(ext);
|
||||
try {
|
||||
if (!isImage && window.wopiEditor && await window.wopiEditor.canEdit(file.name)) {
|
||||
@@ -856,13 +858,13 @@ const ui = {
|
||||
window.app.contextMenuTargetFolder = {
|
||||
id: info.id,
|
||||
name: card.dataset.folderName,
|
||||
parent_id: card.dataset.parentId || ""
|
||||
parent_id: card.dataset.parentId || ''
|
||||
};
|
||||
} else {
|
||||
window.app.contextMenuTargetFile = {
|
||||
id: info.id,
|
||||
name: card.dataset.fileName,
|
||||
folder_id: card.dataset.folderId || ""
|
||||
folder_id: card.dataset.folderId || ''
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -878,10 +880,8 @@ const ui = {
|
||||
const info = itemInfo(card);
|
||||
if (!info) return;
|
||||
setContextTarget(card, info);
|
||||
const menuId = info.type === 'folder'
|
||||
? 'folder-context-menu' : 'file-context-menu';
|
||||
showContextMenuAtElement(
|
||||
e.target.closest('.file-actions'), menuId);
|
||||
const menuId = info.type === 'folder' ? 'folder-context-menu' : 'file-context-menu';
|
||||
showContextMenuAtElement(e.target.closest('.file-actions'), menuId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -926,7 +926,7 @@ const ui = {
|
||||
});
|
||||
|
||||
// ── shared events ──────────────────────
|
||||
|
||||
|
||||
filesList.addEventListener('contextmenu', (e) => {
|
||||
const card = e.target.closest('.file-item');
|
||||
if (!card) return;
|
||||
@@ -934,97 +934,100 @@ const ui = {
|
||||
const info = itemInfo(card);
|
||||
if (!info) return;
|
||||
setContextTarget(card, info);
|
||||
const menuId = info.type === 'folder'
|
||||
? 'folder-context-menu' : 'file-context-menu';
|
||||
const menuId = info.type === 'folder' ? 'folder-context-menu' : 'file-context-menu';
|
||||
const menu = document.getElementById(menuId);
|
||||
if (window.contextMenus && typeof window.contextMenus.syncFavoriteOptionLabels === 'function') {
|
||||
window.contextMenus.syncFavoriteOptionLabels();
|
||||
}
|
||||
if (window.contextMenus && typeof window.contextMenus.syncWopiOptionVisibility === 'function') {
|
||||
window.contextMenus.syncWopiOptionVisibility().catch(function(){});
|
||||
window.contextMenus.syncWopiOptionVisibility().catch(function () {});
|
||||
}
|
||||
menu.style.left = `${e.pageX}px`;
|
||||
menu.style.top = `${e.pageY}px`;
|
||||
menu.style.top = `${e.pageY}px`;
|
||||
menu.style.display = 'block';
|
||||
});
|
||||
|
||||
// dragstart
|
||||
filesList.addEventListener('dragstart', (e) => {
|
||||
let card = e.target.closest('.file-item');
|
||||
if (!card) { e.preventDefault(); return; }
|
||||
|
||||
if (!card) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const info = itemInfo(card);
|
||||
if (!info) { e.preventDefault(); return; }
|
||||
if (!info) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
e.dataTransfer.setData('text/plain', info.id);
|
||||
if (info.type === 'folder') {
|
||||
e.dataTransfer.setData(
|
||||
'application/oxicloud-folder', 'true');
|
||||
if (info.type === 'folder') {
|
||||
e.dataTransfer.setData('application/oxicloud-folder', 'true');
|
||||
}
|
||||
// allow copy or move (handled by the browser)
|
||||
e.dataTransfer.effectAllowed = "copyMove";
|
||||
|
||||
self.draggedItems = document.createElement("div");
|
||||
self.draggedItems.className = "dragged-items";
|
||||
|
||||
e.dataTransfer.effectAllowed = 'copyMove';
|
||||
|
||||
self.draggedItems = document.createElement('div');
|
||||
self.draggedItems.className = 'dragged-items';
|
||||
|
||||
let selectedCardFromList = filesList.querySelectorAll(`div.selected > div.name-cell`);
|
||||
if (selectedCardFromList.length == 0) {
|
||||
// fallback to current element
|
||||
selectedCardFromList = card.querySelectorAll('div.name-cell');
|
||||
}
|
||||
|
||||
|
||||
let index = 0;
|
||||
const maxElements = 4;
|
||||
let lastItemDiv = null;
|
||||
|
||||
while (index < selectedCardFromList.length && index < maxElements) {
|
||||
let iconCell = document.createElement("div");
|
||||
let icon = selectedCardFromList[index].getElementsByClassName("file-icon").item(0)?.cloneNode(true);
|
||||
let iconCell = document.createElement('div');
|
||||
let icon = selectedCardFromList[index].getElementsByClassName('file-icon').item(0)?.cloneNode(true);
|
||||
if (icon) {
|
||||
iconCell.appendChild( icon);
|
||||
iconCell.querySelectorAll('img')?.forEach((img) => { img.loading="eager"; } );
|
||||
iconCell.appendChild(icon);
|
||||
iconCell.querySelectorAll('img')?.forEach((img) => {
|
||||
img.loading = 'eager';
|
||||
});
|
||||
}
|
||||
|
||||
let nameCell = document.createElement("div");
|
||||
let name = selectedCardFromList[index].getElementsByTagName("span").item(0)?.cloneNode(true);
|
||||
let nameCell = document.createElement('div');
|
||||
let name = selectedCardFromList[index].getElementsByTagName('span').item(0)?.cloneNode(true);
|
||||
if (name) {
|
||||
nameCell.appendChild(name);
|
||||
}
|
||||
|
||||
let div = document.createElement("div");
|
||||
div.className="file-item";
|
||||
let div = document.createElement('div');
|
||||
div.className = 'file-item';
|
||||
div.appendChild(iconCell);
|
||||
div.appendChild(nameCell);
|
||||
|
||||
self.draggedItems.appendChild( div);
|
||||
self.draggedItems.appendChild(div);
|
||||
index += 1;
|
||||
lastItemDiv = div;
|
||||
}
|
||||
|
||||
// if more than 1 item, display the badge
|
||||
if (selectedCardFromList.length > 1) {
|
||||
let badge = document.createElement("span");
|
||||
badge.className="dragged-items-badge";
|
||||
badge.innerText=`${selectedCardFromList.length}`;
|
||||
self.draggedItems.appendChild( badge);
|
||||
let badge = document.createElement('span');
|
||||
badge.className = 'dragged-items-badge';
|
||||
badge.innerText = `${selectedCardFromList.length}`;
|
||||
self.draggedItems.appendChild(badge);
|
||||
}
|
||||
|
||||
// if more than maxElements display the fading
|
||||
if (selectedCardFromList.length > maxElements) {
|
||||
lastItemDiv.classList.add("fading");
|
||||
lastItemDiv.classList.add('fading');
|
||||
}
|
||||
|
||||
self.dragPreview.appendChild( self.draggedItems);
|
||||
|
||||
self.dragPreview.appendChild(self.draggedItems);
|
||||
e.dataTransfer.setDragImage(self.draggedItems, 0, 0);
|
||||
|
||||
});
|
||||
|
||||
// dragend
|
||||
filesList.addEventListener('dragend', (e) => {
|
||||
|
||||
self.dragPreview.removeChild( self.draggedItems);
|
||||
document.querySelectorAll('.drop-target')
|
||||
.forEach(el => el.classList.remove('drop-target'));
|
||||
self.dragPreview.removeChild(self.draggedItems);
|
||||
document.querySelectorAll('.drop-target').forEach((el) => el.classList.remove('drop-target'));
|
||||
});
|
||||
|
||||
// dragover – only folders are valid drop targets
|
||||
@@ -1054,9 +1057,8 @@ const ui = {
|
||||
card.classList.remove('drop-target');
|
||||
|
||||
const action = e.dataTransfer.dropEffect;
|
||||
await self._dropToFolder( action, targetFolderId, e.dataTransfer);
|
||||
await self._dropToFolder(action, targetFolderId, e.dataTransfer);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
/* ================================================================
|
||||
@@ -1074,7 +1076,7 @@ const ui = {
|
||||
|
||||
if (!window.favorites) return;
|
||||
|
||||
const itemId = star.dataset.itemId;
|
||||
const itemId = star.dataset.itemId;
|
||||
const itemType = star.dataset.itemType;
|
||||
const itemName = star.dataset.itemName;
|
||||
|
||||
@@ -1099,9 +1101,7 @@ const ui = {
|
||||
* Sync favorite visuals for a file/folder across grid and list views.
|
||||
*/
|
||||
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 starBtn = card ? card.querySelector('.favorite-star') : null;
|
||||
@@ -1127,7 +1127,7 @@ const ui = {
|
||||
i.classList.add(isFavorite ? 'fas' : 'far');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const listItem = document.querySelector(selector);
|
||||
if (listItem) {
|
||||
const nameCell = listItem.querySelector('.name-cell');
|
||||
@@ -1155,9 +1155,9 @@ const ui = {
|
||||
_createFolderItem(folder) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'file-item';
|
||||
el.dataset.folderId = folder.id;
|
||||
el.dataset.folderId = folder.id;
|
||||
el.dataset.folderName = folder.name;
|
||||
el.dataset.parentId = folder.parent_id || "";
|
||||
el.dataset.parentId = folder.parent_id || '';
|
||||
|
||||
const isFav = window.favorites && window.favorites.isFavorite(folder.id, 'folder');
|
||||
const formattedDate = window.formatDateTime(folder.modified_at);
|
||||
@@ -1182,7 +1182,7 @@ const ui = {
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (window.app.currentPath !== "") {
|
||||
if (window.app.currentPath !== '') {
|
||||
el.setAttribute('draggable', 'true');
|
||||
}
|
||||
this._bindStarClick(el);
|
||||
@@ -1195,17 +1195,21 @@ const ui = {
|
||||
const iconSpecialClass = file.icon_special_class || this.getIconSpecialClass(file.name);
|
||||
const cat = file.category || '';
|
||||
const typeLabel = cat
|
||||
? (window.i18n ? window.i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat : cat)
|
||||
: (window.i18n ? window.i18n.t('files.file_types.document') : 'Document');
|
||||
? window.i18n
|
||||
? window.i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat
|
||||
: cat
|
||||
: window.i18n
|
||||
? window.i18n.t('files.file_types.document')
|
||||
: 'Document';
|
||||
const fileSize = file.size_formatted || window.formatFileSize(file.size);
|
||||
const formattedDate = window.formatDateTime(file.modified_at);
|
||||
const isFav = window.favorites && window.favorites.isFavorite(file.id, 'file');
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.className = 'file-item';
|
||||
el.dataset.fileId = file.id;
|
||||
el.dataset.fileId = file.id;
|
||||
el.dataset.fileName = file.name;
|
||||
el.dataset.folderId = file.folder_id || "";
|
||||
el.dataset.folderId = file.folder_id || '';
|
||||
el.setAttribute('draggable', 'true');
|
||||
|
||||
el.innerHTML = `
|
||||
@@ -1230,7 +1234,10 @@ const ui = {
|
||||
</div>
|
||||
`;
|
||||
var thumb = el.querySelector('.file-thumb');
|
||||
if (thumb) thumb.addEventListener('error', function() { this.style.display = 'none'; });
|
||||
if (thumb)
|
||||
thumb.addEventListener('error', function () {
|
||||
this.style.display = 'none';
|
||||
});
|
||||
this._bindStarClick(el);
|
||||
return el;
|
||||
},
|
||||
@@ -1241,11 +1248,11 @@ const ui = {
|
||||
|
||||
resetFilesList() {
|
||||
const filesList = document.getElementById('files-list');
|
||||
const filesContainerError=document.getElementById("files-container-error");
|
||||
const filesContainerError = document.getElementById('files-container-error');
|
||||
|
||||
if (!filesList) return;
|
||||
|
||||
filesList.innerHTML=`
|
||||
filesList.innerHTML = `
|
||||
<div class="list-header">
|
||||
<div class="list-header-checkbox"><input type="checkbox" id="select-all-checkbox" title="Select all"></div>
|
||||
<div data-i18n="files.name">Name</div>
|
||||
@@ -1254,12 +1261,11 @@ const ui = {
|
||||
<div data-i18n="files.modified">Modified</div>
|
||||
<div></div><!-- actions -->
|
||||
</div>`;
|
||||
|
||||
if (window.i18n && window.i18n.translateElement)
|
||||
window.i18n.translateElement( filesList);
|
||||
|
||||
filesList.classList.remove("hidden");
|
||||
filesContainerError?.classList.add("hidden");
|
||||
if (window.i18n && window.i18n.translateElement) window.i18n.translateElement(filesList);
|
||||
|
||||
filesList.classList.remove('hidden');
|
||||
filesContainerError?.classList.add('hidden');
|
||||
},
|
||||
|
||||
showEmptyList() {
|
||||
@@ -1267,8 +1273,7 @@ const ui = {
|
||||
<i class="fas fa-folder-open empty-state-icon"></i>
|
||||
<p data-i18n="files.no_files"></p>
|
||||
<p data-i18n="files.empty_hint"></p>
|
||||
`
|
||||
);
|
||||
`);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1277,16 +1282,14 @@ const ui = {
|
||||
*
|
||||
*/
|
||||
showError(content) {
|
||||
const filesContainerError=document.getElementById("files-container-error");
|
||||
const filesList=document.getElementById("files-list");
|
||||
if (filesContainerError)
|
||||
filesContainerError.innerHTML=content;
|
||||
const filesContainerError = document.getElementById('files-container-error');
|
||||
const filesList = document.getElementById('files-list');
|
||||
if (filesContainerError) filesContainerError.innerHTML = content;
|
||||
|
||||
if (window.i18n && window.i18n.translateElement)
|
||||
window.i18n.translateElement( filesContainerError);
|
||||
if (window.i18n && window.i18n.translateElement) window.i18n.translateElement(filesContainerError);
|
||||
|
||||
filesContainerError?.classList.remove("hidden");
|
||||
filesList?.classList.add("hidden");
|
||||
filesContainerError?.classList.remove('hidden');
|
||||
filesList?.classList.add('hidden');
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1381,7 +1384,7 @@ function toggleCardSelection(card, event) {
|
||||
*/
|
||||
function showContextMenuAtElement(triggerElement, menuId) {
|
||||
// Hide any open menus first
|
||||
document.querySelectorAll('.context-menu').forEach(m => m.style.display = 'none');
|
||||
document.querySelectorAll('.context-menu').forEach((m) => (m.style.display = 'none'));
|
||||
|
||||
const menu = document.getElementById(menuId);
|
||||
if (!menu) return;
|
||||
@@ -1403,7 +1406,7 @@ function showContextMenuAtElement(triggerElement, menuId) {
|
||||
window.contextMenus.syncFavoriteOptionLabels();
|
||||
}
|
||||
if (window.contextMenus && typeof window.contextMenus.syncWopiOptionVisibility === 'function') {
|
||||
window.contextMenus.syncWopiOptionVisibility().catch(function(){});
|
||||
window.contextMenus.syncWopiOptionVisibility().catch(function () {});
|
||||
}
|
||||
|
||||
menu.style.left = `${left}px`;
|
||||
@@ -1426,7 +1429,8 @@ function initRubberBandSelection() {
|
||||
}
|
||||
|
||||
let active = false;
|
||||
let startX = 0, startY = 0;
|
||||
let startX = 0,
|
||||
startY = 0;
|
||||
|
||||
// We listen on the whole files-container (covers grid + empty space)
|
||||
const container = document.querySelector('.files-container') || document.getElementById('files-list');
|
||||
@@ -1435,11 +1439,16 @@ function initRubberBandSelection() {
|
||||
container.addEventListener('mousedown', (e) => {
|
||||
// Only start if clicking empty area (not on a card, button, menu, input…)
|
||||
if (e.button !== 0) return; // left click only
|
||||
if (e.target.closest('.file-item') ||
|
||||
if (
|
||||
e.target.closest('.file-item') ||
|
||||
e.target.closest('.context-menu') ||
|
||||
e.target.closest('.upload-dropdown') || e.target.closest('button') ||
|
||||
e.target.closest('input') || e.target.closest('.breadcrumb') ||
|
||||
e.target.closest('.list-header')) return;
|
||||
e.target.closest('.upload-dropdown') ||
|
||||
e.target.closest('button') ||
|
||||
e.target.closest('input') ||
|
||||
e.target.closest('.breadcrumb') ||
|
||||
e.target.closest('.list-header')
|
||||
)
|
||||
return;
|
||||
|
||||
active = true;
|
||||
startX = e.clientX;
|
||||
@@ -1478,13 +1487,10 @@ function initRubberBandSelection() {
|
||||
// Highlight cards that intersect with the rectangle
|
||||
const rectBounds = { left, top, right: left + width, bottom: top + height };
|
||||
|
||||
document.querySelectorAll('#files-list .file-item').forEach(card => {
|
||||
document.querySelectorAll('#files-list .file-item').forEach((card) => {
|
||||
const cardRect = card.getBoundingClientRect();
|
||||
const intersects =
|
||||
cardRect.left < rectBounds.right &&
|
||||
cardRect.right > rectBounds.left &&
|
||||
cardRect.top < rectBounds.bottom &&
|
||||
cardRect.bottom > rectBounds.top;
|
||||
cardRect.left < rectBounds.right && cardRect.right > rectBounds.left && cardRect.top < rectBounds.bottom && cardRect.bottom > rectBounds.top;
|
||||
|
||||
if (intersects) {
|
||||
card.classList.add('selected');
|
||||
@@ -1498,7 +1504,7 @@ function initRubberBandSelection() {
|
||||
card.classList.remove('selected');
|
||||
// Deselect from multiSelect module
|
||||
if (window.multiSelect) {
|
||||
const info = window.multiSelect._extractInfo(card)
|
||||
const info = window.multiSelect._extractInfo(card);
|
||||
if (info) window.multiSelect.deselect(info.id);
|
||||
}
|
||||
}
|
||||
@@ -1516,7 +1522,9 @@ function initRubberBandSelection() {
|
||||
// deselect handler doesn't immediately clear the selection.
|
||||
if (hadSelection) {
|
||||
window.__rubberBandJustFinished = true;
|
||||
requestAnimationFrame(() => { window.__rubberBandJustFinished = false; });
|
||||
requestAnimationFrame(() => {
|
||||
window.__rubberBandJustFinished = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1533,7 +1541,6 @@ window.toggleCardSelection = toggleCardSelection;
|
||||
window.showContextMenuAtElement = showContextMenuAtElement;
|
||||
window.initRubberBandSelection = initRubberBandSelection;
|
||||
|
||||
|
||||
/**
|
||||
* Show a modern confirm dialog (replaces native confirm())
|
||||
* @param {Object} options
|
||||
@@ -1573,7 +1580,9 @@ function showConfirmDialog({ title, message, confirmText, cancelText, danger = t
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Force layout then show
|
||||
requestAnimationFrame(() => { overlay.classList.add('active'); });
|
||||
requestAnimationFrame(() => {
|
||||
overlay.classList.add('active');
|
||||
});
|
||||
|
||||
const cleanup = (result) => {
|
||||
overlay.classList.remove('active');
|
||||
@@ -1583,7 +1592,9 @@ function showConfirmDialog({ title, message, confirmText, cancelText, danger = t
|
||||
|
||||
overlay.querySelector('.confirm-dialog-cancel').addEventListener('click', () => cleanup(false));
|
||||
overlay.querySelector('.confirm-dialog-ok').addEventListener('click', () => cleanup(true));
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) cleanup(false); });
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) cleanup(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
window.showConfirmDialog = showConfirmDialog;
|
||||
|
||||
+139
-47
@@ -18,24 +18,57 @@ const uiFileTypes = {
|
||||
if (!fileName) return 'fas fa-file';
|
||||
const ext = (fileName.split('.').pop() || '').toLowerCase();
|
||||
const map = {
|
||||
pdf:'fas fa-file-pdf', doc:'fas fa-file-word', docx:'fas fa-file-word',
|
||||
txt:'fas fa-file-alt', rtf:'fas fa-file-alt', odt:'fas fa-file-alt',
|
||||
xls:'fas fa-file-excel', xlsx:'fas fa-file-excel', csv:'fas fa-file-excel', ods:'fas fa-file-excel',
|
||||
ppt:'fas fa-file-powerpoint', pptx:'fas fa-file-powerpoint', odp:'fas fa-file-powerpoint',
|
||||
jpg:'fas fa-file-image', jpeg:'fas fa-file-image', png:'fas fa-file-image',
|
||||
gif:'fas fa-file-image', svg:'fas fa-file-image', webp:'fas fa-file-image',
|
||||
bmp:'fas fa-file-image', ico:'fas fa-file-image',
|
||||
mp4:'fas fa-file-video', avi:'fas fa-file-video', mov:'fas fa-file-video',
|
||||
mkv:'fas fa-file-video', webm:'fas fa-file-video', flv:'fas fa-file-video',
|
||||
mp3:'fas fa-file-audio', wav:'fas fa-file-audio', ogg:'fas fa-file-audio',
|
||||
flac:'fas fa-file-audio', aac:'fas fa-file-audio', m4a:'fas fa-file-audio',
|
||||
zip:'fas fa-file-archive', rar:'fas fa-file-archive', '7z':'fas fa-file-archive',
|
||||
tar:'fas fa-file-archive', gz:'fas fa-file-archive',
|
||||
js:'fas fa-file-code', ts:'fas fa-file-code', py:'fas fa-file-code',
|
||||
rs:'fas fa-file-code', java:'fas fa-file-code', html:'fas fa-file-code',
|
||||
css:'fas fa-file-code', json:'fas fa-file-code', xml:'fas fa-file-code',
|
||||
sh:'fas fa-terminal', bash:'fas fa-terminal', bat:'fas fa-terminal',
|
||||
md:'fas fa-file-alt',
|
||||
pdf: 'fas fa-file-pdf',
|
||||
doc: 'fas fa-file-word',
|
||||
docx: 'fas fa-file-word',
|
||||
txt: 'fas fa-file-alt',
|
||||
rtf: 'fas fa-file-alt',
|
||||
odt: 'fas fa-file-alt',
|
||||
xls: 'fas fa-file-excel',
|
||||
xlsx: 'fas fa-file-excel',
|
||||
csv: 'fas fa-file-excel',
|
||||
ods: 'fas fa-file-excel',
|
||||
ppt: 'fas fa-file-powerpoint',
|
||||
pptx: 'fas fa-file-powerpoint',
|
||||
odp: 'fas fa-file-powerpoint',
|
||||
jpg: 'fas fa-file-image',
|
||||
jpeg: 'fas fa-file-image',
|
||||
png: 'fas fa-file-image',
|
||||
gif: 'fas fa-file-image',
|
||||
svg: 'fas fa-file-image',
|
||||
webp: 'fas fa-file-image',
|
||||
bmp: 'fas fa-file-image',
|
||||
ico: 'fas fa-file-image',
|
||||
mp4: 'fas fa-file-video',
|
||||
avi: 'fas fa-file-video',
|
||||
mov: 'fas fa-file-video',
|
||||
mkv: 'fas fa-file-video',
|
||||
webm: 'fas fa-file-video',
|
||||
flv: 'fas fa-file-video',
|
||||
mp3: 'fas fa-file-audio',
|
||||
wav: 'fas fa-file-audio',
|
||||
ogg: 'fas fa-file-audio',
|
||||
flac: 'fas fa-file-audio',
|
||||
aac: 'fas fa-file-audio',
|
||||
m4a: 'fas fa-file-audio',
|
||||
zip: 'fas fa-file-archive',
|
||||
rar: 'fas fa-file-archive',
|
||||
'7z': 'fas fa-file-archive',
|
||||
tar: 'fas fa-file-archive',
|
||||
gz: 'fas fa-file-archive',
|
||||
js: 'fas fa-file-code',
|
||||
ts: 'fas fa-file-code',
|
||||
py: 'fas fa-file-code',
|
||||
rs: 'fas fa-file-code',
|
||||
java: 'fas fa-file-code',
|
||||
html: 'fas fa-file-code',
|
||||
css: 'fas fa-file-code',
|
||||
json: 'fas fa-file-code',
|
||||
xml: 'fas fa-file-code',
|
||||
sh: 'fas fa-terminal',
|
||||
bash: 'fas fa-terminal',
|
||||
bat: 'fas fa-terminal',
|
||||
md: 'fas fa-file-alt'
|
||||
};
|
||||
return map[ext] || 'fas fa-file';
|
||||
},
|
||||
@@ -44,35 +77,94 @@ const uiFileTypes = {
|
||||
if (!fileName) return '';
|
||||
const ext = (fileName.split('.').pop() || '').toLowerCase();
|
||||
const map = {
|
||||
pdf:'pdf-icon',
|
||||
doc:'doc-icon', docx:'doc-icon', odt:'doc-icon', rtf:'doc-icon',
|
||||
xls:'spreadsheet-icon', xlsx:'spreadsheet-icon', ods:'spreadsheet-icon', csv:'spreadsheet-icon',
|
||||
ppt:'presentation-icon', pptx:'presentation-icon', odp:'presentation-icon', key:'presentation-icon',
|
||||
jpg:'image-icon', jpeg:'image-icon', png:'image-icon', gif:'image-icon',
|
||||
svg:'image-icon', webp:'image-icon', bmp:'image-icon', ico:'image-icon',
|
||||
heic:'image-icon', heif:'image-icon', avif:'image-icon', tiff:'image-icon',
|
||||
mp4:'video-icon', avi:'video-icon', mkv:'video-icon', mov:'video-icon',
|
||||
wmv:'video-icon', flv:'video-icon', webm:'video-icon', m4v:'video-icon',
|
||||
mp3:'audio-icon', wav:'audio-icon', ogg:'audio-icon', flac:'audio-icon',
|
||||
aac:'audio-icon', wma:'audio-icon', m4a:'audio-icon', opus:'audio-icon',
|
||||
zip:'archive-icon', rar:'archive-icon', '7z':'archive-icon',
|
||||
tar:'archive-icon', gz:'archive-icon', bz2:'archive-icon', xz:'archive-icon',
|
||||
exe:'installer-icon', msi:'installer-icon', dmg:'installer-icon',
|
||||
deb:'installer-icon', rpm:'installer-icon', appimage:'installer-icon',
|
||||
py:'code-icon py-icon', rs:'code-icon rust-icon', go:'code-icon go-icon',
|
||||
js:'code-icon js-icon', jsx:'code-icon js-icon', mjs:'code-icon js-icon',
|
||||
ts:'code-icon ts-icon', tsx:'code-icon ts-icon',
|
||||
java:'code-icon java-icon', c:'code-icon c-icon', cpp:'code-icon c-icon',
|
||||
cs:'code-icon cs-icon', rb:'code-icon ruby-icon', php:'code-icon php-icon',
|
||||
swift:'code-icon swift-icon',
|
||||
html:'code-icon html-icon', htm:'code-icon html-icon',
|
||||
css:'code-icon css-icon', scss:'code-icon css-icon',
|
||||
json:'code-icon json-icon', xml:'code-icon html-icon',
|
||||
yaml:'code-icon config-icon', yml:'code-icon config-icon',
|
||||
toml:'code-icon config-icon', ini:'code-icon config-icon',
|
||||
sql:'code-icon sql-icon', vue:'code-icon js-icon', svelte:'code-icon js-icon',
|
||||
sh:'script-icon', bash:'script-icon', zsh:'script-icon', bat:'script-icon',
|
||||
md:'code-icon md-icon', txt:'doc-icon',
|
||||
pdf: 'pdf-icon',
|
||||
doc: 'doc-icon',
|
||||
docx: 'doc-icon',
|
||||
odt: 'doc-icon',
|
||||
rtf: 'doc-icon',
|
||||
xls: 'spreadsheet-icon',
|
||||
xlsx: 'spreadsheet-icon',
|
||||
ods: 'spreadsheet-icon',
|
||||
csv: 'spreadsheet-icon',
|
||||
ppt: 'presentation-icon',
|
||||
pptx: 'presentation-icon',
|
||||
odp: 'presentation-icon',
|
||||
key: 'presentation-icon',
|
||||
jpg: 'image-icon',
|
||||
jpeg: 'image-icon',
|
||||
png: 'image-icon',
|
||||
gif: 'image-icon',
|
||||
svg: 'image-icon',
|
||||
webp: 'image-icon',
|
||||
bmp: 'image-icon',
|
||||
ico: 'image-icon',
|
||||
heic: 'image-icon',
|
||||
heif: 'image-icon',
|
||||
avif: 'image-icon',
|
||||
tiff: 'image-icon',
|
||||
mp4: 'video-icon',
|
||||
avi: 'video-icon',
|
||||
mkv: 'video-icon',
|
||||
mov: 'video-icon',
|
||||
wmv: 'video-icon',
|
||||
flv: 'video-icon',
|
||||
webm: 'video-icon',
|
||||
m4v: 'video-icon',
|
||||
mp3: 'audio-icon',
|
||||
wav: 'audio-icon',
|
||||
ogg: 'audio-icon',
|
||||
flac: 'audio-icon',
|
||||
aac: 'audio-icon',
|
||||
wma: 'audio-icon',
|
||||
m4a: 'audio-icon',
|
||||
opus: 'audio-icon',
|
||||
zip: 'archive-icon',
|
||||
rar: 'archive-icon',
|
||||
'7z': 'archive-icon',
|
||||
tar: 'archive-icon',
|
||||
gz: 'archive-icon',
|
||||
bz2: 'archive-icon',
|
||||
xz: 'archive-icon',
|
||||
exe: 'installer-icon',
|
||||
msi: 'installer-icon',
|
||||
dmg: 'installer-icon',
|
||||
deb: 'installer-icon',
|
||||
rpm: 'installer-icon',
|
||||
appimage: 'installer-icon',
|
||||
py: 'code-icon py-icon',
|
||||
rs: 'code-icon rust-icon',
|
||||
go: 'code-icon go-icon',
|
||||
js: 'code-icon js-icon',
|
||||
jsx: 'code-icon js-icon',
|
||||
mjs: 'code-icon js-icon',
|
||||
ts: 'code-icon ts-icon',
|
||||
tsx: 'code-icon ts-icon',
|
||||
java: 'code-icon java-icon',
|
||||
c: 'code-icon c-icon',
|
||||
cpp: 'code-icon c-icon',
|
||||
cs: 'code-icon cs-icon',
|
||||
rb: 'code-icon ruby-icon',
|
||||
php: 'code-icon php-icon',
|
||||
swift: 'code-icon swift-icon',
|
||||
html: 'code-icon html-icon',
|
||||
htm: 'code-icon html-icon',
|
||||
css: 'code-icon css-icon',
|
||||
scss: 'code-icon css-icon',
|
||||
json: 'code-icon json-icon',
|
||||
xml: 'code-icon html-icon',
|
||||
yaml: 'code-icon config-icon',
|
||||
yml: 'code-icon config-icon',
|
||||
toml: 'code-icon config-icon',
|
||||
ini: 'code-icon config-icon',
|
||||
sql: 'code-icon sql-icon',
|
||||
vue: 'code-icon js-icon',
|
||||
svelte: 'code-icon js-icon',
|
||||
sh: 'script-icon',
|
||||
bash: 'script-icon',
|
||||
zsh: 'script-icon',
|
||||
bat: 'script-icon',
|
||||
md: 'code-icon md-icon',
|
||||
txt: 'doc-icon'
|
||||
};
|
||||
return map[ext] || '';
|
||||
}
|
||||
|
||||
@@ -16,7 +16,13 @@ const uiNotifications = {
|
||||
} else if (normalizedTitle.includes('favorite') || normalizedTitle.includes('favorit') || normalizedTitle.includes('fav')) {
|
||||
icon = 'fa-star';
|
||||
iconClass = 'success';
|
||||
} else if (normalizedTitle.includes('delete') || normalizedTitle.includes('removed') || normalizedTitle.includes('trash') || normalizedTitle.includes('rename') || normalizedTitle.includes('complete')) {
|
||||
} else if (
|
||||
normalizedTitle.includes('delete') ||
|
||||
normalizedTitle.includes('removed') ||
|
||||
normalizedTitle.includes('trash') ||
|
||||
normalizedTitle.includes('rename') ||
|
||||
normalizedTitle.includes('complete')
|
||||
) {
|
||||
icon = 'fa-check-circle';
|
||||
iconClass = 'success';
|
||||
}
|
||||
|
||||
+18
-11
@@ -31,9 +31,15 @@ function setupUserMenu() {
|
||||
const USER_DATA_KEY = 'oxicloud_user';
|
||||
const userData = JSON.parse(localStorage.getItem(USER_DATA_KEY) || '{}');
|
||||
const isAdmin = userData.role === 'admin';
|
||||
if (adminBtn) { isAdmin ? adminBtn.classList.remove('hidden') : adminBtn.classList.add('hidden'); }
|
||||
if (adminDivider) { isAdmin ? adminDivider.classList.remove('hidden') : adminDivider.classList.add('hidden'); }
|
||||
if (roleBadge) { isAdmin ? roleBadge.classList.remove('hidden') : roleBadge.classList.add('hidden'); }
|
||||
if (adminBtn) {
|
||||
isAdmin ? adminBtn.classList.remove('hidden') : adminBtn.classList.add('hidden');
|
||||
}
|
||||
if (adminDivider) {
|
||||
isAdmin ? adminDivider.classList.remove('hidden') : adminDivider.classList.add('hidden');
|
||||
}
|
||||
if (roleBadge) {
|
||||
isAdmin ? roleBadge.classList.remove('hidden') : roleBadge.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -96,10 +102,7 @@ function setupUserMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
window.ui.showNotification(
|
||||
newIsDark ? '🌙' : '☀️',
|
||||
newIsDark ? 'Dark mode enabled' : 'Light mode enabled'
|
||||
);
|
||||
window.ui.showNotification(newIsDark ? '🌙' : '☀️', newIsDark ? 'Dark mode enabled' : 'Light mode enabled');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -165,7 +168,7 @@ function updateUserMenuData() {
|
||||
}
|
||||
|
||||
const usedBytes = userData.storage_used_bytes || 0;
|
||||
const quotaBytes = userData.storage_quota_bytes == null ? (10 * 1024 * 1024 * 1024) : userData.storage_quota_bytes;
|
||||
const quotaBytes = userData.storage_quota_bytes == null ? 10 * 1024 * 1024 * 1024 : userData.storage_quota_bytes;
|
||||
const percentage = quotaBytes > 0 ? Math.min(Math.round((usedBytes / quotaBytes) * 100), 100) : 0;
|
||||
|
||||
if (storageFill) storageFill.style.width = percentage + '%';
|
||||
@@ -199,11 +202,11 @@ function showUserProfileModal() {
|
||||
const role = userData.role || 'user';
|
||||
const initials = username.substring(0, 2).toUpperCase();
|
||||
const usedBytes = userData.storage_used_bytes || 0;
|
||||
const quotaBytes = userData.storage_quota_bytes == null ? (10 * 1024 * 1024 * 1024) : userData.storage_quota_bytes;
|
||||
const quotaBytes = userData.storage_quota_bytes == null ? 10 * 1024 * 1024 * 1024 : userData.storage_quota_bytes;
|
||||
const percentage = quotaBytes > 0 ? Math.min(Math.round((usedBytes / quotaBytes) * 100), 100) : 0;
|
||||
const barColor = percentage > 90 ? '#ef4444' : percentage > 70 ? '#f59e0b' : '#22c55e';
|
||||
|
||||
const t = (key, fallback) => (window.i18n && window.i18n.t) ? window.i18n.t(key) || fallback : fallback;
|
||||
const t = (key, fallback) => (window.i18n && window.i18n.t ? window.i18n.t(key) || fallback : fallback);
|
||||
|
||||
const existing = document.getElementById('profile-modal-overlay');
|
||||
if (existing) existing.remove();
|
||||
@@ -268,7 +271,11 @@ async function logout() {
|
||||
// cleared before redirecting, otherwise the login page's session probe
|
||||
// will refresh the token and redirect back to the app).
|
||||
try {
|
||||
await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin', headers: getCsrfHeaders() });
|
||||
await fetch('/api/auth/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: getCsrfHeaders()
|
||||
});
|
||||
} catch (_) {
|
||||
// Best-effort
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user