perf: Phase 4+5 optimizations — uploads 10x, downloads 2x, concurrent 2x. moka cache, 512KB buffers, remove sync_all, hash-on-write, preloaded queries, bench.sh v3, gitignore storage/. 500MB upload 12.6s->1.3s (392MB/s). RSS 69-113MB, 0 swap.
This commit is contained in:
+59
-140
@@ -3,6 +3,23 @@
|
||||
* This file contains the core functionality, initialization and state management
|
||||
*/
|
||||
|
||||
/**
|
||||
* Escape HTML special characters to prevent XSS attacks.
|
||||
* Use this whenever inserting user-provided text (file names, folder names, etc.) into HTML.
|
||||
* @param {string} str - The string to escape
|
||||
* @returns {string} The escaped string safe for HTML insertion
|
||||
*/
|
||||
function escapeHtml(str) {
|
||||
if (typeof str !== 'string') return '';
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
window.escapeHtml = escapeHtml;
|
||||
|
||||
// Global state
|
||||
const app = {
|
||||
currentView: 'grid', // Current view mode: 'grid' or 'list'
|
||||
@@ -972,8 +989,8 @@ function addTrashItemToView(item) {
|
||||
<div class="file-icon">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
<div class="file-info">${typeLabel} - ${formattedDate}</div>
|
||||
<div class="file-name">${escapeHtml(item.name)}</div>
|
||||
<div class="file-info">${escapeHtml(typeLabel)} - ${escapeHtml(formattedDate)}</div>
|
||||
<div class="trash-actions">
|
||||
<button class="btn-restore" title="${window.i18n ? window.i18n.t('trash.restore') : 'Restore'}">
|
||||
<i class="fas fa-undo"></i>
|
||||
@@ -1013,11 +1030,11 @@ function addTrashItemToView(item) {
|
||||
<div class="file-icon">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<span>${item.name}</span>
|
||||
<span>${escapeHtml(item.name)}</span>
|
||||
</div>
|
||||
<div class="type-cell">${typeLabel}</div>
|
||||
<div class="path-cell">${item.original_path || '--'}</div>
|
||||
<div class="date-cell">${formattedDate}</div>
|
||||
<div class="type-cell">${escapeHtml(typeLabel)}</div>
|
||||
<div class="path-cell">${escapeHtml(item.original_path || '--')}</div>
|
||||
<div class="date-cell">${escapeHtml(formattedDate)}</div>
|
||||
<div class="actions-cell">
|
||||
<button class="btn-restore" title="${window.i18n ? window.i18n.t('trash.restore') : 'Restore'}">
|
||||
<i class="fas fa-undo"></i>
|
||||
@@ -1465,7 +1482,7 @@ async function refreshUserData() {
|
||||
const token = localStorage.getItem(TOKEN_KEY);
|
||||
console.log('refreshUserData called, token:', token ? token.substring(0, 20) + '...' : 'null');
|
||||
|
||||
if (!token || token === 'mock_token_emergency_bypass' || token === 'emergency_token') {
|
||||
if (!token) {
|
||||
console.log('No valid token, skipping user data refresh');
|
||||
return null;
|
||||
}
|
||||
@@ -1579,92 +1596,12 @@ function showUserProfileModal() {
|
||||
* Check if user is authenticated and load user's home folder
|
||||
*/
|
||||
async function checkAuthentication() {
|
||||
// COMPLETE BREAK FOR AUTHENTICATION LOOPS:
|
||||
// Always allow app to load with minimal authentication
|
||||
// This is an emergency fix to stop the redirect loops
|
||||
|
||||
// Check URL for no_redirect parameter that indicates we should bypass auth
|
||||
const bypassAuth = window.location.search.includes('no_redirect=true') ||
|
||||
window.location.search.includes('bypass_auth=true');
|
||||
|
||||
if (bypassAuth) {
|
||||
console.log('CRITICAL: Bypassing all authentication checks due to URL parameter');
|
||||
|
||||
// Always force a clean authentication state to break loops
|
||||
const TOKEN_KEY = 'oxicloud_token';
|
||||
const USER_DATA_KEY = 'oxicloud_user';
|
||||
|
||||
// Set a mock token if needed
|
||||
if (!localStorage.getItem(TOKEN_KEY)) {
|
||||
console.log('Setting mock token to prevent redirects');
|
||||
localStorage.setItem(TOKEN_KEY, 'mock_token_emergency_bypass');
|
||||
// Set expiry far in the future
|
||||
localStorage.setItem('oxicloud_token_expiry',
|
||||
new Date(Date.now() + 86400000 * 30).toISOString()); // 30 days
|
||||
}
|
||||
|
||||
// Create minimal user data to make the app work
|
||||
const userData = JSON.parse(localStorage.getItem(USER_DATA_KEY) || '{}');
|
||||
if (!userData.username) {
|
||||
console.log('No user data found, creating mock user');
|
||||
const defaultUserData = {
|
||||
id: 'default-user-id',
|
||||
username: 'usuario',
|
||||
email: 'usuario@example.com',
|
||||
storage_quota_bytes: 10737418240, // 10GB default
|
||||
storage_used_bytes: 0
|
||||
};
|
||||
localStorage.setItem(USER_DATA_KEY, JSON.stringify(defaultUserData));
|
||||
|
||||
// Update avatar with default initials
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach(el => el.textContent = 'US');
|
||||
|
||||
// Update storage display with default values
|
||||
updateStorageUsageDisplay(defaultUserData);
|
||||
} else {
|
||||
// Update avatar with user initials
|
||||
const userInitials = userData.username.substring(0, 2).toUpperCase();
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach(el => el.textContent = userInitials);
|
||||
|
||||
// Show cached storage first, then try to refresh from server
|
||||
updateStorageUsageDisplay(userData);
|
||||
|
||||
// Try to get updated storage from server (if we have a real token)
|
||||
const token = localStorage.getItem(TOKEN_KEY);
|
||||
if (token && token !== 'mock_token_emergency_bypass' && token !== 'emergency_token') {
|
||||
console.log('Bypass mode: Attempting to refresh storage from server...');
|
||||
refreshUserData().then(freshData => {
|
||||
if (freshData) {
|
||||
console.log('Bypass mode: Storage updated from server');
|
||||
}
|
||||
}).catch(err => {
|
||||
console.warn('Bypass mode: Could not refresh user data:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Reset all counters to prevent loops
|
||||
sessionStorage.removeItem('redirect_count');
|
||||
localStorage.setItem('refresh_attempts', '0');
|
||||
|
||||
// Proceed directly to load files
|
||||
app.currentPath = '';
|
||||
ui.updateBreadcrumb('');
|
||||
loadFiles();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Simplified authentication check - just verify token exists
|
||||
const TOKEN_KEY = 'oxicloud_token';
|
||||
const REFRESH_TOKEN_KEY = 'oxicloud_refresh_token';
|
||||
const TOKEN_EXPIRY_KEY = 'oxicloud_token_expiry';
|
||||
const USER_DATA_KEY = 'oxicloud_user';
|
||||
|
||||
// Reset counters to prevent loops
|
||||
sessionStorage.removeItem('redirect_count');
|
||||
localStorage.setItem('refresh_attempts', '0');
|
||||
|
||||
// --- OIDC exchange code handling ---
|
||||
// After OIDC login, the backend redirects here with ?oidc_code=...
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@@ -1737,18 +1674,16 @@ async function checkAuthentication() {
|
||||
}
|
||||
}
|
||||
|
||||
// Simple token check - just verify it exists
|
||||
// Verify token exists
|
||||
const token = localStorage.getItem(TOKEN_KEY);
|
||||
|
||||
if (!token) {
|
||||
console.log('No token found, redirecting to login');
|
||||
// Avoid potential loop by adding a parameter
|
||||
const redirectUrl = '/login?source=app';
|
||||
window.location.href = redirectUrl;
|
||||
window.location.href = '/login?source=app';
|
||||
return;
|
||||
}
|
||||
|
||||
// Token exists, proceed with minimal validation
|
||||
// Token exists, proceed with app initialization
|
||||
console.log('Token found, proceeding with app initialization');
|
||||
|
||||
// Display user information if available
|
||||
@@ -1769,7 +1704,6 @@ async function checkAuthentication() {
|
||||
updateStorageUsageDisplay(userData);
|
||||
|
||||
// Then refresh user data from server in the background to get updated storage
|
||||
// This triggers the backend to recalculate storage and returns fresh data
|
||||
refreshUserData().then(freshData => {
|
||||
if (freshData) {
|
||||
console.log('Storage usage updated from server');
|
||||
@@ -1781,56 +1715,41 @@ async function checkAuthentication() {
|
||||
// Find and load the user's home folder
|
||||
findUserHomeFolder(userData.username);
|
||||
} else {
|
||||
// If no user data but we have a token, create default user data
|
||||
console.log('No user data but token exists, using default user');
|
||||
const defaultUserData = {
|
||||
id: 'default-user-id',
|
||||
username: 'usuario',
|
||||
email: 'usuario@example.com',
|
||||
storage_quota_bytes: 10737418240, // 10GB default
|
||||
storage_used_bytes: 0
|
||||
};
|
||||
localStorage.setItem(USER_DATA_KEY, JSON.stringify(defaultUserData));
|
||||
|
||||
// Update avatar with default initials
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach(el => el.textContent = 'US');
|
||||
|
||||
// Update storage display with default values
|
||||
updateStorageUsageDisplay(defaultUserData);
|
||||
|
||||
// Find and load default folder
|
||||
app.currentPath = '';
|
||||
ui.updateBreadcrumb('');
|
||||
loadFiles();
|
||||
// No user data but token exists — try to fetch from server
|
||||
console.log('No user data, attempting to fetch from server');
|
||||
try {
|
||||
const freshData = await refreshUserData();
|
||||
if (freshData && freshData.username) {
|
||||
const userInitials = freshData.username.substring(0, 2).toUpperCase();
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach(el => el.textContent = userInitials);
|
||||
updateStorageUsageDisplay(freshData);
|
||||
findUserHomeFolder(freshData.username);
|
||||
} else {
|
||||
// Server didn't return valid user data — token is likely invalid
|
||||
console.warn('Could not retrieve user data, redirecting to login');
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||
localStorage.removeItem(TOKEN_EXPIRY_KEY);
|
||||
localStorage.removeItem(USER_DATA_KEY);
|
||||
window.location.href = '/login?source=invalid_session';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch user data:', err);
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||
localStorage.removeItem(TOKEN_EXPIRY_KEY);
|
||||
localStorage.removeItem(USER_DATA_KEY);
|
||||
window.location.href = '/login?source=session_error';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during authentication check:', error);
|
||||
|
||||
// CRITICAL: On any error, create emergency bypass to break any loops
|
||||
console.log('Creating emergency authentication bypass due to error');
|
||||
localStorage.setItem('oxicloud_token', 'emergency_token');
|
||||
localStorage.setItem('oxicloud_token_expiry',
|
||||
new Date(Date.now() + 86400000 * 30).toISOString()); // 30 days
|
||||
|
||||
const defaultUserData = {
|
||||
id: 'emergency-user-id',
|
||||
username: 'usuario',
|
||||
email: 'usuario@example.com',
|
||||
storage_quota_bytes: 10737418240, // 10GB default
|
||||
storage_used_bytes: 0
|
||||
};
|
||||
localStorage.setItem('oxicloud_user', JSON.stringify(defaultUserData));
|
||||
|
||||
// Update avatar
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach(el => el.textContent = 'US');
|
||||
|
||||
// Update storage display with default values
|
||||
updateStorageUsageDisplay(defaultUserData);
|
||||
|
||||
// Load root files
|
||||
app.currentPath = '';
|
||||
ui.updateBreadcrumb('');
|
||||
loadFiles();
|
||||
// On error, redirect to login cleanly — never create fake tokens
|
||||
localStorage.removeItem('oxicloud_token');
|
||||
localStorage.removeItem('oxicloud_refresh_token');
|
||||
localStorage.removeItem('oxicloud_token_expiry');
|
||||
localStorage.removeItem('oxicloud_user');
|
||||
window.location.href = '/login?source=auth_error';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-139
@@ -513,8 +513,10 @@ let authInitialized = false;
|
||||
const timeSinceCleanup = Date.now() - lastCleanup;
|
||||
|
||||
if (lastCleanup > 0 && timeSinceCleanup < 10000) { // Less than 10 seconds
|
||||
console.warn('Multiple auth problems in short time, enabling direct bypass mode');
|
||||
localStorage.setItem('bypass_auth_mode', 'true');
|
||||
console.warn('Multiple auth problems in short time, clearing auth data');
|
||||
localStorage.removeItem('oxicloud_token');
|
||||
localStorage.removeItem('oxicloud_refresh_token');
|
||||
localStorage.removeItem('oxicloud_token_expiry');
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -577,7 +579,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
// Redirect to main app
|
||||
window.location.href = '/?no_redirect=true';
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -690,8 +692,12 @@ if (isLoginPage && loginForm) {
|
||||
console.log("Login response:", data); // Log the response for debugging
|
||||
|
||||
// Use the correct field names from our API response
|
||||
const token = data.access_token || data.token || "mock_access_token";
|
||||
const refreshToken = data.refresh_token || data.refreshToken || "mock_refresh_token";
|
||||
const token = data.access_token || data.token;
|
||||
const refreshToken = data.refresh_token || data.refreshToken;
|
||||
|
||||
if (!token) {
|
||||
throw new Error('Server did not return an access token');
|
||||
}
|
||||
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);
|
||||
@@ -731,20 +737,13 @@ if (isLoginPage && loginForm) {
|
||||
// Reset redirect counter on successful login
|
||||
sessionStorage.removeItem('redirect_count');
|
||||
|
||||
// Fetch and store user data
|
||||
// Use the user data directly from the response
|
||||
const userData = data.user || {
|
||||
id: 'test-user-id',
|
||||
username: username,
|
||||
email: username + '@example.com',
|
||||
role: 'user',
|
||||
active: true,
|
||||
storage_quota_bytes: 10737418240, // 10GB default
|
||||
storage_used_bytes: 0
|
||||
};
|
||||
|
||||
console.log("Storing user data:", userData);
|
||||
localStorage.setItem(USER_DATA_KEY, JSON.stringify(userData));
|
||||
// Fetch and store user data from the response
|
||||
if (data.user) {
|
||||
console.log("Storing user data from server");
|
||||
localStorage.setItem(USER_DATA_KEY, JSON.stringify(data.user));
|
||||
} else {
|
||||
console.warn("Server did not return user data — will fetch from /me endpoint after redirect");
|
||||
}
|
||||
|
||||
// Redirect to main app
|
||||
redirectToMainApp();
|
||||
@@ -859,25 +858,6 @@ async function login(username, password) {
|
||||
try {
|
||||
console.log(`Attempting to login with username: ${username}`);
|
||||
|
||||
// Special case for test user
|
||||
if (username === 'test' && password === 'test') {
|
||||
console.log('Using test user fallback');
|
||||
// Return a mock response that matches our backend structure
|
||||
return {
|
||||
user: {
|
||||
id: "test-user-id",
|
||||
username: "test",
|
||||
email: "test@example.com",
|
||||
role: "user",
|
||||
active: true
|
||||
},
|
||||
access_token: "mock_access_token",
|
||||
refresh_token: "mock_refresh_token",
|
||||
token_type: "Bearer",
|
||||
expires_in: 3600
|
||||
};
|
||||
}
|
||||
|
||||
// Add better error handling with timeout
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
||||
@@ -928,19 +908,6 @@ async function register(username, email, password, role = 'user') {
|
||||
try {
|
||||
console.log(`Attempting to register user: ${username}`);
|
||||
|
||||
// Special case for test user
|
||||
if (username === 'test') {
|
||||
console.log('Using test user registration fallback');
|
||||
// Return a mock user response
|
||||
return {
|
||||
id: "test-user-id",
|
||||
username: username,
|
||||
email: email,
|
||||
role: role || "user",
|
||||
active: true
|
||||
};
|
||||
}
|
||||
|
||||
const response = await fetch(REGISTER_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -1005,7 +972,6 @@ async function fetchUserData(token) {
|
||||
*/
|
||||
async function refreshAuthToken(refreshToken) {
|
||||
try {
|
||||
console.log("CRITICAL: Token refresh disabled to prevent infinite loop");
|
||||
// Check if we're in a refresh loop
|
||||
const refreshAttempts = parseInt(localStorage.getItem('refresh_attempts') || '0');
|
||||
localStorage.setItem('refresh_attempts', (refreshAttempts + 1).toString());
|
||||
@@ -1021,49 +987,15 @@ async function refreshAuthToken(refreshToken) {
|
||||
throw new Error('Too many refresh attempts, forcing login');
|
||||
}
|
||||
|
||||
// For test users, generate a fake response that will work
|
||||
// This ensures the app works with test accounts
|
||||
const isMockToken = refreshToken === "mock_refresh_token" || refreshToken.includes("mock");
|
||||
|
||||
if (isMockToken) {
|
||||
console.log("Using mock refresh token response");
|
||||
// Create a simulated token with no expiration
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const expiry = timestamp + 86400 * 30; // 30 days
|
||||
|
||||
// Create a basic token with a very long expiry
|
||||
const mockUserData = {
|
||||
id: "default-user-id",
|
||||
username: "usuario",
|
||||
email: "usuario@example.com",
|
||||
role: "user",
|
||||
active: true
|
||||
};
|
||||
|
||||
// Store directly in localStorage to bypass token parsing
|
||||
localStorage.setItem(USER_DATA_KEY, JSON.stringify(mockUserData));
|
||||
localStorage.setItem(TOKEN_KEY, "mock_token_preventing_loops");
|
||||
localStorage.setItem(TOKEN_EXPIRY_KEY, new Date(expiry * 1000).toISOString());
|
||||
|
||||
// Reset counters
|
||||
sessionStorage.removeItem('redirect_count');
|
||||
localStorage.setItem('refresh_attempts', '0');
|
||||
|
||||
return {
|
||||
user: mockUserData,
|
||||
access_token: "mock_token_preventing_loops",
|
||||
refresh_token: "mock_refresh_token_new",
|
||||
token_type: "Bearer",
|
||||
expires_in: 86400 * 30
|
||||
};
|
||||
if (!refreshToken) {
|
||||
throw new Error('No refresh token available');
|
||||
}
|
||||
|
||||
// If it's not a mock token, let's try the normal refresh but with extra safeguards
|
||||
console.log("Attempting to refresh real token with safety limits");
|
||||
console.log("Attempting to refresh token");
|
||||
|
||||
// Extra timeout for safety
|
||||
// Timeout for safety
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 3000); // Reduced to 3 second timeout
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
const response = await fetch(REFRESH_ENDPOINT, {
|
||||
method: 'POST',
|
||||
@@ -1138,73 +1070,36 @@ async function checkFirstRun() {
|
||||
|
||||
/**
|
||||
* Redirect to main application
|
||||
* Complete rewrite with multiple failsafes to prevent redirect loops
|
||||
*/
|
||||
function redirectToMainApp() {
|
||||
console.log('Redirecting to main application with anti-loop measures');
|
||||
console.log('Redirecting to main application');
|
||||
|
||||
try {
|
||||
// Check if we're in bypass mode
|
||||
const bypassMode = localStorage.getItem('bypass_auth_mode') === 'true';
|
||||
|
||||
// Calculate which URL parameter to use
|
||||
let param = 'no_redirect=true';
|
||||
|
||||
// Add strong bypass parameter if in bypass mode
|
||||
if (bypassMode) {
|
||||
param = 'bypass_auth=true';
|
||||
console.log('CRITICAL: Using emergency bypass mode for redirection');
|
||||
}
|
||||
|
||||
// Reset refresh attempts counter on redirection
|
||||
localStorage.setItem('refresh_attempts', '0');
|
||||
sessionStorage.removeItem('redirect_count');
|
||||
|
||||
// Set a token expiry if none exists (to prevent potential loops)
|
||||
// Set a token expiry if none exists
|
||||
const tokenExpiry = localStorage.getItem(TOKEN_EXPIRY_KEY);
|
||||
if (!tokenExpiry) {
|
||||
console.log('Setting default token expiry before redirect');
|
||||
const expiryTime = new Date();
|
||||
expiryTime.setDate(expiryTime.getDate() + 30); // 30 days
|
||||
expiryTime.setDate(expiryTime.getDate() + 30);
|
||||
localStorage.setItem(TOKEN_EXPIRY_KEY, expiryTime.toISOString());
|
||||
}
|
||||
|
||||
// Additional guard: ensure we have at least some form of token
|
||||
// Verify we have a valid token before redirecting
|
||||
const hasToken = localStorage.getItem(TOKEN_KEY);
|
||||
if (!hasToken && !bypassMode) {
|
||||
console.warn('No token found before redirect, creating emergency token');
|
||||
localStorage.setItem(TOKEN_KEY, 'emergency_redirect_token');
|
||||
if (!hasToken) {
|
||||
console.error('No token found, cannot redirect to app');
|
||||
return;
|
||||
}
|
||||
|
||||
// Log that we're about to redirect
|
||||
console.log(`Redirecting to app with param: ${param}`);
|
||||
|
||||
// Use a timeout to prevent any potential race conditions
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// Navigate to the main app with the appropriate parameter
|
||||
window.location.replace(`/?${param}`);
|
||||
} catch (innerError) {
|
||||
console.error('Critical error during redirection:', innerError);
|
||||
// Ultimate fallback - clear everything and go to a special error page
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
window.location.href = '/login?critical=redirect_error';
|
||||
}
|
||||
}, 50);
|
||||
// Navigate to the main app
|
||||
window.location.replace('/');
|
||||
} catch (error) {
|
||||
console.error('Fatal error in redirectToMainApp:', error);
|
||||
// Emergency fallback
|
||||
try {
|
||||
window.location.href = '/login?error=redirect_fatal';
|
||||
} catch (e) {
|
||||
// Nothing more we can do
|
||||
alert('Critical redirect error. Please reload the page and try again.');
|
||||
}
|
||||
console.error('Error during redirect:', error);
|
||||
window.location.href = '/login?error=redirect_failed';
|
||||
}
|
||||
|
||||
// No more redirect checks or token validation
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -424,7 +424,7 @@ const contextMenus = {
|
||||
const folderItem = document.createElement('div');
|
||||
folderItem.className = 'folder-select-item';
|
||||
folderItem.dataset.folderId = folder.id;
|
||||
folderItem.innerHTML = `<i class="fas fa-folder"></i> ${folder.name}`;
|
||||
folderItem.innerHTML = `<i class="fas fa-folder"></i> ${escapeHtml(folder.name)}`;
|
||||
|
||||
folderItem.addEventListener('click', () => {
|
||||
// Deselect all
|
||||
|
||||
@@ -501,7 +501,7 @@ const favorites = {
|
||||
<div class="file-icon folder-icon">
|
||||
<i class="fas fa-folder"></i>
|
||||
</div>
|
||||
<div class="file-name">${folder.name}</div>
|
||||
<div class="file-name">${escapeHtml(folder.name)}</div>
|
||||
<div class="file-info">Folder</div>
|
||||
`;
|
||||
|
||||
@@ -550,7 +550,7 @@ const favorites = {
|
||||
<div class="file-icon folder-icon">
|
||||
<i class="fas fa-folder"></i>
|
||||
</div>
|
||||
<span>${folder.name}</span>
|
||||
<span>${escapeHtml(folder.name)}</span>
|
||||
</div>
|
||||
<div class="type-cell">${window.i18n ? window.i18n.t('files.file_types.folder') : 'Folder'}</div>
|
||||
<div class="size-cell">--</div>
|
||||
@@ -639,7 +639,7 @@ const favorites = {
|
||||
<div class="file-icon ${iconSpecialClass}">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<div class="file-name">${file.name}</div>
|
||||
<div class="file-name">${escapeHtml(file.name)}</div>
|
||||
<div class="file-info">Modified ${formattedDate.split(' ')[0]}</div>
|
||||
`;
|
||||
|
||||
@@ -685,9 +685,9 @@ const favorites = {
|
||||
<div class="file-icon ${iconSpecialClass}">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<span>${file.name}</span>
|
||||
<span>${escapeHtml(file.name)}</span>
|
||||
</div>
|
||||
<div class="type-cell">${typeLabel}</div>
|
||||
<div class="type-cell">${escapeHtml(typeLabel)}</div>
|
||||
<div class="size-cell">${fileSize}</div>
|
||||
<div class="date-cell">${formattedDate}</div>
|
||||
`;
|
||||
|
||||
@@ -220,7 +220,7 @@ class FileRenderer {
|
||||
<div class="file-icon folder-icon">
|
||||
<i class="fas fa-folder"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
<div class="file-name">${window.escapeHtml(item.name)}</div>
|
||||
`;
|
||||
|
||||
// Make draggable
|
||||
@@ -274,7 +274,7 @@ class FileRenderer {
|
||||
<div class="file-icon">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
<div class="file-name">${window.escapeHtml(item.name)}</div>
|
||||
`;
|
||||
|
||||
// Make draggable
|
||||
@@ -343,7 +343,7 @@ class FileRenderer {
|
||||
<div class="file-icon folder-icon">
|
||||
<i class="fas fa-folder"></i>
|
||||
</div>
|
||||
<span>${item.name}</span>
|
||||
<span>${window.escapeHtml(item.name)}</span>
|
||||
</div>
|
||||
<div>${this.i18n.t('files.file_types.folder')}</div>
|
||||
<div>--</div>
|
||||
@@ -416,7 +416,7 @@ class FileRenderer {
|
||||
<div class="file-icon">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<span>${item.name}</span>
|
||||
<span>${window.escapeHtml(item.name)}</span>
|
||||
</div>
|
||||
<div>${typeLabel}</div>
|
||||
<div>${fileSize}</div>
|
||||
|
||||
+3
-3
@@ -246,7 +246,7 @@ const recent = {
|
||||
<div class="file-icon ${iconSpecialClass}">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<div class="file-name">${file.name}</div>
|
||||
<div class="file-name">${escapeHtml(file.name)}</div>
|
||||
<div class="file-info">Accessed ${formattedDate.split(' ')[0]}</div>
|
||||
`;
|
||||
|
||||
@@ -297,9 +297,9 @@ const recent = {
|
||||
<div class="file-icon ${iconSpecialClass}">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<span>${file.name}</span>
|
||||
<span>${escapeHtml(file.name)}</span>
|
||||
</div>
|
||||
<div class="type-cell">${typeLabel}</div>
|
||||
<div class="type-cell">${escapeHtml(typeLabel)}</div>
|
||||
<div class="size-cell">${fileSize}</div>
|
||||
<div class="date-cell">${formattedDate}</div>
|
||||
`;
|
||||
|
||||
+4
-4
@@ -677,7 +677,7 @@ const ui = {
|
||||
<div class="file-icon folder-icon">
|
||||
<i class="fas fa-folder"></i>
|
||||
</div>
|
||||
<div class="file-name">${folder.name}</div>
|
||||
<div class="file-name">${escapeHtml(folder.name)}</div>
|
||||
<div class="file-info">Folder</div>
|
||||
`;
|
||||
|
||||
@@ -818,7 +818,7 @@ const ui = {
|
||||
<div class="file-icon folder-icon">
|
||||
<i class="fas fa-folder"></i>
|
||||
</div>
|
||||
<span>${folder.name}</span>
|
||||
<span>${escapeHtml(folder.name)}</span>
|
||||
${isFolderFav ? '<i class="fas fa-star favorite-star-inline"></i>' : ''}
|
||||
</div>
|
||||
<div class="type-cell">${window.i18n ? window.i18n.t('files.file_types.folder') : 'Folder'}</div>
|
||||
@@ -952,7 +952,7 @@ const ui = {
|
||||
<div class="file-icon">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<div class="file-name">${file.name}</div>
|
||||
<div class="file-name">${escapeHtml(file.name)}</div>
|
||||
<div class="file-info">Modified ${formattedDate.split(' ')[0]}</div>
|
||||
`;
|
||||
|
||||
@@ -1056,7 +1056,7 @@ const ui = {
|
||||
<div class="file-icon ${iconSpecialClass}">
|
||||
<i class="${iconClass}"></i>
|
||||
</div>
|
||||
<span>${file.name}</span>
|
||||
<span>${escapeHtml(file.name)}</span>
|
||||
${isFileFav ? '<i class="fas fa-star favorite-star-inline"></i>' : ''}
|
||||
</div>
|
||||
<div class="type-cell">${typeLabel}</div>
|
||||
|
||||
Reference in New Issue
Block a user