253 lines
8.4 KiB
JavaScript
253 lines
8.4 KiB
JavaScript
import { sendRequest } from './networkUtils.js';
|
|
import { toggleVisibility, toggleAllCheckboxes, updateFileActionButtons, showToast } from './domUtils.js';
|
|
import { initUpload } from './upload.js';
|
|
import { initAuth, fetchWithCsrf, checkAuthentication, loadAdminConfigFunc } from './auth.js';
|
|
import { loadFolderTree } from './folderManager.js';
|
|
import { setupTrashRestoreDelete } from './trashRestoreDelete.js';
|
|
import { initDragAndDrop, loadSidebarOrder, loadHeaderOrder } from './dragAndDrop.js';
|
|
import { initTagSearch, openTagModal, filterFilesByTag } from './fileTags.js';
|
|
import { displayFilePreview } from './filePreview.js';
|
|
import { loadFileList } from './fileListView.js';
|
|
import { initFileActions, renameFile, openDownloadModal, confirmSingleDownload } from './fileActions.js';
|
|
import { editFile, saveFile } from './fileEditor.js';
|
|
import { t, applyTranslations, setLocale } from './i18n.js';
|
|
|
|
/* =========================
|
|
CSRF HOTFIX UTILITIES
|
|
========================= */
|
|
const _nativeFetch = window.fetch; // keep the real fetch
|
|
|
|
function setCsrfToken(token) {
|
|
if (!token) return;
|
|
window.csrfToken = token;
|
|
localStorage.setItem('csrf', token);
|
|
|
|
// meta tag for easy access in other places
|
|
let meta = document.querySelector('meta[name="csrf-token"]');
|
|
if (!meta) {
|
|
meta = document.createElement('meta');
|
|
meta.name = 'csrf-token';
|
|
document.head.appendChild(meta);
|
|
}
|
|
meta.content = token;
|
|
}
|
|
function getCsrfToken() {
|
|
return window.csrfToken || localStorage.getItem('csrf') || '';
|
|
}
|
|
|
|
// Seed CSRF from storage ASAP (before any requests)
|
|
setCsrfToken(getCsrfToken());
|
|
|
|
// Wrap the existing fetchWithCsrf so we also capture rotated tokens from headers.
|
|
async function fetchWithCsrfAndRefresh(input, init = {}) {
|
|
const res = await fetchWithCsrf(input, init);
|
|
try {
|
|
const rotated = res.headers?.get('X-CSRF-Token');
|
|
if (rotated) setCsrfToken(rotated);
|
|
} catch { /* ignore */ }
|
|
return res;
|
|
}
|
|
|
|
// Replace global fetch with the wrapped version so *all* callers benefit.
|
|
window.fetch = fetchWithCsrfAndRefresh;
|
|
|
|
/* =========================
|
|
APP INIT
|
|
========================= */
|
|
|
|
export function initializeApp() {
|
|
const saved = parseInt(localStorage.getItem('rowHeight') || '48', 10);
|
|
document.documentElement.style.setProperty('--file-row-height', saved + 'px');
|
|
|
|
window.currentFolder = "root";
|
|
const stored = localStorage.getItem('showFoldersInList');
|
|
window.showFoldersInList = stored === null ? true : stored === 'true';
|
|
|
|
initTagSearch();
|
|
loadFileList(window.currentFolder);
|
|
|
|
const fileListArea = document.getElementById('fileListContainer');
|
|
const uploadArea = document.getElementById('uploadDropArea');
|
|
if (fileListArea && uploadArea) {
|
|
fileListArea.addEventListener('dragover', e => {
|
|
e.preventDefault();
|
|
fileListArea.classList.add('drop-hover');
|
|
});
|
|
fileListArea.addEventListener('dragleave', () => {
|
|
fileListArea.classList.remove('drop-hover');
|
|
});
|
|
fileListArea.addEventListener('drop', e => {
|
|
e.preventDefault();
|
|
fileListArea.classList.remove('drop-hover');
|
|
uploadArea.dispatchEvent(new DragEvent('drop', {
|
|
dataTransfer: e.dataTransfer,
|
|
bubbles: true,
|
|
cancelable: true
|
|
}));
|
|
});
|
|
}
|
|
|
|
initDragAndDrop();
|
|
loadSidebarOrder();
|
|
loadHeaderOrder();
|
|
initFileActions();
|
|
initUpload();
|
|
loadFolderTree();
|
|
setupTrashRestoreDelete();
|
|
loadAdminConfigFunc();
|
|
|
|
const helpBtn = document.getElementById("folderHelpBtn");
|
|
const helpTooltip = document.getElementById("folderHelpTooltip");
|
|
if (helpBtn && helpTooltip) {
|
|
helpBtn.addEventListener("click", () => {
|
|
helpTooltip.style.display =
|
|
helpTooltip.style.display === "block" ? "none" : "block";
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bootstrap/refresh CSRF from the server.
|
|
* Uses the *native* fetch to avoid any wrapper loops and to work even if we don't
|
|
* yet have a token. Also accepts a rotated token from the response header.
|
|
*/
|
|
export function loadCsrfToken() {
|
|
return _nativeFetch('/api/auth/token.php', { method: 'GET', credentials: 'include' })
|
|
.then(async res => {
|
|
// header-based rotation
|
|
const hdr = res.headers.get('X-CSRF-Token');
|
|
if (hdr) setCsrfToken(hdr);
|
|
|
|
// body (if provided)
|
|
let body = {};
|
|
try { body = await res.json(); } catch { /* token endpoint may return empty */ }
|
|
|
|
const token = body.csrf_token || getCsrfToken();
|
|
setCsrfToken(token);
|
|
|
|
// share-url meta should reflect the actual origin
|
|
const actualShare = window.location.origin;
|
|
let shareMeta = document.querySelector('meta[name="share-url"]');
|
|
if (!shareMeta) {
|
|
shareMeta = document.createElement('meta');
|
|
shareMeta.name = 'share-url';
|
|
document.head.appendChild(shareMeta);
|
|
}
|
|
shareMeta.content = actualShare;
|
|
|
|
return { csrf_token: token, share_url: actualShare };
|
|
});
|
|
}
|
|
|
|
// 1) Immediately clear “?logout=1” flag
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.get('logout') === '1') {
|
|
localStorage.removeItem("username");
|
|
localStorage.removeItem("userTOTPEnabled");
|
|
}
|
|
|
|
export function triggerLogout() {
|
|
_nativeFetch("/api/auth/logout.php", {
|
|
method: "POST",
|
|
credentials: "include",
|
|
headers: { "X-CSRF-Token": getCsrfToken() }
|
|
})
|
|
.then(() => window.location.reload(true))
|
|
.catch(() => { });
|
|
}
|
|
|
|
// Expose functions for inline handlers.
|
|
window.sendRequest = sendRequest;
|
|
window.toggleVisibility = toggleVisibility;
|
|
window.toggleAllCheckboxes = toggleAllCheckboxes;
|
|
window.editFile = editFile;
|
|
window.saveFile = saveFile;
|
|
window.renameFile = renameFile;
|
|
window.confirmSingleDownload = confirmSingleDownload;
|
|
window.openDownloadModal = openDownloadModal;
|
|
|
|
// Global variable for the current folder.
|
|
window.currentFolder = "root";
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
loadAdminConfigFunc();
|
|
|
|
// i18n
|
|
const savedLanguage = localStorage.getItem("language") || "en";
|
|
setLocale(savedLanguage);
|
|
applyTranslations();
|
|
|
|
// 1) Get/refresh CSRF first
|
|
loadCsrfToken()
|
|
.then(() => {
|
|
// 2) Auth boot
|
|
initAuth();
|
|
|
|
// 3) If authenticated, start app
|
|
checkAuthentication().then(authenticated => {
|
|
if (authenticated) {
|
|
const overlay = document.getElementById('loadingOverlay');
|
|
if (overlay) overlay.remove();
|
|
initializeApp();
|
|
}
|
|
});
|
|
|
|
// --- Dark Mode Persistence ---
|
|
const darkModeToggle = document.getElementById("darkModeToggle");
|
|
const darkModeIcon = document.getElementById("darkModeIcon");
|
|
|
|
if (darkModeToggle && darkModeIcon) {
|
|
let stored = localStorage.getItem("darkMode");
|
|
const hasStored = stored !== null;
|
|
|
|
const isDark = hasStored
|
|
? (stored === "true")
|
|
: (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches);
|
|
|
|
document.body.classList.toggle("dark-mode", isDark);
|
|
darkModeToggle.classList.toggle("active", isDark);
|
|
|
|
function updateIcon() {
|
|
const dark = document.body.classList.contains("dark-mode");
|
|
darkModeIcon.textContent = dark ? "light_mode" : "dark_mode";
|
|
darkModeToggle.setAttribute("aria-label", dark ? t("light_mode") : t("dark_mode"));
|
|
darkModeToggle.setAttribute("title", dark ? t("switch_to_light_mode") : t("switch_to_dark_mode"));
|
|
}
|
|
updateIcon();
|
|
|
|
darkModeToggle.addEventListener("click", () => {
|
|
const nowDark = document.body.classList.toggle("dark-mode");
|
|
localStorage.setItem("darkMode", nowDark ? "true" : "false");
|
|
updateIcon();
|
|
});
|
|
|
|
if (!hasStored && window.matchMedia) {
|
|
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", e => {
|
|
document.body.classList.toggle("dark-mode", e.matches);
|
|
updateIcon();
|
|
});
|
|
}
|
|
}
|
|
// --- End Dark Mode Persistence ---
|
|
|
|
const message = sessionStorage.getItem("welcomeMessage");
|
|
if (message) {
|
|
showToast(message);
|
|
sessionStorage.removeItem("welcomeMessage");
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error("Initialization halted due to CSRF token load failure.", error);
|
|
});
|
|
|
|
// --- Auto-scroll During Drag ---
|
|
const SCROLL_THRESHOLD = 50;
|
|
const SCROLL_SPEED = 20;
|
|
document.addEventListener("dragover", function (e) {
|
|
if (e.clientY < SCROLL_THRESHOLD) {
|
|
window.scrollBy(0, -SCROLL_SPEED);
|
|
} else if (e.clientY > window.innerHeight - SCROLL_THRESHOLD) {
|
|
window.scrollBy(0, SCROLL_SPEED);
|
|
}
|
|
});
|
|
}); |