diff --git a/static/js/app/authSession.js b/static/js/app/authSession.js index ffb6f6bc..f756c8b9 100644 --- a/static/js/app/authSession.js +++ b/static/js/app/authSession.js @@ -115,7 +115,7 @@ async function checkAuthentication() { window.location.href = '/login?source=session_expired'; return; } - } catch (err) { + } catch (_err) { localStorage.removeItem(USER_DATA_KEY); window.location.href = '/login?source=session_expired'; return; @@ -128,9 +128,11 @@ async function checkAuthentication() { console.log('No cached user data, fetching from server'); try { const freshData = await refreshUserData(); - if (freshData && freshData.username) { + if (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 { diff --git a/static/js/app/filesView.js b/static/js/app/filesView.js index abdee799..4faa2221 100644 --- a/static/js/app/filesView.js +++ b/static/js/app/filesView.js @@ -35,7 +35,7 @@ async function getFolder(id) { cache: 'no-store' }; - let folderInformations = await fetch(`/api/folders/${id}`, requestOptions); + const folderInformations = await fetch(`/api/folders/${id}`, requestOptions); if (folderInformations.ok) { return folderInformations.json(); } else { @@ -67,7 +67,7 @@ async function rebuildBreadCrumb() { while (id !== null) { console.log(`fetching folder information for folder ${id}`); try { - let folderInfo = await getFolder(id); + const folderInfo = await getFolder(id); // store the Leaf which is the current folder if (currentFolderInfo === null) { @@ -84,7 +84,7 @@ async function rebuildBreadCrumb() { // 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( @@ -110,7 +110,6 @@ async function rebuildBreadCrumb() { */ async function loadFiles(options = { insertHistory: true }) { const app = window.app; - const elements = window.appElements; try { console.log('Starting loadFiles() - loading files...', options); @@ -125,7 +124,7 @@ async function loadFiles(options = { insertHistory: true }) { 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(() => { + const loadingFiles = setTimeout(() => { // display loader after few delay (will be canceled if result take less time) window.ui.showError(`
@@ -139,7 +138,7 @@ async function loadFiles(options = { insertHistory: true }) { await window.resolveHomeFolder(); } - const timestamp = new Date().getTime(); + const timestamp = Math.floor(Date.now() / 1000); await rebuildBreadCrumb(); @@ -181,7 +180,6 @@ async function loadFiles(options = { insertHistory: true }) { if (forceRefresh) { url += `&force_refresh=true`; - // @ts-ignore requestOptions.headers['X-Force-Refresh'] = 'true'; console.log('Forcing complete refresh ignoring cache'); } @@ -226,7 +224,7 @@ async function loadFiles(options = { insertHistory: true }) { let fileFound = null; // lookup for the given fle - for( const file of fileList) { + for (const file of fileList) { if (file.id === window.app.viewFile) { fileFound = file; break; @@ -236,14 +234,13 @@ async function loadFiles(options = { insertHistory: true }) { if (fileFound) { console.log(`file ${window.app.viewFile} found, calling viewer`); await window.inlineViewer.openFile(fileFound); - } - else { + } else { // remove file console.log(`file ${window.app.viewFile} not found`); window.app.viewFile = null; // correct url/history as file is not found - window.updateHistory( false); + window.updateHistory(false); } } } diff --git a/static/js/app/main.js b/static/js/app/main.js index d0414757..7b90552f 100644 --- a/static/js/app/main.js +++ b/static/js/app/main.js @@ -126,7 +126,7 @@ function setActionsBarMode(mode, force = false) { elements.gridViewBtn = document.getElementById('grid-view-btn'); elements.listViewBtn = document.getElementById('list-view-btn'); - if (window.i18n && window.i18n.translateElement) { + if (window.i18n?.translateElement) { window.i18n.translateElement(elements.actionsBar); } @@ -231,8 +231,8 @@ function deserializeHash() { if (hash_elements[1] === 'files' && hash_elements[2] === 'folder' && hash_elements[3] !== null) { hashContext.path = hash_elements[3]; - - if (hash_elements[4] == 'file' && hash_elements[5] !== null) { + + if (hash_elements[4] === 'file' && hash_elements[5] !== null) { hashContext.file = hash_elements[5]; } } @@ -248,7 +248,7 @@ function deserializeHash() { function updateHistory(insertHistory) { const app = window.app; - let historyData = { + const historyData = { section: app.currentSection, id: app.currentFolder, file: app.viewFile @@ -261,7 +261,7 @@ function updateHistory(insertHistory) { historyUrl = historyUrl.concat('/folder/', app.currentFolderInfo.id); if (window.app.viewFile) { - historyUrl = historyUrl.concat('/file/', window.app.viewFile); + historyUrl = historyUrl.concat('/file/', window.app.viewFile); } // update title document.title = `OxiCloud: ${app.currentFolderInfo.path}`; @@ -325,7 +325,7 @@ function initApp() { cacheElements(); // Initialize file sharing module first - if (window.fileSharing && window.fileSharing.init) { + if (window.fileSharing?.init) { window.fileSharing.init(); } else { console.warn('fileSharing module not fully initialized'); @@ -349,7 +349,7 @@ function initApp() { } // Initialize favorites module if available - if (window.favorites && window.favorites.init) { + if (window.favorites?.init) { console.log('Initializing favorites module'); window.favorites.init(); } else { @@ -357,7 +357,7 @@ function initApp() { } // Initialize recent files module if available - if (window.recent && window.recent.init) { + if (window.recent?.init) { console.log('Initializing recent files module'); window.recent.init(); } else { @@ -365,21 +365,21 @@ function initApp() { } // Initialize multi-select / batch actions - if (window.multiSelect && window.multiSelect.init) { + if (window.multiSelect?.init) { console.log('Initializing multi-select module'); window.multiSelect.init(); } window.addEventListener('authenticationDone', () => { // Check if a context was provided in the URL - let hashContext = deserializeHash(); + const hashContext = deserializeHash(); 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; } - + if (hashContext.file !== null) { window.app.viewFile = hashContext.file; } @@ -388,7 +388,7 @@ function initApp() { }); // Wait for translations to load before checking authentication - if (window.i18n && window.i18n.isLoaded && window.i18n.isLoaded()) { + if (window.i18n?.isLoaded?.()) { // Translations already loaded, proceed with authentication window.checkAuthentication(); } else { @@ -401,7 +401,7 @@ function initApp() { // Set a timeout as a fallback in case translations take too long setTimeout(() => { - if (!window.i18n || !window.i18n.isLoaded || !window.i18n.isLoaded()) { + if (!window.i18n?.isLoaded?.()) { console.warn('Translations loading timeout, proceeding with authentication anyway'); window.checkAuthentication(); } @@ -451,7 +451,9 @@ function setupUploadDropdown() { 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')); + document.querySelectorAll('.upload-dropdown-menu.show').forEach((m) => { + m.classList.remove('show'); + }); if (!isOpen) { menu.classList.add('show'); } @@ -462,14 +464,14 @@ function setupUploadDropdown() { // Close dropdown when clicking outside // remove+add stable handler: guarantees exactly one global listener if (uploadDropdownDocumentClickHandler) { - // @ts-ignore document.removeEventListener('click', uploadDropdownDocumentClickHandler); } 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); } @@ -587,13 +589,15 @@ function setupEventListeners() { 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'); + const itemI18nKey = item.querySelector('span').getAttribute('data-i18n'); switch (itemI18nKey) { case 'nav.shared': // Switch to shared view @@ -712,7 +716,7 @@ function updateStorageUsageDisplay(userData) { storageInfo.removeAttribute('data-i18n'); // Use i18n if available - if (window.i18n && window.i18n.t) { + if (window.i18n?.t) { storageInfo.textContent = window.i18n.t('storage.used', { percentage: usagePercentage, used: usedFormatted, diff --git a/static/js/app/navigation.js b/static/js/app/navigation.js index 2ebf9643..6cd52d19 100644 --- a/static/js/app/navigation.js +++ b/static/js/app/navigation.js @@ -127,7 +127,7 @@ function getSectionFromNavItem(navItem) { * @returns {boolean} true if the section changed */ function setCurrentSection(section) { - if (window.app.currentSection == section) return false; + 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]) => { diff --git a/static/js/app/searchView.js b/static/js/app/searchView.js index 7f687d40..2dfcfc85 100644 --- a/static/js/app/searchView.js +++ b/static/js/app/searchView.js @@ -42,7 +42,7 @@ async function performSearch(query, sortBy) { document.addEventListener('search-resort', (e) => { const searchInput = document.querySelector('.search-container input'); - if (searchInput && searchInput.value.trim()) { + if (searchInput?.value.trim()) { performSearch(searchInput.value.trim(), e.detail.sort_by); } }); diff --git a/static/js/app/trashView.js b/static/js/app/trashView.js index 065e13c9..9ad63788 100644 --- a/static/js/app/trashView.js +++ b/static/js/app/trashView.js @@ -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?.t ? window.i18n.t : (k) => k.split('.').pop(); elements.filesList.innerHTML = `
${_tt('files.name')}
@@ -53,8 +53,8 @@ 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?.getIconClass ? window.ui.getIconClass(item.name) : 'fas fa-file'); + iconSpecialClass = window.ui?.getIconSpecialClass ? window.ui.getIconSpecialClass(item.name) : ''; const cat = item.category || ''; typeLabel = cat ? window.i18n diff --git a/static/js/app/ui.js b/static/js/app/ui.js index 6e30a43f..d7a967cc 100644 --- a/static/js/app/ui.js +++ b/static/js/app/ui.js @@ -359,7 +359,7 @@ 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?.includes('/')); if (hasFolderStructure) { fileOps.uploadFolderEntries(droppedEntries); } else { @@ -372,7 +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?.includes('/')); if (hasRelativePaths) { fileOps.uploadFolderFiles(e.dataTransfer.files); } else { @@ -415,7 +415,7 @@ 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?.includes('/')); if (hasFolderStructure) { fileOps.uploadFolderEntries(droppedEntries); } else { @@ -428,7 +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?.includes('/')); if (hasRelativePaths) { fileOps.uploadFolderFiles(e.dataTransfer.files); } else { @@ -474,13 +474,11 @@ const ui = { updateBreadcrumb() { const breadcrumb = document.querySelector('.breadcrumb'); breadcrumb.innerHTML = ''; - - const self = this; const path = window.app.breadcrumbPath; // [{id, name}, ...] // Helper function to safely get translation text const getTranslatedText = (key, defaultValue) => { - if (!window.i18n || !window.i18n.t) return defaultValue; + if (!window.i18n?.t) return defaultValue; return window.i18n.t(key); }; @@ -496,7 +494,7 @@ const ui = { homeIcon.addEventListener('click', () => { window.app.breadcrumbPath = []; window.app.currentPath = window.app.userHomeFolderId; - self.updateBreadcrumb(); + this.updateBreadcrumb(); window.loadFiles(); }); } @@ -535,7 +533,7 @@ const ui = { item.addEventListener('click', () => { window.app.breadcrumbPath = path.slice(0, index + 1); window.app.currentPath = segment.id; - self.updateBreadcrumb(); + this.updateBreadcrumb(); window.loadFiles(); }); @@ -543,7 +541,7 @@ const ui = { // dragover – only folders are valid drop targets item.addEventListener('dragover', (e) => { const card = e.target.closest('span'); - if (!card || !card.dataset.folderId) return; + if (!card?.dataset.folderId) return; e.preventDefault(); card.classList.add('drop-target'); }); @@ -552,7 +550,7 @@ const ui = { item.addEventListener('dragleave', (e) => { console.log('dragleave ', e); const card = e.target.closest('span'); - if (!card || !card.dataset.folderId) return; + if (!card?.dataset.folderId) return; card.classList.remove('drop-target'); }); @@ -567,7 +565,7 @@ const ui = { card.classList.remove('drop-target'); const action = e.dataTransfer?.dropEffect; - await self._dropToFolder(action, targetFolderId, e.dataTransfer); + await this._dropToFolder(action, targetFolderId, e.dataTransfer); }); } else { // Last segment: current location, not clickable @@ -693,7 +691,7 @@ const ui = { }, _upsertById(arr, item) { - if (!Array.isArray(arr) || !item || !item.id) return; + if (!Array.isArray(arr) || !item?.id) return; const idx = arr.findIndex((x) => x && x.id === item.id); if (idx >= 0) { arr[idx] = item; @@ -709,11 +707,11 @@ const ui = { * @param {any} dataTransfer fallback if nothing is selected */ async _dropToFolder(action, targetFolderId, dataTransfer) { - let selection = window.multiSelect.getSelection(targetFolderId); + const selection = window.multiSelect.getSelection(targetFolderId); window.multiSelect.clear(); - if (selection.fileIds.length == 0 && selection.folderIds.length == 0) { + 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'; @@ -785,8 +783,6 @@ const ui = { if (!filesList) return; this._delegationReady = true; - const self = this; - // ── helpers ──────────────────────────────────────────────── const itemInfo = (card) => { if (!card) return null; @@ -796,7 +792,7 @@ const ui = { type: 'file', id: fileId, name: card.dataset.fileName, - data: self._items.get(fileId) + data: this._items.get(fileId) }; const folderId = card.dataset.folderId; if (folderId) @@ -804,7 +800,7 @@ const ui = { type: 'folder', id: folderId, name: card.dataset.folderName, - data: self._items.get(folderId) + data: this._items.get(folderId) }; return null; }; @@ -818,25 +814,23 @@ const ui = { // 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 isImage = (file.mime_type && file.mime_type.startsWith('image/')) || imageExts.includes(ext); + const isImage = file.mime_type?.startsWith('image/') || imageExts.includes(ext); try { - if (!isImage && window.wopiEditor && await window.wopiEditor.canEdit(file.name)) { - await window.wopiEditor.openInModal(file.id, file.name, 'edit') + if (!isImage && window.wopiEditor && (await window.wopiEditor.canEdit(file.name))) { + await window.wopiEditor.openInModal(file.id, file.name, 'edit'); return; } - } - catch (e) { + } catch (e) { console.warn(`WOPI Editor failed, falling bck to classic view `, e); } - if (self.isViewableFile(file) || isImage) { + if (this.isViewableFile(file) || isImage) { if (window.inlineViewer) { window.inlineViewer.openFile(file); // update history window.app.viewFile = file.id; window.updateHistory(false); - } - else { + } else { window.fileOps.downloadFile(file.id, file.name); } } else { @@ -849,7 +843,7 @@ const ui = { const folderName = card.dataset.folderName; window.app.breadcrumbPath.push({ id: folderId, name: folderName }); window.app.currentPath = folderId; - self.updateBreadcrumb(); + this.updateBreadcrumb(); window.loadFiles(); }; @@ -940,7 +934,7 @@ const ui = { window.contextMenus.syncFavoriteOptionLabels(); } if (window.contextMenus && typeof window.contextMenus.syncWopiOptionVisibility === 'function') { - window.contextMenus.syncWopiOptionVisibility().catch(function () {}); + window.contextMenus.syncWopiOptionVisibility().catch(() => {}); } menu.style.left = `${e.pageX}px`; menu.style.top = `${e.pageY}px`; @@ -949,7 +943,7 @@ const ui = { // dragstart filesList.addEventListener('dragstart', (e) => { - let card = e.target.closest('.file-item'); + const card = e.target.closest('.file-item'); if (!card) { e.preventDefault(); return; @@ -968,11 +962,11 @@ const ui = { // allow copy or move (handled by the browser) e.dataTransfer.effectAllowed = 'copyMove'; - self.draggedItems = document.createElement('div'); - self.draggedItems.className = 'dragged-items'; + this.draggedItems = document.createElement('div'); + this.draggedItems.className = 'dragged-items'; let selectedCardFromList = filesList.querySelectorAll(`div.selected > div.name-cell`); - if (selectedCardFromList.length == 0) { + if (selectedCardFromList.length === 0) { // fallback to current element selectedCardFromList = card.querySelectorAll('div.name-cell'); } @@ -982,8 +976,8 @@ const ui = { 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); + const iconCell = document.createElement('div'); + const icon = selectedCardFromList[index].getElementsByClassName('file-icon').item(0)?.cloneNode(true); if (icon) { iconCell.appendChild(icon); iconCell.querySelectorAll('img')?.forEach((img) => { @@ -991,28 +985,28 @@ const ui = { }); } - let nameCell = document.createElement('div'); - let name = selectedCardFromList[index].getElementsByTagName('span').item(0)?.cloneNode(true); + const nameCell = document.createElement('div'); + const name = selectedCardFromList[index].getElementsByTagName('span').item(0)?.cloneNode(true); if (name) { nameCell.appendChild(name); } - let div = document.createElement('div'); + const div = document.createElement('div'); div.className = 'file-item'; div.appendChild(iconCell); div.appendChild(nameCell); - self.draggedItems.appendChild(div); + this.draggedItems.appendChild(div); index += 1; lastItemDiv = div; } // if more than 1 item, display the badge if (selectedCardFromList.length > 1) { - let badge = document.createElement('span'); + const badge = document.createElement('span'); badge.className = 'dragged-items-badge'; badge.innerText = `${selectedCardFromList.length}`; - self.draggedItems.appendChild(badge); + this.draggedItems.appendChild(badge); } // if more than maxElements display the fading @@ -1020,14 +1014,16 @@ const ui = { lastItemDiv.classList.add('fading'); } - self.dragPreview.appendChild(self.draggedItems); - e.dataTransfer.setDragImage(self.draggedItems, 0, 0); + this.dragPreview.appendChild(this.draggedItems); + e.dataTransfer.setDragImage(this.draggedItems, 0, 0); }); // dragend - filesList.addEventListener('dragend', (e) => { - self.dragPreview.removeChild(self.draggedItems); - document.querySelectorAll('.drop-target').forEach((el) => el.classList.remove('drop-target')); + filesList.addEventListener('dragend', (_e) => { + this.dragPreview.removeChild(this.draggedItems); + document.querySelectorAll('.drop-target').forEach((el) => { + el.classList.remove('drop-target'); + }); }); // dragover – only folders are valid drop targets @@ -1057,7 +1053,7 @@ const ui = { card.classList.remove('drop-target'); const action = e.dataTransfer.dropEffect; - await self._dropToFolder(action, targetFolderId, e.dataTransfer); + await this._dropToFolder(action, targetFolderId, e.dataTransfer); }); }, @@ -1111,8 +1107,8 @@ const ui = { // SVG icon path (after icons.js replacement) const svg = starBtn.querySelector('svg'); - const filledPath = window.OxiIcons && window.OxiIcons['star']; - const outlinePath = window.OxiIcons && window.OxiIcons['star-outline']; + const filledPath = window.OxiIcons?.star; + const outlinePath = window.OxiIcons?.['star-outline']; const targetPath = isFavorite ? filledPath : outlinePath; if (svg && targetPath) { const p = svg.querySelector('path'); @@ -1159,7 +1155,7 @@ const ui = { el.dataset.folderName = folder.name; el.dataset.parentId = folder.parent_id || ''; - const isFav = window.favorites && window.favorites.isFavorite(folder.id, 'folder'); + const isFav = window.favorites?.isFavorite(folder.id, 'folder'); const formattedDate = window.formatDateTime(folder.modified_at); el.innerHTML = ` @@ -1203,7 +1199,7 @@ const ui = { : '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 isFav = window.favorites?.isFavorite(file.id, 'file'); const el = document.createElement('div'); el.className = 'file-item'; @@ -1213,7 +1209,7 @@ const ui = { el.setAttribute('draggable', 'true'); el.innerHTML = ` - +
@@ -1262,7 +1258,7 @@ const ui = {
`; - if (window.i18n && window.i18n.translateElement) window.i18n.translateElement(filesList); + if (window.i18n?.translateElement) window.i18n.translateElement(filesList); filesList.classList.remove('hidden'); filesContainerError?.classList.add('hidden'); @@ -1286,7 +1282,7 @@ const ui = { const filesList = document.getElementById('files-list'); if (filesContainerError) filesContainerError.innerHTML = content; - if (window.i18n && window.i18n.translateElement) window.i18n.translateElement(filesContainerError); + if (window.i18n?.translateElement) window.i18n.translateElement(filesContainerError); filesContainerError?.classList.remove('hidden'); filesList?.classList.add('hidden'); @@ -1384,7 +1380,9 @@ 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; @@ -1406,7 +1404,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(() => {}); } menu.style.left = `${left}px`; diff --git a/static/js/app/uiFileTypes.js b/static/js/app/uiFileTypes.js index 0e7157a8..dd1f5761 100644 --- a/static/js/app/uiFileTypes.js +++ b/static/js/app/uiFileTypes.js @@ -6,7 +6,7 @@ const uiFileTypes = { // TODO: 'd better to use a canViw() method in inlineViewer isViewableFile(file) { - if (!file || !file.mime_type) return false; + if (!file?.mime_type) return false; if (file.mime_type.startsWith('image/')) return true; if (file.mime_type === 'application/pdf') return true; if (file.mime_type.startsWith('audio/')) return true; diff --git a/static/js/app/userMenu.js b/static/js/app/userMenu.js index 4fac01d4..9096c26f 100644 --- a/static/js/app/userMenu.js +++ b/static/js/app/userMenu.js @@ -171,7 +171,7 @@ function updateUserMenuData() { 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 + '%'; + if (storageFill) storageFill.style.width = `${percentage}%`; if (storageText) { const used = window.formatFileSize(usedBytes); const total = window.formatQuotaSize(quotaBytes); @@ -206,7 +206,7 @@ function showUserProfileModal() { 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?.t ? window.i18n.t(key) || fallback : fallback); const existing = document.getElementById('profile-modal-overlay'); if (existing) existing.remove(); @@ -220,7 +220,7 @@ function showUserProfileModal() {
${initials}

${username}

- ${role === 'admin' ? '🛡️ Admin' : '👤 ' + t('user_menu.role_user', 'User')} + ${role === 'admin' ? '🛡️ Admin' : `👤 ${t('user_menu.role_user', 'User')}`}
@@ -240,7 +240,7 @@ function showUserProfileModal() { // Set dynamic bar width and color via JS property (CSP-safe) const barFill = overlay.querySelector('#about-bar-fill'); if (barFill) { - barFill.style.width = percentage + '%'; + barFill.style.width = `${percentage}%`; barFill.style.background = barColor; } diff --git a/static/js/core/csrf.js b/static/js/core/csrf.js index a589d4a7..c8c3159e 100644 --- a/static/js/core/csrf.js +++ b/static/js/core/csrf.js @@ -19,7 +19,7 @@ function getCsrfToken() { return match ? match.split('=')[1] : ''; } -// eslint-disable-next-line no-unused-vars +// biome-ignore lint/correctness/noUnusedVariables: global function function getCsrfHeaders() { const token = getCsrfToken(); return token ? { 'X-CSRF-Token': token } : {}; diff --git a/static/js/core/formatters.js b/static/js/core/formatters.js index 1dbb416a..774a65cf 100644 --- a/static/js/core/formatters.js +++ b/static/js/core/formatters.js @@ -5,7 +5,7 @@ function escapeHtml(str) { if (typeof str !== 'string') return ''; - return str.replace(/&/g, '&').replace(//g, '>').replace(/\"/g, '"').replace(/'/g, '''); + return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); } function formatFileSize(bytes) { @@ -15,7 +15,7 @@ function formatFileSize(bytes) { const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`; } /// Formats a byte count for quota display. When bytes is 0, returns "∞" (unlimited). @@ -34,14 +34,14 @@ function formatDateTime(value) { } else { dateValue = new Date(value); } - if (isNaN(dateValue.getTime())) return String(value); - return dateValue.toLocaleDateString() + ' ' + dateValue.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + if (Number.isNaN(dateValue.getTime())) return String(value); + return `${dateValue.toLocaleDateString()} ${dateValue.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`; } function formatDateShort(value) { if (!value) return 'N/A'; const dateValue = typeof value === 'number' ? new Date(value * 1000) : new Date(value); - if (isNaN(dateValue.getTime())) return String(value); + if (Number.isNaN(dateValue.getTime())) return String(value); return dateValue.toLocaleDateString(undefined, { year: 'numeric', month: 'short', diff --git a/static/js/core/i18n.js b/static/js/core/i18n.js index 8382d2ca..01c9d28f 100644 --- a/static/js/core/i18n.js +++ b/static/js/core/i18n.js @@ -6,7 +6,7 @@ */ // Current locale code (default to browser locale if available, fallback to English) -let currentLocale = (navigator.language && navigator.language.substring(0, 2)) || (navigator.userLanguage && navigator.userLanguage.substring(0, 2)) || 'en'; +let currentLocale = navigator.language?.substring(0, 2) || navigator.userLanguage?.substring(0, 2) || 'en'; // Supported locales (languages that have locale files on the server) // When a locale file is not found, the system gracefully falls back to English @@ -93,6 +93,8 @@ function getNestedValue(obj, path) { * @param {object} params - Parameters to replace in the translation (e.g., {name: 'John'}) * @returns {string} - The translated string or the key itself if not found */ + +// biome-ignore lint/correctness/noUnusedVariables: global function function t(key, params = {}) { // Get translation from cache const localeData = translations[currentLocale]; @@ -131,8 +133,8 @@ function t(key, params = {}) { if (!value) { // Try fallback to English - if (currentLocale !== 'en' && translations['en']) { - let fallbackValue = getNestedValue(translations['en'], key); + if (currentLocale !== 'en' && translations.en) { + let fallbackValue = getNestedValue(translations.en, key); if (!fallbackValue) { const aliasMap = { @@ -143,7 +145,7 @@ function t(key, params = {}) { }; const aliasKey = aliasMap[key]; if (aliasKey) { - fallbackValue = getNestedValue(translations['en'], aliasKey); + fallbackValue = getNestedValue(translations.en, aliasKey); } } @@ -304,8 +306,8 @@ function safeT(key, params = {}) { let value = getNestedValue(localeData, key); // Fallback to English - if (!value && currentLocale !== 'en' && translations['en']) { - value = getNestedValue(translations['en'], key); + if (!value && currentLocale !== 'en' && translations.en) { + value = getNestedValue(translations.en, key); } if (!value) return key; diff --git a/static/js/core/icons.js b/static/js/core/icons.js index d2745df9..e6938360 100644 --- a/static/js/core/icons.js +++ b/static/js/core/icons.js @@ -10,8 +10,6 @@ * https://fontawesome.com/license/free */ -'use strict'; - // ── Icon registry ────────────────────────────────────────────── // Each entry: [viewBox-width, svg-path-d] // All icons use viewBox="0 0 {width} 512" and fill="currentColor". @@ -379,7 +377,7 @@ function oxiIcon(name, extraClass) { const entry = _ICONS[name]; if (!entry) return ''; const [w, d] = entry; - const cls = extraClass ? 'oxi-icon ' + extraClass : 'oxi-icon'; + const cls = extraClass ? `oxi-icon ${extraClass}` : 'oxi-icon'; return ``; } @@ -423,8 +421,8 @@ function replaceIconsInElement(container) { } // Use outline variant if available and element uses "far" - if (isRegular && _ICONS[iconName + '-outline']) { - iconName = iconName + '-outline'; + if (isRegular && _ICONS[`${iconName}-outline`]) { + iconName = `${iconName}-outline`; } if (!iconName || !_ICONS[iconName]) continue; @@ -433,12 +431,12 @@ function replaceIconsInElement(container) { // Build SVG element const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - svg.setAttribute('viewBox', '0 0 ' + w + ' 512'); + svg.setAttribute('viewBox', `0 0 ${w} 512`); svg.setAttribute('aria-hidden', 'true'); let svgClass = 'oxi-icon'; if (isSpin) svgClass += ' oxi-icon-spin'; - if (extraClasses.length) svgClass += ' ' + extraClasses.join(' '); + if (extraClasses.length) svgClass += ` ${extraClasses.join(' ')}`; svg.setAttribute('class', svgClass); // Copy inline style if present @@ -479,7 +477,7 @@ window.OxiIcons = _ICONS; } // Observe future mutations (dynamic renders, modals, etc.) - new MutationObserver(function (mutations) { + new MutationObserver((mutations) => { if (raf) return; for (let i = 0; i < mutations.length; i++) { if (mutations[i].addedNodes.length) { diff --git a/static/js/core/notifications.js b/static/js/core/notifications.js index 08fa2818..86315ccc 100644 --- a/static/js/core/notifications.js +++ b/static/js/core/notifications.js @@ -14,8 +14,6 @@ */ const notifications = (() => { - 'use strict'; - /* ── state ──────────────────────────────────────────────── */ let _badgeCount = 0; let _batchSeq = 0; @@ -126,7 +124,7 @@ const notifications = (() => { // If panel is closed, bump badge const wrapper = $('notif-wrapper'); - if (!wrapper || !wrapper.classList.contains('open')) { + if (!wrapper?.classList.contains('open')) { _incrementBadge(); } _showEmptyIfNeeded(); @@ -141,7 +139,7 @@ const notifications = (() => { * @param {string} [folderName] root folder name (for folder uploads) */ function addUploadBatch(totalFiles, folderName) { - const batchId = 'batch-' + ++_batchSeq; + const batchId = `batch-${++_batchSeq}`; const body = $('notif-panel-body'); if (!body) return batchId; @@ -217,9 +215,9 @@ const notifications = (() => { if (!shouldUpdate) return; // Show just the file name being uploaded (truncate long paths) - const curEl = $(batchId + '-current'); + const curEl = $(`${batchId}-current`); if (curEl) { - const shortName = fileName.length > 50 ? '…' + fileName.slice(-49) : fileName; + const shortName = fileName.length > 50 ? `…${fileName.slice(-49)}` : fileName; curEl.textContent = shortName; } batch.lastLabelFile = fileName; @@ -228,10 +226,10 @@ const notifications = (() => { // Update progress bar with per-file granularity: // overall% = (completed_files + current_file_fraction) / total_files const overallPct = Math.round(((batch.completed + pct / 100) / batch.totalFiles) * 100); - const fillEl = $(batchId + '-fill'); - const pctEl = $(batchId + '-pct'); - if (fillEl) fillEl.style.width = overallPct + '%'; - if (pctEl) pctEl.textContent = overallPct + '%'; + const fillEl = $(`${batchId}-fill`); + const pctEl = $(`${batchId}-pct`); + if (fillEl) fillEl.style.width = `${overallPct}%`; + if (pctEl) pctEl.textContent = `${overallPct}%`; } } @@ -250,15 +248,15 @@ const notifications = (() => { if (!isLast && batch.completed % 5 !== 0) return; const pctVal = Math.round((batch.completed / batch.totalFiles) * 100); - const fillEl = $(batchId + '-fill'); - const pctEl = $(batchId + '-pct'); - const statsEl = $(batchId + '-stats'); + const fillEl = $(`${batchId}-fill`); + const pctEl = $(`${batchId}-pct`); + const statsEl = $(`${batchId}-stats`); const t = window.i18n?.t || ((k) => k); const filesLabel = t('upload.files'); - if (fillEl) fillEl.style.width = pctVal + '%'; - if (pctEl) pctEl.textContent = pctVal + '%'; + if (fillEl) fillEl.style.width = `${pctVal}%`; + if (pctEl) pctEl.textContent = `${pctVal}%`; if (statsEl) statsEl.textContent = `${batch.completed} / ${batch.totalFiles} ${filesLabel}`; } @@ -269,7 +267,7 @@ const notifications = (() => { const batch = _batches[batchId]; if (!batch) return; - const fillEl = $(batchId + '-fill'); + const fillEl = $(`${batchId}-fill`); if (fillEl) { fillEl.style.width = '100%'; fillEl.classList.add(successCount === totalFiles ? 'done' : 'error'); @@ -279,11 +277,10 @@ const notifications = (() => { const iconEl = batch.el.querySelector('.notif-item-icon'); // Clear the current-file label - const curEl = $(batchId + '-current'); + const curEl = $(`${batchId}-current`); if (curEl) curEl.textContent = ''; const t = window.i18n?.t || ((k) => k); - const filesLabel = t('upload.files'); const completeText = t('upload.complete', { count: successCount, total: totalFiles @@ -302,7 +299,7 @@ const notifications = (() => { // If the panel is closed, bump badge const wrapper = $('notif-wrapper'); - if (!wrapper || !wrapper.classList.contains('open')) { + if (!wrapper?.classList.contains('open')) { _incrementBadge(); } } @@ -312,7 +309,9 @@ const notifications = (() => { const body = $('notif-panel-body'); if (!body) return; // Remove all notif-items - body.querySelectorAll('.notif-item').forEach((el) => el.remove()); + body.querySelectorAll('.notif-item').forEach((el) => { + el.remove(); + }); _clearBadge(); _showEmptyIfNeeded(); // automatically close notification center on clear diff --git a/static/js/core/sw-register.js b/static/js/core/sw-register.js index 5e4de4d0..99d354cd 100644 --- a/static/js/core/sw-register.js +++ b/static/js/core/sw-register.js @@ -1,12 +1,12 @@ // Service Worker registration — runs after page load. if ('serviceWorker' in navigator) { - window.addEventListener('load', function () { + window.addEventListener('load', () => { navigator.serviceWorker .register('/sw.js') - .then(function () { + .then(() => { /* registered */ }) - .catch(function (err) { + .catch((err) => { console.log('Service Worker registration failed:', err); }); }); diff --git a/static/js/features/auth/auth.js b/static/js/features/auth/auth.js index 4e72c20b..a4f71e39 100644 --- a/static/js/features/auth/auth.js +++ b/static/js/features/auth/auth.js @@ -378,7 +378,7 @@ function detectBrowserLanguage() { // Build a language option element (card style) function buildLanguageCard(lang, isSelected) { const item = document.createElement('div'); - item.className = 'lang-picker-item' + (isSelected ? ' selected' : ''); + item.className = `lang-picker-item${isSelected ? ' selected' : ''}`; item.setAttribute('data-lang', lang.code); item.setAttribute('role', 'option'); item.setAttribute('aria-selected', isSelected); @@ -397,7 +397,6 @@ function initLanguageSelector() { const continueBtn = document.getElementById('language-continue'); const picker = document.getElementById('lang-picker'); const pickerSelected = document.getElementById('lang-picker-selected'); - const pickerDropdown = document.getElementById('lang-picker-dropdown'); const pickerList = document.getElementById('lang-picker-list'); const pickerFlag = document.getElementById('lang-picker-flag'); const pickerName = document.getElementById('lang-picker-name'); @@ -510,7 +509,7 @@ function initLanguageSelector() { localStorage.setItem(FIRST_RUN_KEY, 'true'); // Update i18n if available - if (window.i18n && window.i18n.setLocale) { + if (window.i18n?.setLocale) { await window.i18n.setLocale(selectedLanguage); } @@ -633,7 +632,7 @@ async function configureOidcLoginUI() { // Update button text with provider name const btnTextEl = oidcBtn.querySelector('span'); if (btnTextEl && oidcInfo.provider_name) { - const template = window.i18n && window.i18n.t ? window.i18n.t('auth.sso_login_provider') : 'Sign in with {{provider}}'; + const template = window.i18n?.t ? window.i18n.t('auth.sso_login_provider') : 'Sign in with {{provider}}'; btnTextEl.textContent = template.replace('{{provider}}', oidcInfo.provider_name); } @@ -658,7 +657,7 @@ async function configureOidcLoginUI() { } // DOM elements -let loginPanel, registerPanel, adminSetupPanel, languagePanel; +let loginPanel, registerPanel, adminSetupPanel; let loginForm, registerForm, adminSetupForm; let loginError, registerError, registerSuccess, adminSetupError; @@ -725,7 +724,7 @@ let authInitialized = false; // and clear auth data to break the loop (() => { // Check if we're being redirected in a loop - const refreshAttempts = parseInt(localStorage.getItem('refresh_attempts') || '0'); + const refreshAttempts = parseInt(localStorage.getItem('refresh_attempts') || '0', 10); const redirectSource = new URLSearchParams(window.location.search).get('source'); // Case 1: High refresh attempts @@ -749,7 +748,7 @@ let authInitialized = false; } // Case 3: Multiple redirects in short time - const lastCleanup = parseInt(localStorage.getItem('last_emergency_clean') || '0'); + const lastCleanup = parseInt(localStorage.getItem('last_emergency_clean') || '0', 10); const timeSinceCleanup = Date.now() - lastCleanup; if (lastCleanup > 0 && timeSinceCleanup < 10000) { @@ -931,7 +930,7 @@ if (isLoginPage && registerForm) { } try { - const data = await register(username, email, password); + await register(username, email, password); // Show success message const successMsg = window.i18n ? window.i18n.t('auth.account_success') : 'Account created successfully! You can now log in.'; @@ -992,7 +991,7 @@ if (isLoginPage && adminSetupForm) { const err = await response.json().catch(() => ({})); throw new Error(err.message || 'Setup failed'); } - const data = await response.json(); + await response.json(); // Show success message in the GUI instead of alert const successMsg = window.i18n ? window.i18n.t('auth.admin_success') : 'Admin account created successfully! You can now log in.'; @@ -1048,7 +1047,7 @@ async function login(username, password) { try { const errorData = await response.json(); throw new Error(errorData.error || 'Authentication failed'); - } catch (jsonError) { + } catch (_jsonError) { // If the error response is not valid JSON throw new Error(`Authentication error (${response.status}): ${response.statusText}`); } @@ -1092,7 +1091,7 @@ async function register(username, email, password, role = 'user') { try { const errorData = await response.json(); throw new Error(errorData.error || 'Registration error'); - } catch (jsonError) { + } catch (_jsonError) { // If the error response is not valid JSON throw new Error(`Registration error (${response.status}): ${response.statusText}`); } @@ -1116,6 +1115,8 @@ async function register(username, email, password, role = 'user') { /** * Fetch current user data — relies on HttpOnly cookie (auto-sent). */ + +// biome-ignore lint/correctness/noUnusedVariables: global function async function fetchUserData() { try { const response = await fetch(ME_ENDPOINT, { @@ -1142,7 +1143,7 @@ async function fetchUserData() { async function refreshAuthToken() { try { // Loop-breaker - const refreshAttempts = parseInt(localStorage.getItem('refresh_attempts') || '0'); + const refreshAttempts = parseInt(localStorage.getItem('refresh_attempts') || '0', 10); localStorage.setItem('refresh_attempts', (refreshAttempts + 1).toString()); if (refreshAttempts > 3) { @@ -1232,6 +1233,8 @@ function redirectToMainApp() { /** * Logout — tell the server to clear HttpOnly cookies, then redirect. */ + +// biome-ignore lint/correctness/noUnusedVariables: global function async function logout() { try { await fetch('/api/auth/logout', { diff --git a/static/js/features/files/contextMenus.js b/static/js/features/files/contextMenus.js index 57743dfe..86cf4e00 100644 --- a/static/js/features/files/contextMenus.js +++ b/static/js/features/files/contextMenus.js @@ -25,9 +25,9 @@ const contextMenus = { const wopiEditTab = document.getElementById('wopi-edit-file-tab-option'); if (!wopiEdit || !wopiEditTab) return; - const targetFile = window.app && window.app.contextMenuTargetFile; + const targetFile = window.app?.contextMenuTargetFile; // Don't show WOPI editor for image files - they should use inline preview - const isImage = targetFile && targetFile.mime_type && targetFile.mime_type.startsWith('image/'); + const isImage = targetFile?.mime_type?.startsWith('image/'); const show = targetFile && !isImage && window.wopiEditor && (await window.wopiEditor.canEdit(targetFile.name)); wopiEdit.classList.toggle('hidden', !show); @@ -37,8 +37,8 @@ const contextMenus = { syncFavoriteOptionLabels() { if (!window.favorites) return; - const targetFile = window.app && window.app.contextMenuTargetFile; - const targetFolder = window.app && window.app.contextMenuTargetFolder; + const targetFile = window.app?.contextMenuTargetFile; + const targetFolder = window.app?.contextMenuTargetFolder; if (targetFile) { const isFav = window.favorites.isFavorite(targetFile.id, 'file'); @@ -68,7 +68,7 @@ const contextMenus = { const folder = window.app.contextMenuTargetFolder; // Check if folder is already in favorites to toggle - if (window.favorites && window.favorites.isFavorite(folder.id, 'folder')) { + if (window.favorites?.isFavorite(folder.id, 'folder')) { // Remove from favorites const ok = await window.favorites.removeFromFavorites(folder.id, 'folder'); if (ok && window.ui && typeof window.ui.setFavoriteVisualState === 'function') { @@ -127,7 +127,7 @@ const contextMenus = { .then((response) => response.json()) .then((fileDetails) => { // Check if viewable file type (images, PDFs, text files) - if (window.ui && window.ui.isViewableFile(fileDetails)) { + if (window.ui?.isViewableFile(fileDetails)) { // Open with inline viewer if (window.inlineViewer) { window.inlineViewer.openFile(fileDetails); @@ -177,7 +177,7 @@ const contextMenus = { const file = window.app.contextMenuTargetFile; // Check if file is already in favorites to toggle - if (window.favorites && window.favorites.isFavorite(file.id, 'file')) { + if (window.favorites?.isFavorite(file.id, 'file')) { // Remove from favorites const ok = await window.favorites.removeFromFavorites(file.id, 'file'); if (ok && window.ui && typeof window.ui.setFavoriteVisualState === 'function') { @@ -273,7 +273,7 @@ const contextMenus = { const fileIds = items.filter((i) => i.type === 'file').map((i) => i.id); const folderIds = items.filter((i) => i.type === 'folder').map((i) => i.id); - let result = await window.fileOps.batchCopy(fileIds, folderIds, targetId); + const result = await window.fileOps.batchCopy(fileIds, folderIds, targetId); this.closeMoveDialog(); window.multiSelect.clear(); @@ -306,7 +306,7 @@ const contextMenus = { const fileIds = items.filter((i) => i.type === 'file').map((i) => i.id); const folderIds = items.filter((i) => i.type === 'folder' && i.id !== targetId).map((i) => i.id); - let result = await window.fileOps.batchMove(fileIds, folderIds, targetId); + const result = await window.fileOps.batchMove(fileIds, folderIds, targetId); this.closeMoveDialog(); window.multiSelect.clear(); @@ -664,7 +664,7 @@ const contextMenus = { window.app.selectedTargetFolderId = parentFolderId || ''; // Translate new elements - if (window.i18n && window.i18n.translateElement) { + if (window.i18n?.translateElement) { window.i18n.translateElement(folderSelectContainer); } } catch (error) { @@ -675,7 +675,7 @@ const contextMenus = { /** * Render breadcrumb navigation for move dialog */ - _renderMoveDialogBreadcrumb(container, breadcrumb, currentFolderId) { + _renderMoveDialogBreadcrumb(container, breadcrumb, _currentFolderId) { if (!container) return; container.innerHTML = ''; @@ -757,6 +757,7 @@ const contextMenus = { // Use loadMoveDialogFolders which uses /api/folders/{id}/contents await this.loadMoveDialogFolders(window.app.userHomeFolderId || null); }, + /** * Show share dialog for files or folders * @param {Object} item - File or folder object diff --git a/static/js/features/files/fileOperations.js b/static/js/features/files/fileOperations.js index 4cd42fa3..c7aa9128 100644 --- a/static/js/features/files/fileOperations.js +++ b/static/js/features/files/fileOperations.js @@ -350,7 +350,7 @@ const fileOps = { // Legacy dropzone bar if (progressBar) { - progressBar.style.width = (uploadedCount / totalFiles) * 100 + '%'; + progressBar.style.width = `${(uploadedCount / totalFiles) * 100}%`; } // Notify bell of per-file completion if (window.notifications && batchId) { @@ -601,7 +601,7 @@ const fileOps = { result = await this._uploadFileFetch(formData, thisTimeout); - console.log(`[UPLOAD END] #${idx} ${rel} ok=${result.ok}${result.errorMsg ? ' err=' + result.errorMsg : ''}`); + console.log(`[UPLOAD END] #${idx} ${rel} ok=${result.ok}${result.errorMsg ? ` err=${result.errorMsg}` : ''}`); } catch (e) { result = { ok: false, @@ -618,7 +618,7 @@ const fileOps = { } catch (_) {} } if (progressBar && uploadedCount % 10 === 0) { - progressBar.style.width = (uploadedCount / totalFiles) * 100 + '%'; + progressBar.style.width = `${(uploadedCount / totalFiles) * 100}%`; } if (uploadedCount % 50 === 0 || uploadedCount === totalFiles) { console.log(`Progress: ${uploadedCount}/${totalFiles} (${successCount} ok)`); @@ -748,7 +748,7 @@ const fileOps = { try { const errorData = await response.json(); errorMessage = errorData.error || 'Unknown error'; - } catch (e) { + } catch (_e) { errorMessage = 'Error processing server response'; } window.ui.showNotification('Error', `Error moving the file: ${errorMessage}`); @@ -790,7 +790,7 @@ const fileOps = { try { const errorData = await response.json(); errorMessage = errorData.error || 'Unknown error'; - } catch (e) { + } catch (_e) { errorMessage = 'Error processing server response'; } window.ui.showNotification('Error', `Error moving the folder: ${errorMessage}`); @@ -808,7 +808,7 @@ const fileOps = { * @property {number} success number of files|folders sucessfully updated * @property {number} errors number of files|folders in error * / - + /** * Move files & folders * @param {string[]} fileIds - File IDs @@ -883,7 +883,7 @@ const fileOps = { }); if (response.ok) { - const result = await response.json(); + await response.json(); // Reload files after copying await window.loadFiles(); window.ui.showNotification('File copied', 'File copied successfully'); @@ -893,7 +893,7 @@ const fileOps = { try { const errorData = await response.json(); errorMessage = errorData.error || 'Unknown error'; - } catch (e) { + } catch (_e) { errorMessage = 'Error processing server response'; } window.ui.showNotification('Error', `Error copying the file: ${errorMessage}`); @@ -997,7 +997,7 @@ const fileOps = { try { const errorData = JSON.parse(errorText); errorMessage = errorData.error || response.statusText; - } catch (e) { + } catch (_e) { errorMessage = errorText || response.statusText; } window.ui.showNotification('Error', `Error renaming the file: ${errorMessage}`); @@ -1043,7 +1043,7 @@ const fileOps = { // Try to parse as JSON const errorData = JSON.parse(errorText); errorMessage = errorData.error || response.statusText; - } catch (e) { + } catch (_e) { // If not JSON, use text as is errorMessage = errorText || response.statusText; } diff --git a/static/js/features/files/inlineViewer.js b/static/js/features/files/inlineViewer.js index a791022a..e1ce5fb8 100644 --- a/static/js/features/files/inlineViewer.js +++ b/static/js/features/files/inlineViewer.js @@ -96,7 +96,7 @@ class InlineViewer { // Detect images by mime type OR extension (uploads via WebDAV may lack correct mime) const ext = (file.name || '').split('.').pop().toLowerCase(); 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); + const isImage = file.mime_type?.startsWith('image/') || imageExts.includes(ext); if (!isImage && window.wopiEditor && (await window.wopiEditor.canEdit(file.name))) { try { window.wopiEditor.openInModal(file.id, file.name, 'edit'); @@ -159,7 +159,7 @@ class InlineViewer { // Create text viewer using authenticated fetch this.createTextViewer(file, container, loader); - } else if (file.mime_type && file.mime_type.startsWith('audio/')) { + } else if (file.mime_type?.startsWith('audio/')) { // Hide zoom controls for audio controls.style.display = 'none'; @@ -171,7 +171,7 @@ class InlineViewer { // Create audio player this.createMediaViewer(file, 'audio', container, loader); - } else if (file.mime_type && file.mime_type.startsWith('video/')) { + } else if (file.mime_type?.startsWith('video/')) { // Hide zoom controls for video controls.style.display = 'none'; @@ -225,7 +225,7 @@ class InlineViewer { const text = await response.text(); // Remove loader - if (loader && loader.parentNode) { + if (loader?.parentNode) { loader.parentNode.removeChild(loader); } @@ -240,7 +240,7 @@ class InlineViewer { console.error('Error creating text viewer:', error); // Remove loader - if (loader && loader.parentNode) { + if (loader?.parentNode) { loader.parentNode.removeChild(loader); } @@ -284,8 +284,8 @@ class InlineViewer { // e.loaded and e.total are JavaScript numbers (64-bit float) const progress = e.loaded / e.total; const pct = Math.round(progress * 100); - progressBar.style.width = pct + '%'; - progressText.textContent = pct + '%'; + progressBar.style.width = `${pct}%`; + progressText.textContent = `${pct}%`; } }; @@ -299,7 +299,7 @@ class InlineViewer { } }; - xhr.onerror = function () { + xhr.onerror = () => { reject(new Error('Network error')); }; @@ -310,10 +310,10 @@ class InlineViewer { const blob = response; const blobUrl = URL.createObjectURL(blob); - console.log('Created blob URL:', blobUrl.substring(0, 30) + '...'); + console.log('Created blob URL:', `${blobUrl.substring(0, 30)}...`); // Remove loader - if (loader && loader.parentNode) { + if (loader?.parentNode) { loader.parentNode.removeChild(loader); } @@ -369,7 +369,7 @@ class InlineViewer { console.error('Error creating blob URL viewer:', error); // Remove loader - if (loader && loader.parentNode) { + if (loader?.parentNode) { loader.parentNode.removeChild(loader); } @@ -395,7 +395,7 @@ class InlineViewer { const blobUrl = URL.createObjectURL(blob); // Remove loader - if (loader && loader.parentNode) { + if (loader?.parentNode) { loader.parentNode.removeChild(loader); } @@ -472,7 +472,7 @@ class InlineViewer { } catch (error) { console.error(`Error creating ${mediaType} viewer:`, error); - if (loader && loader.parentNode) { + if (loader?.parentNode) { loader.parentNode.removeChild(loader); } @@ -492,85 +492,85 @@ class InlineViewer {

Try downloading it directly.

`; - container.appendChild(message); - } - - closeViewer() { - // Get modal - const modal = document.getElementById('inline-viewer-modal'); - - // stops audio/video before closing viewver - const media = modal.querySelector('audio, video'); - if (media && !media.paused) media.pause(); - - // Hide modal - modal.classList.remove('active'); - - // Clean up blob URL if exists - if (this.currentBlobUrl) { - URL.revokeObjectURL(this.currentBlobUrl); - this.currentBlobUrl = null; + container.appendChild(message); } - // clear - window.app.viewFile = null; - window.updateHistory(false); + closeViewer() { + // Get modal + const modal = document.getElementById('inline-viewer-modal'); - // Clear references - this.currentFile = null; - } - - downloadFile(file) { - fetch(`/api/files/${file.id}`, { credentials: 'same-origin' }) - .then(res => { - if (!res.ok) throw new Error(`HTTP ${res.status}`); - return res.blob(); - }) - .then(blob => { - const url = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = file.name; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - }) - .catch(err => console.error('Download error:', err)); - } - - zoomImage(factor) { - const container = document.querySelector('.inline-viewer-container'); - const img = container.querySelector('.inline-viewer-image'); - - if (!img) return; - - // Get current scale - let scale = img.dataset.scale ? parseFloat(img.dataset.scale) : 1.0; - - // Apply zoom factor - scale *= factor; - - // Limit scale - scale = Math.max(0.1, Math.min(5.0, scale)); - - // Save scale - img.dataset.scale = scale; - - // Apply scale - img.style.transform = `scale(${scale})`; - } - - resetZoom() { - const container = document.querySelector('.inline-viewer-container'); - const img = container.querySelector('.inline-viewer-image'); - - if (!img) return; - - // Reset scale - img.dataset.scale = 1.0; - img.style.transform = 'scale(1.0)'; - } + // stops audio/video before closing viewver + const media = modal.querySelector('audio, video'); + if (media && !media.paused) media.pause(); + + // Hide modal + modal.classList.remove('active'); + + // Clean up blob URL if exists + if (this.currentBlobUrl) { + URL.revokeObjectURL(this.currentBlobUrl); + this.currentBlobUrl = null; + } + + // clear + window.app.viewFile = null; + window.updateHistory(false); + + // Clear references + this.currentFile = null; + } + + downloadFile(file) { + fetch(`/api/files/${file.id}`, { credentials: 'same-origin' }) + .then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.blob(); + }) + .then((blob) => { + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = file.name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }) + .catch((err) => console.error('Download error:', err)); + } + + zoomImage(factor) { + const container = document.querySelector('.inline-viewer-container'); + const img = container.querySelector('.inline-viewer-image'); + + if (!img) return; + + // Get current scale + let scale = img.dataset.scale ? parseFloat(img.dataset.scale) : 1.0; + + // Apply zoom factor + scale *= factor; + + // Limit scale + scale = Math.max(0.1, Math.min(5.0, scale)); + + // Save scale + img.dataset.scale = scale; + + // Apply scale + img.style.transform = `scale(${scale})`; + } + + resetZoom() { + const container = document.querySelector('.inline-viewer-container'); + const img = container.querySelector('.inline-viewer-image'); + + if (!img) return; + + // Reset scale + img.dataset.scale = 1.0; + img.style.transform = 'scale(1.0)'; + } } // Initialize viewer when document is ready diff --git a/static/js/features/files/multiSelect.js b/static/js/features/files/multiSelect.js index 54aa8ee5..5cae4d1d 100644 --- a/static/js/features/files/multiSelect.js +++ b/static/js/features/files/multiSelect.js @@ -72,8 +72,12 @@ const multiSelect = { clear() { this._selected.clear(); this._lastClickedIndex = -1; - document.querySelectorAll('.file-item.selected').forEach((el) => el.classList.remove('selected')); - document.querySelectorAll('.item-checkbox').forEach((cb) => (cb.checked = false)); + document.querySelectorAll('.file-item.selected').forEach((el) => { + el.classList.remove('selected'); + }); + document.querySelectorAll('.item-checkbox').forEach((cb) => { + cb.checked = false; + }); this._syncUI(); }, @@ -103,8 +107,8 @@ const multiSelect = { * @return {ItemSelection} */ getSelection(targtFolderId) { - let fileIds = []; - let folderIds = []; + const fileIds = []; + const folderIds = []; // TODO optimize & check if _selected is a better use document.querySelectorAll(`div.file-item.selected`).forEach((item) => { @@ -155,7 +159,9 @@ const multiSelect = { _selectAllInContainer(containerId, selector) { const container = document.getElementById(containerId); if (!container) return; - container.querySelectorAll(selector).forEach((el) => this._selectElement(el)); + container.querySelectorAll(selector).forEach((el) => { + this._selectElement(el); + }); }, _getAllVisibleItems() { @@ -190,7 +196,7 @@ const multiSelect = { const info = this._extractInfo(el); if (!info) return; - if (event && event.shiftKey && this._lastClickedIndex >= 0 && index >= 0) { + if (event?.shiftKey && this._lastClickedIndex >= 0 && index >= 0) { const start = Math.min(this._lastClickedIndex, index); const end = Math.max(this._lastClickedIndex, index); for (let i = start; i <= end; i++) { @@ -200,7 +206,7 @@ const multiSelect = { const sel = iInfo.type === 'folder' ? `[data-folder-id="${iInfo.id}"]` : `[data-file-id="${iInfo.id}"]`; document.querySelectorAll(sel).forEach((e) => { e.classList.add('selected'); - let checkbox = e.querySelector('input[type="checkbox"]'); + const checkbox = e.querySelector('input[type="checkbox"]'); if (checkbox) checkbox.checked = true; }); } @@ -208,7 +214,7 @@ const multiSelect = { } else { const nowSelected = this.toggle(info.id, info.name, info.type, info.parentId); el.classList.toggle('selected', nowSelected); - let checkbox = el.querySelector('input[type="checkbox"]'); + const checkbox = el.querySelector('input[type="checkbox"]'); if (checkbox) checkbox.checked = nowSelected; } this._lastClickedIndex = index; @@ -504,7 +510,7 @@ const multiSelect = { const batchSelectionBar = document.getElementById('batch-selection-bar'); batchSelectionBar.innerHTML = this._buildSelectionBarHTML(); - if (window.i18n && window.i18n.translateElement) { + if (window.i18n?.translateElement) { window.i18n.translateElement(batchSelectionBar); } this._wireBarButtons(); @@ -512,10 +518,9 @@ const multiSelect = { // FIXME: competition with _ _injectListHeaderCheckbox() { - const self = this; const selectAllCheckbox = document.getElementById('select-all-checkbox'); if (!selectAllCheckbox) return; - selectAllCheckbox.addEventListener('change', () => self.toggleAll()); + selectAllCheckbox.addEventListener('change', () => this.toggleAll()); } }; diff --git a/static/js/features/files/search.js b/static/js/features/files/search.js index 8d27106b..46dfc78e 100644 --- a/static/js/features/files/search.js +++ b/static/js/features/files/search.js @@ -47,7 +47,7 @@ const search = { try { const errorJson = await response.json(); errorText = errorJson.error || response.statusText; - } catch (e) { + } catch (_e) { errorText = response.statusText; } console.error(`Search error: ${errorText}`); @@ -142,7 +142,7 @@ const search = { if (sortSelect) { sortSelect.addEventListener('change', () => { const searchInput = document.querySelector('.search-container input'); - if (searchInput && searchInput.value.trim()) { + if (searchInput?.value.trim()) { const event = new CustomEvent('search-resort', { detail: { sort_by: sortSelect.value } }); diff --git a/static/js/features/files/wopiEditor.js b/static/js/features/files/wopiEditor.js index a3a36717..435f4001 100644 --- a/static/js/features/files/wopiEditor.js +++ b/static/js/features/files/wopiEditor.js @@ -17,8 +17,8 @@ class WopiEditor { * Fetches supported extensions from the server (cached after first call). */ async canEdit(filename) { - var ext = filename.split('.').pop().toLowerCase(); - var supported = await this._getSupportedExtensions(); + const ext = filename.split('.').pop().toLowerCase(); + const supported = await this._getSupportedExtensions(); return supported.includes(ext); } @@ -38,8 +38,8 @@ class WopiEditor { async openInTab(fileId, fileName, action) { action = action || 'edit'; try { - var data = await this._getEditorUrlWithFallback(fileId, fileName, action); - var hostUrl = '/wopi/edit/' + encodeURIComponent(fileId) + '?access_token=' + encodeURIComponent(data.access_token); + const data = await this._getEditorUrlWithFallback(fileId, fileName, action); + const hostUrl = `/wopi/edit/${encodeURIComponent(fileId)}?access_token=${encodeURIComponent(data.access_token)}`; window.open(hostUrl, '_blank'); } catch (error) { console.error('Failed to open WOPI editor in tab:', error); @@ -53,12 +53,12 @@ class WopiEditor { * Fetch editor URL and WOPI token from the backend. */ async _getEditorUrl(fileId, action) { - var response = await fetch('/api/wopi/editor-url?file_id=' + encodeURIComponent(fileId) + '&action=' + encodeURIComponent(action), { + const response = await fetch(`/api/wopi/editor-url?file_id=${encodeURIComponent(fileId)}&action=${encodeURIComponent(action)}`, { credentials: 'same-origin' }); if (!response.ok) { - var text = await response.text(); - throw new Error('Editor URL request failed: ' + response.status + ' ' + text); + const text = await response.text(); + throw new Error(`Editor URL request failed: ${response.status} ${text}`); } return response.json(); } @@ -94,55 +94,55 @@ class WopiEditor { _showModal(editorData, fileName) { this.closeEditor(); - var modal = document.createElement('div'); + const modal = document.createElement('div'); modal.id = 'wopi-editor-modal'; modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;z-index:10000;background:#fff;'; - var header = document.createElement('div'); + const header = document.createElement('div'); header.style.cssText = 'height:40px;background:#333;color:#fff;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-family:sans-serif;font-size:14px;'; - var title = document.createElement('span'); + const title = document.createElement('span'); title.textContent = fileName; header.appendChild(title); - var closeBtn = document.createElement('button'); + const closeBtn = document.createElement('button'); closeBtn.textContent = '\u2715'; closeBtn.style.cssText = 'background:none;border:none;color:#fff;cursor:pointer;font-size:18px;padding:4px 8px;'; closeBtn.onclick = this.closeEditor.bind(this); header.appendChild(closeBtn); - var form = document.createElement('form'); + const form = document.createElement('form'); form.id = 'wopi_form'; form.target = 'wopi_frame'; form.action = editorData.editor_url; form.method = 'post'; form.style.display = 'none'; - var tokenInput = document.createElement('input'); + const tokenInput = document.createElement('input'); tokenInput.name = 'access_token'; tokenInput.value = editorData.access_token; tokenInput.type = 'hidden'; form.appendChild(tokenInput); - var ttlInput = document.createElement('input'); + const ttlInput = document.createElement('input'); ttlInput.name = 'access_token_ttl'; ttlInput.value = editorData.access_token_ttl; ttlInput.type = 'hidden'; form.appendChild(ttlInput); - var frameHolder = document.createElement('div'); + const frameHolder = document.createElement('div'); frameHolder.style.cssText = 'position:absolute;top:40px;left:0;right:0;bottom:0;'; // Loading spinner (removed once the editor signals ready) - var spinner = document.createElement('div'); + const spinner = document.createElement('div'); spinner.id = 'wopi-loading-spinner'; spinner.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;background:#f5f5f5;z-index:1;'; spinner.innerHTML = ''; frameHolder.appendChild(spinner); - var iframe = document.createElement('iframe'); + const iframe = document.createElement('iframe'); iframe.name = 'wopi_frame'; iframe.title = 'Document Editor'; iframe.style.cssText = 'width:100%;height:100%;border:none;'; @@ -175,9 +175,9 @@ class WopiEditor { if (msgId === 'UI_Close' || msgId === 'close') { this.closeEditor(); } else if (msgId === 'App_LoadingStatus') { - var status = data.Values && data.Values.Status; + const status = data.Values?.Status; if (status === 'Document_Loaded' || status === 'Frame_Ready') { - var sp = document.getElementById('wopi-loading-spinner'); + const sp = document.getElementById('wopi-loading-spinner'); if (sp) sp.remove(); } } @@ -225,9 +225,9 @@ class WopiEditor { */ async _fetchSupportedExtensions() { try { - var response = await fetch('/wopi/supported-extensions'); + const response = await fetch('/wopi/supported-extensions'); if (response.ok) { - var exts = await response.json(); + const exts = await response.json(); if (Array.isArray(exts) && exts.length > 0) { this._supportedExtensions = exts; return exts; diff --git a/static/js/features/library/favorites.js b/static/js/features/library/favorites.js index 3d9dc7e9..91e24eb8 100644 --- a/static/js/features/library/favorites.js +++ b/static/js/features/library/favorites.js @@ -104,7 +104,7 @@ const favorites = { await this._fetchFromServer(); // Notify user - if (window.ui && window.ui.showNotification) { + if (window.ui?.showNotification) { window.ui.showNotification( window.i18n ? window.i18n.t('favorites.added_title') : 'Added to favorites', `"${name}" ${window.i18n ? window.i18n.t('favorites.added_msg') : 'added to favorites'}` @@ -139,7 +139,7 @@ const favorites = { // Remove from local cache this._cache.delete(this._cacheKey(id, type)); - if (window.ui && window.ui.showNotification) { + if (window.ui?.showNotification) { window.ui.showNotification( window.i18n ? window.i18n.t('favorites.removed_title') : 'Removed from favorites', `"${itemName}" ${window.i18n ? window.i18n.t('favorites.removed_msg') : 'removed from favorites'}` @@ -208,7 +208,7 @@ const favorites = { if (files.length) window.ui.renderFiles(files); } catch (error) { console.error('Error displaying favorites:', error); - if (window.ui && window.ui.showNotification) { + if (window.ui?.showNotification) { window.ui.showNotification('Error', 'Error loading favorite items'); } } diff --git a/static/js/features/library/photos.js b/static/js/features/library/photos.js index ad9ae7e0..410b2920 100644 --- a/static/js/features/library/photos.js +++ b/static/js/features/library/photos.js @@ -207,7 +207,7 @@ const photosView = { if (existingHeader) { // Append tiles to existing grid and update count badge const grid = existingHeader.nextElementSibling; - if (grid && grid.classList.contains('photos-grid')) { + if (grid?.classList.contains('photos-grid')) { grid.insertAdjacentHTML('beforeend', tilesHtml); const countSpan = existingHeader.querySelector('.photos-day-count'); if (countSpan) countSpan.textContent = grid.children.length; @@ -228,7 +228,7 @@ const photosView = { /** Generate HTML for a single photo/video tile */ _renderTile(file) { - const isVideo = file.mime_type && file.mime_type.startsWith('video/'); + const isVideo = file.mime_type?.startsWith('video/'); const selected = this.selected.has(file.id) ? ' selected' : ''; const cachedThumb = isVideo && this._videoThumbCache.has(file.id) ? this._videoThumbCache.get(file.id) : null; const thumbUrl = cachedThumb || `/api/files/${file.id}/thumbnail/preview`; @@ -359,7 +359,7 @@ const photosView = { // Upload to server for permanent caching const token = localStorage.getItem('token') || sessionStorage.getItem('token'); const headers = { 'Content-Type': blob.type, ...getCsrfHeaders() }; - if (token) headers['Authorization'] = `Bearer ${token}`; + if (token) headers.Authorization = `Bearer ${token}`; fetch(`/api/files/${fileId}/thumbnail/preview`, { method: 'PUT', @@ -527,7 +527,9 @@ const photosView = { bar.querySelector('#photos-sel-clear').onclick = () => { this.selected.clear(); - this._container.querySelectorAll('.photo-tile.selected').forEach((t) => t.classList.remove('selected')); + this._container.querySelectorAll('.photo-tile.selected').forEach((t) => { + t.classList.remove('selected'); + }); this._hideSelectionBar(); }; diff --git a/static/js/features/library/photosLightbox.js b/static/js/features/library/photosLightbox.js index 23ce828f..bb661ca1 100644 --- a/static/js/features/library/photosLightbox.js +++ b/static/js/features/library/photosLightbox.js @@ -139,7 +139,7 @@ const photosLightbox = { content.innerHTML = '
'; try { - const isVideo = item.mime_type && item.mime_type.startsWith('video/'); + const isVideo = item.mime_type?.startsWith('video/'); const res = await fetch(`/api/files/${item.id}`, { credentials: 'include', headers: this._headers() @@ -172,7 +172,7 @@ const photosLightbox = { }); if (res.ok) { const data = await res.json(); - let parts = [dateStr]; + const parts = [dateStr]; if (sizeStr) parts.push(sizeStr); if (data.camera_make || data.camera_model) { parts.push([data.camera_make, data.camera_model].filter(Boolean).join(' ')); @@ -182,7 +182,7 @@ const photosLightbox = { } metaEl.textContent = parts.join(' · '); } - } catch (err) { + } catch (_err) { // Non-critical, keep existing meta } }, diff --git a/static/js/features/library/recent.js b/static/js/features/library/recent.js index 78bf32e3..129284b4 100644 --- a/static/js/features/library/recent.js +++ b/static/js/features/library/recent.js @@ -32,7 +32,7 @@ const recent = { */ setupEventListeners() { document.addEventListener('file-accessed', (event) => { - if (event.detail && event.detail.file) { + if (event.detail?.file) { const file = event.detail.file; const itemType = file.item_type || 'file'; this._recordAccess(file.id, itemType); @@ -144,7 +144,7 @@ const recent = { if (files.length) window.ui.renderFiles(files); } catch (error) { console.error('Error displaying recent files:', error); - if (window.ui && window.ui.showNotification) { + if (window.ui?.showNotification) { window.ui.showNotification('Error', 'Error loading recent files'); } } diff --git a/static/js/views/admin/admin.js b/static/js/views/admin/admin.js index 6212a40e..c8478428 100644 --- a/static/js/views/admin/admin.js +++ b/static/js/views/admin/admin.js @@ -14,8 +14,8 @@ function t(key, params) { /** Escape a string for safe embedding inside a JS string literal within an HTML attribute. */ function _escJs(s) { if (typeof s !== 'string') return ''; - return s.replace(/[^\w .\-]/g, function (c) { - return '\\x' + c.charCodeAt(0).toString(16).padStart(2, '0'); + return s.replace(/[^\w .-]/g, (c) => { + return `\\x${c.charCodeAt(0).toString(16).padStart(2, '0')}`; }); } @@ -46,7 +46,7 @@ function formatBytes(bytes) { const k = 1024, sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; + return `${parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`; } function timeAgo(dateStr) { @@ -63,11 +63,11 @@ function timeAgo(dateStr) { /* ── Custom confirm modal ── */ function showConfirm(message) { - return new Promise(function (resolve) { - var overlay = document.getElementById('confirm-modal'); - var msgEl = document.getElementById('confirm-message'); - var yesBtn = document.getElementById('confirm-yes'); - var noBtn = document.getElementById('confirm-cancel'); + return new Promise((resolve) => { + const overlay = document.getElementById('confirm-modal'); + const msgEl = document.getElementById('confirm-message'); + const yesBtn = document.getElementById('confirm-yes'); + const noBtn = document.getElementById('confirm-cancel'); msgEl.textContent = message; overlay.classList.remove('hidden'); overlay.classList.add('show-flex'); @@ -100,10 +100,10 @@ let activeTabName = 'dashboard'; function switchTab(name, el) { if (name === activeTabName) return; - var oldTab = document.getElementById('tab-' + activeTabName); - var newTab = document.getElementById('tab-' + name); + var oldTab = document.getElementById(`tab-${activeTabName}`); + var newTab = document.getElementById(`tab-${name}`); - document.querySelectorAll('.admin-tab').forEach(function (b) { + document.querySelectorAll('.admin-tab').forEach((b) => { b.classList.remove('active'); }); if (el) el.classList.add('active'); @@ -138,7 +138,7 @@ function switchTab(name, el) { async function loadDashboard() { try { - const resp = await fetch(API + '/admin/dashboard', { + const resp = await fetch(`${API}/admin/dashboard`, { headers: headers(), credentials: 'same-origin' }); @@ -147,13 +147,13 @@ async function loadDashboard() { document.getElementById('ds-total-users').textContent = d.total_users; document.getElementById('ds-active-users').textContent = d.active_users; document.getElementById('ds-admin-users').textContent = d.admin_users; - document.getElementById('ds-version').textContent = 'v' + d.server_version; + document.getElementById('ds-version').textContent = `v${d.server_version}`; document.getElementById('ds-used').textContent = formatBytes(d.total_used_bytes); document.getElementById('ds-quota').textContent = formatBytes(d.total_quota_bytes); - document.getElementById('ds-usage-pct').textContent = d.storage_usage_percent.toFixed(1) + '%'; + document.getElementById('ds-usage-pct').textContent = `${d.storage_usage_percent.toFixed(1)}%`; const bar = document.getElementById('ds-bar'); - bar.style.width = Math.min(d.storage_usage_percent, 100) + '%'; - bar.className = 'progress-fill ' + (d.storage_usage_percent > 90 ? 'red' : d.storage_usage_percent > 70 ? 'orange' : 'green'); + bar.style.width = `${Math.min(d.storage_usage_percent, 100)}%`; + bar.className = `progress-fill ${d.storage_usage_percent > 90 ? 'red' : d.storage_usage_percent > 70 ? 'orange' : 'green'}`; document.getElementById('ds-auth').textContent = d.auth_enabled ? t('admin.enabled') : t('admin.disabled'); document.getElementById('ds-oidc').textContent = d.oidc_configured ? t('admin.active') : t('admin.off'); document.getElementById('ds-quotas-flag').textContent = d.quotas_enabled ? t('admin.enabled') : t('admin.disabled'); @@ -179,10 +179,9 @@ async function loadDashboard() { async function loadUsers() { const tbody = document.getElementById('users-tbody'); - tbody.innerHTML = - ' ' + escapeHtml(t('admin.loading_users')) + ''; + tbody.innerHTML = ` ${escapeHtml(t('admin.loading_users'))}`; try { - const resp = await fetch(API + '/admin/users?limit=' + PAGE_SIZE + '&offset=' + usersPage * PAGE_SIZE, { + const resp = await fetch(`${API}/admin/users?limit=${PAGE_SIZE}&offset=${usersPage * PAGE_SIZE}`, { headers: headers(), credentials: 'same-origin' }); @@ -197,7 +196,7 @@ async function loadUsers() { totalUsers = data.total; const users = data.users; if (users.length === 0) { - tbody.innerHTML = '' + escapeHtml(t('admin.no_users_found')) + ''; + tbody.innerHTML = `${escapeHtml(t('admin.no_users_found'))}`; return; } @@ -207,8 +206,8 @@ async function loadUsers() { const quotaColor = quotaPct > 90 ? 'red' : quotaPct > 70 ? 'orange' : 'green'; const quotaText = u.storage_quota_bytes > 0 - ? formatBytes(u.storage_used_bytes) + ' / ' + formatBytes(u.storage_quota_bytes) - : formatBytes(u.storage_used_bytes) + ' / ∞'; + ? `${formatBytes(u.storage_used_bytes)} / ${formatBytes(u.storage_quota_bytes)}` + : `${formatBytes(u.storage_used_bytes)} / ∞`; const isSelf = u.id === currentAdminId; const isOidc = u.auth_provider && u.auth_provider !== 'local'; const authBadge = isOidc @@ -217,12 +216,12 @@ async function loadUsers() { '"> ' + escapeHtml(u.auth_provider) + '' - : '' + escapeHtml(t('admin.local')) + ''; + : `${escapeHtml(t('admin.local'))}`; return ( '' + '' + @@ -308,15 +307,15 @@ async function loadUsers() { .join(''); // Set dynamic progress bar widths (CSP-safe via JS property) - document.querySelectorAll('.progress-fill[data-width]').forEach(function (el) { - el.style.width = el.dataset.width + '%'; + document.querySelectorAll('.progress-fill[data-width]').forEach((el) => { + el.style.width = `${el.dataset.width}%`; el.removeAttribute('data-width'); }); // Wire up admin action buttons (replaces inline onclick handlers) - document.querySelectorAll('.admin-action-btn').forEach(function (btn) { - btn.addEventListener('click', function () { - var action = btn.dataset.action; + document.querySelectorAll('.admin-action-btn').forEach((btn) => { + btn.addEventListener('click', () => { + const action = btn.dataset.action; if (action === 'quota') openQuotaModal(btn.dataset.uid, btn.dataset.uname, Number(btn.dataset.quota)); else if (action === 'reset-pw') openResetPasswordModal(btn.dataset.uid, btn.dataset.uname); else if (action === 'toggle-role') toggleRole(btn.dataset.uid, btn.dataset.role); @@ -356,7 +355,7 @@ async function toggleRole(userId, currentRole) { const ok = await showConfirm(t('admin.confirm_role_change', { role: newRole })); if (!ok) return; try { - const resp = await fetch(API + '/admin/users/' + userId + '/role', { + const resp = await fetch(`${API}/admin/users/${userId}/role`, { method: 'PUT', headers: headers(), credentials: 'same-origin', @@ -377,7 +376,7 @@ async function toggleActive(userId, currentActive) { const ok = await showConfirm(msg); if (!ok) return; try { - const resp = await fetch(API + '/admin/users/' + userId + '/active', { + const resp = await fetch(`${API}/admin/users/${userId}/active`, { method: 'PUT', headers: headers(), credentials: 'same-origin', @@ -397,7 +396,7 @@ async function deleteUser(userId, username) { const ok = await showConfirm(t('admin.confirm_delete_user', { name: username })); if (!ok) return; try { - const resp = await fetch(API + '/admin/users/' + userId, { + const resp = await fetch(`${API}/admin/users/${userId}`, { method: 'DELETE', headers: headers(), credentials: 'same-origin' @@ -429,10 +428,10 @@ function closeQuotaModal() { async function saveQuota() { const val = parseFloat(document.getElementById('qm-value').value) || 0; - const unit = parseInt(document.getElementById('qm-unit').value); + const unit = parseInt(document.getElementById('qm-unit').value, 10); const bytes = Math.round(val * unit); try { - const resp = await fetch(API + '/admin/users/' + quotaUserId + '/quota', { + const resp = await fetch(`${API}/admin/users/${quotaUserId}/quota`, { method: 'PUT', headers: headers(), credentials: 'same-origin', @@ -473,7 +472,7 @@ async function submitCreateUser() { const email = document.getElementById('cu-email').value.trim() || null; const role = document.getElementById('cu-role').value; const quotaVal = parseFloat(document.getElementById('cu-quota-value').value) || 0; - const quotaUnit = parseInt(document.getElementById('cu-quota-unit').value); + const quotaUnit = parseInt(document.getElementById('cu-quota-unit').value, 10); const quotaBytes = Math.round(quotaVal * quotaUnit); const errorEl = document.getElementById('cu-error'); @@ -490,9 +489,9 @@ async function submitCreateUser() { const btn = document.getElementById('cu-submit'); btn.disabled = true; - btn.innerHTML = ' ' + escapeHtml(t('admin.creating')); + btn.innerHTML = ` ${escapeHtml(t('admin.creating'))}`; try { - const resp = await fetch(API + '/admin/users', { + const resp = await fetch(`${API}/admin/users`, { method: 'POST', headers: headers(), credentials: 'same-origin', @@ -518,7 +517,7 @@ async function submitCreateUser() { errorEl.className = 'alert alert-error'; } btn.disabled = false; - btn.innerHTML = ' ' + escapeHtml(t('admin.create_user')); + btn.innerHTML = ` ${escapeHtml(t('admin.create_user'))}`; } let resetPwUserId = ''; @@ -546,9 +545,9 @@ async function submitResetPassword() { const btn = document.getElementById('rp-submit'); btn.disabled = true; - btn.innerHTML = ' ' + escapeHtml(t('admin.resetting')); + btn.innerHTML = ` ${escapeHtml(t('admin.resetting'))}`; try { - const resp = await fetch(API + '/admin/users/' + resetPwUserId + '/password', { + const resp = await fetch(`${API}/admin/users/${resetPwUserId}/password`, { method: 'PUT', headers: headers(), credentials: 'same-origin', @@ -566,14 +565,14 @@ async function submitResetPassword() { errorEl.className = 'alert alert-error'; } btn.disabled = false; - btn.innerHTML = ' ' + escapeHtml(t('admin.reset_btn')); + btn.innerHTML = ` ${escapeHtml(t('admin.reset_btn'))}`; } async function toggleRegistration(enabled) { if (enabled) hideElement('registration-warning'); else showElement('registration-warning', 'flex'); try { - const resp = await fetch(API + '/admin/settings/registration', { + const resp = await fetch(`${API}/admin/settings/registration`, { method: 'PUT', headers: headers(), credentials: 'same-origin', @@ -606,7 +605,7 @@ document.getElementById('disable-password').addEventListener('change', function function showOidcStatus(msg, type) { const el = document.getElementById('oidc-status'); el.textContent = msg; - el.className = 'alert alert-' + type; + el.className = `alert alert-${type}`; } function copyCallback() { @@ -622,10 +621,10 @@ async function testConnection() { } const btn = document.getElementById('discover-btn'); btn.disabled = true; - btn.innerHTML = ' ' + escapeHtml(t('admin.discovering')); + btn.innerHTML = ` ${escapeHtml(t('admin.discovering'))}`; const resultDiv = document.getElementById('discovery-result'); try { - const resp = await fetch(API + '/admin/settings/oidc/test', { + const resp = await fetch(`${API}/admin/settings/oidc/test`, { method: 'POST', headers: headers(), credentials: 'same-origin', @@ -644,19 +643,19 @@ async function testConnection() { if (!document.getElementById('provider-name').value && r.provider_name_suggestion) document.getElementById('provider-name').value = r.provider_name_suggestion; } else { - resultDiv.innerHTML = '
' + escapeHtml(r.message) + '
'; + resultDiv.innerHTML = `
${escapeHtml(r.message)}
`; } } catch (e) { - resultDiv.innerHTML = '
Error: ' + escapeHtml(e.message) + '
'; + resultDiv.innerHTML = `
Error: ${escapeHtml(e.message)}
`; } btn.disabled = false; - btn.innerHTML = ' ' + escapeHtml(t('admin.auto_discover')); + btn.innerHTML = ` ${escapeHtml(t('admin.auto_discover'))}`; } async function saveOidcSettings() { const btn = document.getElementById('save-btn'); btn.disabled = true; - btn.innerHTML = ' ' + escapeHtml(t('admin.saving')); + btn.innerHTML = ` ${escapeHtml(t('admin.saving'))}`; const body = { enabled: document.getElementById('oidc-enabled').checked, issuer_url: document.getElementById('issuer-url').value.trim(), @@ -669,7 +668,7 @@ async function saveOidcSettings() { provider_name: document.getElementById('provider-name').value.trim() || null }; try { - const resp = await fetch(API + '/admin/settings/oidc', { + const resp = await fetch(`${API}/admin/settings/oidc`, { method: 'PUT', headers: headers(), credentials: 'same-origin', @@ -681,18 +680,18 @@ async function saveOidcSettings() { loadDashboard(); } else { const e = await resp.json().catch(() => ({})); - showOidcStatus('Error: ' + (e.message || resp.statusText), 'error'); + showOidcStatus(`Error: ${e.message || resp.statusText}`, 'error'); } } catch (e) { showOidcStatus(t('admin.error_network', { message: e.message }), 'error'); } btn.disabled = false; - btn.innerHTML = ' ' + escapeHtml(t('admin.save_btn')); + btn.innerHTML = ` ${escapeHtml(t('admin.save_btn'))}`; } async function init() { try { - const me = await fetch(API + '/auth/me', { + const me = await fetch(`${API}/auth/me`, { headers: headers(), credentials: 'same-origin' }); @@ -707,7 +706,7 @@ async function init() { } currentAdminId = user.id; - const oidcResp = await fetch(API + '/admin/settings/oidc', { + const oidcResp = await fetch(`${API}/admin/settings/oidc`, { headers: headers(), credentials: 'same-origin' }); @@ -728,7 +727,7 @@ async function init() { document.getElementById('callback-url').textContent = s.callback_url; if (s.client_secret_set) showElement('secret-hint'); (s.env_overrides || []).forEach((field) => { - const badge = document.getElementById('badge-' + field); + const badge = document.getElementById(`badge-${field}`); if (badge) badge.innerHTML = 'ENV'; }); } @@ -748,14 +747,14 @@ function showAccessDenied() { } /* ── Apply i18n when translations load / change ── */ -document.addEventListener('translationsLoaded', function () { - if (window.i18n && window.i18n.translatePage) window.i18n.translatePage(); +document.addEventListener('translationsLoaded', () => { + if (window.i18n?.translatePage) window.i18n.translatePage(); // Re-render dynamic content that uses t() loadDashboard(); if (activeTabName === 'users') loadUsers(); }); -document.addEventListener('localeChanged', function () { - if (window.i18n && window.i18n.translatePage) window.i18n.translatePage(); +document.addEventListener('localeChanged', () => { + if (window.i18n?.translatePage) window.i18n.translatePage(); loadDashboard(); if (activeTabName === 'users') loadUsers(); }); diff --git a/static/js/views/device-verify/device-verify.js b/static/js/views/device-verify/device-verify.js index 14a6a2d8..c62af2ab 100644 --- a/static/js/views/device-verify/device-verify.js +++ b/static/js/views/device-verify/device-verify.js @@ -1,7 +1,5 @@ // device-verify.js — Extracted from inline