style: apply linter suggestions
This commit is contained in:
@@ -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', {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
<p>Try downloading it directly.</p>
|
||||
</div>
|
||||
`;
|
||||
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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 }
|
||||
});
|
||||
|
||||
@@ -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 = '<i class="fas fa-spinner fa-spin empty-state-icon spinner"></i>';
|
||||
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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ const photosLightbox = {
|
||||
content.innerHTML = '<div class="photos-loading"><i class="fas fa-spinner"></i></div>';
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user