Files
FileRise/public/index.html
T
Ryan e862ed956b release(v3.0.1): archive create/extract upgrades (7z + RAR via unar) + login focus fix (closes #82)
- add 7z archive format option for multi-file downloads (worker + download streaming)
- expand extraction to support ZIP + 7z formats via 7z, with RAR preferring unar when available
- harden archive extraction against traversal, symlinks, zip-bombs, and empty/escaped outputs
- improve archive job robustness (stale job cleanup, clearer queued/worker errors, correct MIME/filenames)
- UI: archive format selector + name normalization, better “Extract Archive” handling, i18n updates
- fix login screen focus (auto-focus username when login prompt shows)

Closes #82
2026-01-14 21:06:06 -05:00

705 lines
37 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>FileRise</title>
<meta name="theme-color" content="#0b5ed7">
<script>(function(){try{var s=localStorage.getItem('darkMode');var isDark=(s===null)?(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches):(s==='1'||s==='true');var root=document.documentElement;root.setAttribute('data-theme',isDark?'dark':'light');root.classList.toggle('dark-mode',isDark);var bg=isDark?'#121212':'#ffffff';root.style.backgroundColor=bg;root.style.colorScheme=isDark?'dark':'light';root.style.setProperty('--pre-bg',bg);var m=document.querySelector('meta[name="theme-color"]');if(m)m.setAttribute('content',bg);}catch(e){}})();</script>
<style id="pretheme-css">
html,body,#loadingOverlay{background:var(--pre-bg,#ffffff) !important;}
</style>
<!-- Favicons (ordered: SVG -> PNGs -> ICO) -->
<link rel="icon" href="assets/logo.svg?v={{APP_QVER}}" type="image/svg+xml" sizes="any">
<link rel="icon" href="assets/logo.png?v={{APP_QVER}}" type="image/png" sizes="512x512">
<link rel="icon" href="assets/logo-32.png?v={{APP_QVER}}" type="image/png" sizes="32x32">
<link rel="icon" href="assets/logo-16.png?v={{APP_QVER}}" type="image/png" sizes="16x16">
<link rel="shortcut icon" href="assets/favicon.ico?v={{APP_QVER}}">
<meta name="description" content="FileRise is a fast, self-hosted file manager with granular per-folder ACLs, drag-and-drop folder moves, WebDAV, tagging, and a clean UI.">
<meta name="csrf-token" content=""><meta name="share-url" content=""><meta name="color-scheme" content="light dark">
<link rel="manifest" href="manifest.webmanifest?v={{APP_QVER}}">
<link rel="apple-touch-icon" href="assets/icons/icon-192.png?v={{APP_QVER}}">
<!-- Critical CSS -->
<link rel="stylesheet" href="vendor/bootstrap/4.6.2/bootstrap.min.css?v={{APP_QVER}}">
<link rel="stylesheet" href="css/styles.css?v={{APP_QVER}}">
<link rel="stylesheet" href="css/vendor/roboto.css?v={{APP_QVER}}">
<!-- Fonts -->
<link rel="preload" as="font" href="fonts/roboto/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3KUBHMdazTgWw.woff2?v={{APP_QVER}}" type="font/woff2" crossorigin>
<link rel="preload" as="font" href="fonts/roboto/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBHMdazQ.woff2?v={{APP_QVER}}" type="font/woff2" crossorigin>
<!-- Vendor & version (deferred) -->
<script src="vendor/dompurify/3.3.1/purify.min.js?v={{APP_QVER}}" defer></script>
<script src="js/version.js?v={{APP_QVER}}" defer></script>
<!-- App entry -->
<link rel="modulepreload" href="js/main.js?v={{APP_QVER}}"><script type="module" src="js/main.js?v={{APP_QVER}}"></script>
</head>
<body>
<div id="appRoot" style="visibility:hidden">
<header class="header-container">
<div class="header-left">
<a href="index.html">
<div class="header-logo">
<img
src="assets/logo.svg?v={{APP_QVER}}"
alt="FileRise"
class="logo"
width="50"
height="50"
decoding="async"
fetchpriority="high"
/>
</div>
</a>
</div>
<div class="header-title">
<h1>FileRise</h1>
</div>
<div class="header-right">
<div class="header-buttons-wrapper" style="display: flex; align-items: center;">
<div id="headerDropArea" class="header-drop-zone"></div>
<div class="header-buttons">
<button id="changePasswordBtn" data-i18n-title="change_password" style="display: none;">
<i class="material-icons">vpn_key</i>
</button>
<div id="restoreFilesModal" class="modal centered-modal" style="display: none;">
<div class="modal-content restore-modal">
<div class="restore-modal__header">
<div id="restoreModalIcon" class="restore-modal__icon" aria-hidden="true"></div>
<div class="restore-modal__titles">
<h4 data-i18n-key="recycle_bin">Recycle Bin</h4>
<p data-i18n-key="restore_delete_hint">Restore or permanently delete trashed items.</p>
<p id="restoreModalSourceHint" class="restore-modal__source" hidden></p>
</div>
<button id="closeRestoreModal" class="restore-close-btn" aria-label="Close">
<span class="material-icons">close</span>
</button>
</div>
<div id="restoreFilesList" class="restore-list" role="listbox" aria-label="Trash items">
</div>
<div class="restore-modal__actions">
<button id="restoreSelectedBtn" class="btn restore-btn restore-btn-primary">
<span class="material-icons">history</span>
<span data-i18n-key="restore_selected">Restore Selected</span>
</button>
<button id="restoreAllBtn" class="btn restore-btn">
<span class="material-icons">undo</span>
<span data-i18n-key="restore_all">Restore All</span>
</button>
<button id="deleteTrashSelectedBtn" class="btn restore-btn restore-btn-warn">
<span class="material-icons">delete_sweep</span>
<span data-i18n-key="delete_selected_trash">Delete Selected</span>
</button>
<button id="deleteAllBtn" class="btn restore-btn restore-btn-danger">
<span class="material-icons">delete_forever</span>
<span data-i18n-key="delete_all">Delete All</span>
</button>
</div>
</div>
</div>
<button id="addUserBtn" data-i18n-title="add_user" style="display: none;">
<i class="material-icons">person_add</i>
</button>
<button id="removeUserBtn" data-i18n-title="remove_user" style="display: none;">
<i class="material-icons">person_remove</i>
</button>
<button id="darkModeToggle" class="btn-icon" aria-label="Toggle dark mode" hidden>
<span class="material-icons" id="darkModeIcon">
dark_mode
</span>
</button>
</div>
</div>
</div>
</header>
<div id="loadingOverlay"></div>
<!-- Custom Toast Container -->
<div id="customToast"></div>
<div id="hiddenCardsContainer" style="display:none;"></div>
<div id="appZoomShell">
<main id="main" hidden>
<div class="row mt-4" id="loginForm">
<div class="col-12">
<div id="loginBox" class="login-box">
<div id="fr-login-tip" class="alert alert-info login-hint" role="status" aria-live="polite" style="display:none;"></div>
<form id="authForm" method="post">
<div class="form-group">
<label for="loginUsername" data-i18n-key="user">User:</label>
<input type="text" class="form-control" id="loginUsername" name="username" required autofocus />
</div>
<div class="form-group">
<label for="loginPassword" data-i18n-key="password">Password:</label>
<input type="password" class="form-control" id="loginPassword" name="password" required />
</div>
<button type="submit" class="btn btn-primary btn-block btn-login" data-i18n-key="login" data-default>Login</button>
<div class="form-group remember-me-container">
<input type="checkbox" id="rememberMeCheckbox" name="remember_me" />
<label for="rememberMeCheckbox" data-i18n-key="remember_me">Remember me</label>
</div>
</form>
<!-- OIDC Login Option -->
<div class="text-center mt-3">
<button id="oidcLoginBtn" class="btn btn-secondary" data-i18n-key="login_oidc">Login with OIDC</button>
</div>
<!-- Basic HTTP Login Option -->
<div class="text-center mt-3">
<a href="api/auth/login_basic.php" class="btn btn-secondary" data-i18n-key="basic_http_login">Use Basic
HTTP
Login</a>
</div>
<div>
</div>
</div>
</main>
<!-- Main Wrapper: Hidden by default; remove "display: none;" after login -->
<div class="main-wrapper" hidden>
<!-- Sidebar Drop Zone: Hidden until you drag a card (display controlled by JS) -->
<div id="sidebarDropArea" class="drop-target-sidebar"></div>
<!-- Main Column -->
<div id="mainColumn" class="main-column">
<div class="container-fluid">
<!-- Main Operations: Upload and Folder Management -->
<div id="mainOperations">
<div class="container" style="max-width: 1400px; margin: 0 auto;">
<!-- Top Zone: Two columns (60% and 40%) -->
<div id="uploadFolderRow" class="row">
<!-- Left Column (60% for Upload Card) -->
<div id="leftCol" class="col-md-7" style="display: flex; justify-content: center;">
<div id="uploadCard" class="card" style="width: 100%;">
<div class="card-header" data-i18n-key="upload_header">Upload Files/Folders</div>
<div class="card-body d-flex flex-column">
<form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column">
<div class="form-group flex-grow-1" style="margin-bottom: 0rem;">
<div id="uploadDropArea"
style="border:2px dashed #ccc; padding:20px; cursor:pointer; display:flex; flex-direction:column; justify-content:center; align-items:center; position:relative;">
<span data-i18n-key="upload_instruction">Drop files/folders here or click 'Choose
Files'</span>
<br />
<input type="file" id="file" name="file[]" class="form-control-file" multiple
style="opacity:0; position:absolute; width:1px; height:1px;" />
<button type="button" id="customChooseBtn" data-i18n-key="choose_files">Choose Files</button>
</div>
</div>
<button type="submit" id="uploadBtn" class="btn btn-primary mx-auto"
data-i18n-key="upload">Upload</button>
<div id="uploadProgressContainer"></div>
</form>
</div>
</div>
</div>
<!-- Right Column (40% for Folder Management Card) -->
<div id="rightCol" class="col-md-5" style="display: flex; justify-content: center;">
<div id="folderManagementCard" class="card" style="width: 100%; position: relative;">
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
<span data-i18n-key="folder_navigation">Folder Navigation &amp; Management</span>
</div>
<div class="card-body custom-folder-card-body">
<div class="form-group d-flex align-items-top" style="padding-top:0; margin-bottom:0; flex-direction:column;">
<div id="sourceSelectorWrap" class="source-selector" hidden>
<label for="sourceSelector" data-i18n-key="storage_source">Source</label>
<select id="sourceSelector" class="form-control" aria-label="Source"></select>
</div>
<div id="folderTreeContainer"></div>
</div>
<div class="folder-actions">
<button id="createFolderBtn" class="btn btn-primary" data-i18n-title="create_folder">
<i class="material-icons">create_new_folder</i>
</button>
<div id="createFolderModal" class="modal">
<div class="modal-content">
<h4 data-i18n-key="create_folder_title">Create Folder</h4>
<input type="text" id="newFolderName" class="form-control"
data-i18n-placeholder="enter_folder_name" placeholder="Enter folder name"
style="margin-top:10px;" />
<div style="margin-top:15px; text-align:right;">
<button id="cancelCreateFolder" class="btn btn-secondary"
data-i18n-key="cancel">Cancel</button>
<button id="submitCreateFolder" class="btn btn-primary"
data-i18n-key="create">Create</button>
</div>
</div>
</div>
<button id="moveFolderBtn" class="btn btn-warning ml-2" data-i18n-title="move_folder">
<i class="material-icons">drive_file_move</i>
</button>
<!-- MOVE FOLDER MODAL (place near your other folder modals) -->
<div id="moveFolderModal" class="modal" style="display:none;">
<div class="modal-content">
<h4 data-i18n-key="move_folder_title">Move Folder</h4>
<p data-i18n-key="move_folder_message">Select a destination folder to move the current folder
into:</p>
<div class="modal-source-row" id="moveFolderSourceRow" style="display:none;">
<label for="moveFolderTargetSource" data-i18n-key="storage_source">Source</label>
<select id="moveFolderTargetSource" class="form-control modal-input"></select>
</div>
<select id="moveFolderTarget" class="form-control modal-input"></select>
<div class="modal-footer" style="margin-top:15px; text-align:right;">
<button id="cancelMoveFolder" class="btn btn-secondary"
data-i18n-key="cancel">Cancel</button>
<button id="confirmMoveFolder" class="btn btn-primary" data-i18n-key="move" data-default>Move</button>
</div>
</div>
</div>
<button id="renameFolderBtn" class="btn btn-warning ml-2" data-i18n-title="rename_folder">
<i class="material-icons">drive_file_rename_outline</i>
</button>
<div id="renameFolderModal" class="modal">
<div class="modal-content">
<h4 data-i18n-key="rename_folder_title">Rename Folder</h4>
<input type="text" id="newRenameFolderName" class="form-control"
data-i18n-placeholder="rename_folder_placeholder" placeholder="Enter new folder name"
style="margin-top:10px;" />
<div style="margin-top:15px; text-align:right;">
<button id="cancelRenameFolder" class="btn btn-secondary"
data-i18n-key="cancel">Cancel</button>
<button id="submitRenameFolder" class="btn btn-primary"
data-i18n-key="rename" data-default>Rename</button>
</div>
</div>
</div>
<button id="colorFolderBtn" class="btn btn-color-folder ml-2" data-i18n-title="color_folder" title="Color folder">
<i class="material-icons">palette</i>
</button>
<button id="shareFolderBtn" class="btn btn-secondary ml-2" data-i18n-title="share_folder">
<i class="material-icons">share</i>
</button>
<button id="deleteFolderBtn" class="btn btn-danger ml-2" data-i18n-title="delete_folder">
<i class="material-icons">delete</i>
</button>
<div id="deleteFolderModal" class="modal">
<div class="modal-content">
<h4 data-i18n-key="delete_folder_title">Delete Folder</h4>
<p id="deleteFolderMessage" data-i18n-key="delete_folder_message">Are you sure you want to
delete this folder?</p>
<div style="margin-top:15px; text-align:right;">
<button id="cancelDeleteFolder" class="btn btn-secondary"
data-i18n-key="cancel">Cancel</button>
<button id="confirmDeleteFolder" class="btn btn-danger"
data-i18n-key="delete" data-default>Delete</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div> <!-- end uploadFolderRow -->
</div> <!-- end container -->
</div> <!-- end mainOperations -->
<!-- File List Section -->
<div id="fileListShell" class="file-list-shell">
<div id="fileListContainer" class="file-list-pane primary-pane" style="display: none;">
<h2 id="fileListTitle" data-i18n-key="file_list_title">Files in (Root)</h2>
<div id="fileListActions" class="file-list-actions">
<div class="file-actions-strip collapsed" aria-label="File actions" id="fileActionsBar">
<div class="action-group primary-actions" id="createDropdown" style="position:relative; display:inline-flex;">
<button id="createBtn" class="btn action-btn" type="button" aria-haspopup="true" aria-expanded="false" title="Create">
<span class="material-icons" aria-hidden="true">add</span>
<span class="btn-label">New</span>
<span class="material-icons dropdown-caret" aria-hidden="true">arrow_drop_down</span>
</button>
<ul id="createMenu" class="dropdown-menu" style="display:none; position:absolute; top:100%; left:0; margin:4px 0 0; padding:0; list-style:none; background:#fff; border:1px solid #ccc; box-shadow:0 2px 6px rgba(0,0,0,0.2); z-index:10010; min-width:160px;">
<li id="createFileOption" class="dropdown-item" style="padding:8px 12px; cursor:pointer;">
<span data-i18n-key="create_file">Create file</span>
</li>
<li id="createFolderOption" class="dropdown-item" style="padding:8px 12px; cursor:pointer;">
<span data-i18n-key="create_folder">Create folder</span>
</li>
<li id="uploadOption" class="dropdown-item" style="padding:8px 12px; cursor:pointer;">
<span data-i18n-key="upload">Upload file(s)</span>
</li>
</ul>
</div>
<div class="action-separator secondary-separator" aria-hidden="true"></div>
<div class="action-group secondary-actions">
<button id="downloadZipBtn" class="btn action-btn icon-only" disabled title="Download archive" data-i18n-title="download_zip">
<span class="material-icons" aria-hidden="true">file_download</span>
</button>
<button id="copySelectedBtn" class="btn action-btn icon-only" disabled title="Copy" data-i18n-title="copy_files">
<span class="material-icons" aria-hidden="true">content_copy</span>
</button>
<button id="moveSelectedBtn" class="btn action-btn icon-only" disabled title="Move" data-i18n-title="move_files">
<span class="material-icons" aria-hidden="true">drive_file_move</span>
</button>
<button id="renameSelectedBtn" class="btn action-btn icon-only" disabled title="Rename">
<span class="material-icons" aria-hidden="true">drive_file_rename_outline</span>
</button>
<button id="shareSelectedBtn" class="btn action-btn icon-only" disabled title="Share">
<span class="material-icons" aria-hidden="true">share</span>
</button>
<button id="deleteSelectedBtn" class="btn action-btn icon-only" disabled title="Delete" data-i18n-title="delete_files">
<span class="material-icons" aria-hidden="true">delete</span>
</button>
<button id="extractZipBtn" class="btn action-btn icon-only" style="display: none;" disabled title="Extract archive" data-i18n-title="extract_zip_button">
<span class="material-icons" aria-hidden="true">unarchive</span>
</button>
<button id="toolbarMenuBtn" class="btn action-btn icon-only" title="More actions">
<span class="material-icons" aria-hidden="true">more_horiz</span>
</button>
</div>
<div class="action-group folder-inline-actions" id="folderActionsInline">
<button id="folderMoveInlineBtn" class="btn action-btn icon-only" title="Move folder" data-i18n-title="move_folder">
<span class="material-icons" aria-hidden="true">drive_file_move</span>
</button>
<button id="folderRenameInlineBtn" class="btn action-btn icon-only" title="Rename folder" data-i18n-title="rename_folder">
<span class="material-icons" aria-hidden="true">drive_file_rename_outline</span>
</button>
<button id="folderColorInlineBtn" class="btn action-btn icon-only" title="Color folder" data-i18n-title="color_folder">
<span class="material-icons" aria-hidden="true">palette</span>
</button>
<button id="folderEncryptInlineBtn" class="btn action-btn icon-only" title="Encrypt folder">
<span class="material-icons" aria-hidden="true">lock</span>
</button>
<button id="folderDecryptInlineBtn" class="btn action-btn icon-only" title="Decrypt folder">
<span class="material-icons" aria-hidden="true">lock_open</span>
</button>
<button id="folderShareInlineBtn" class="btn action-btn icon-only" title="Share folder" data-i18n-title="folder_share">
<span class="material-icons" aria-hidden="true">share</span>
</button>
<button id="folderDeleteInlineBtn" class="btn action-btn icon-only" title="Delete folder" data-i18n-title="delete_folder">
<span class="material-icons" aria-hidden="true">delete</span>
</button>
</div>
<div class="action-separator" aria-hidden="true"></div>
<div class="action-group view-actions">
<button id="toggleViewBtn" class="btn action-btn icon-only btn-light" title="Switch view">
<i class="material-icons" aria-hidden="true">view_module</i>
</button>
<button id="viewOptionsBtn" class="btn action-btn icon-only" title="View options">
<span class="material-icons" aria-hidden="true">tune</span>
</button>
</div>
</div>
<div id="deleteFilesModal" class="modal">
<div class="modal-content">
<h4 data-i18n-key="delete_selected_files_title">Delete Selected Files</h4>
<p id="deleteFilesMessage" data-i18n-key="delete_files_message">Are you sure you want to delete the
selected files?</p>
<div class="modal-footer">
<button id="cancelDeleteFiles" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
<button id="confirmDeleteFiles" class="btn btn-danger" data-i18n-key="delete" data-default>Delete</button>
</div>
</div>
</div>
<div id="copyFilesModal" class="modal">
<div class="modal-content">
<h4 data-i18n-key="copy_files_title">Copy Selected Files</h4>
<p id="copyFilesMessage" data-i18n-key="copy_files_message">Select a target folder for copying the
selected files:</p>
<div class="modal-source-row" id="copyFilesSourceRow" style="display:none;">
<label for="copyTargetSource" data-i18n-key="storage_source">Source</label>
<select id="copyTargetSource" class="form-control modal-input"></select>
</div>
<select id="copyTargetFolder" class="form-control modal-input"></select>
<div class="modal-footer">
<button id="cancelCopyFiles" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
<button id="confirmCopyFiles" class="btn btn-primary" data-i18n-key="copy" data-default>Copy</button>
</div>
</div>
</div>
<div id="moveFilesModal" class="modal">
<div class="modal-content">
<h4 data-i18n-key="move_files_title">Move Selected Files</h4>
<p id="moveFilesMessage" data-i18n-key="move_files_message">Select a target folder for moving the
selected files:</p>
<div class="modal-source-row" id="moveFilesSourceRow" style="display:none;">
<label for="moveTargetSource" data-i18n-key="storage_source">Source</label>
<select id="moveTargetSource" class="form-control modal-input"></select>
</div>
<select id="moveTargetFolder" class="form-control modal-input"></select>
<div class="modal-footer">
<button id="cancelMoveFiles" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
<button id="confirmMoveFiles" class="btn btn-primary" data-i18n-key="move" data-default>Move</button>
</div>
</div>
</div>
<!-- Create File Modal -->
<div id="createFileModal" class="modal" style="display:none;">
<div class="modal-content">
<h4 data-i18n-key="create_new_file">Create New File</h4>
<input type="text" id="createFileNameInput" class="form-control" placeholder="Enter filename…"
data-i18n-placeholder="newfile_placeholder" />
<div class="modal-footer" style="margin-top:1rem; text-align:right;">
<button id="cancelCreateFile" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
<button id="confirmCreateFile" class="btn btn-primary" data-i18n-key="create" data-default>Create</button>
</div>
</div>
</div>
<div id="downloadZipModal" class="modal" style="display:none;">
<div class="modal-content">
<h4 data-i18n-key="download_zip_title">Download Selected Files as Archive</h4>
<label for="archiveFormatSelect" data-i18n-key="download_archive_format" style="display:block; margin-top:10px;">Archive format</label>
<select id="archiveFormatSelect" class="form-control">
<option value="zip">ZIP (.zip)</option>
<option value="7z">7-Zip (.7z)</option>
</select>
<p data-i18n-key="download_zip_prompt" style="margin-top:10px;">Enter a name for the archive file:</p>
<input type="text" id="zipFileNameInput" class="form-control" data-i18n-placeholder="zip_placeholder"
placeholder="files.zip" />
<div class="modal-footer" style="margin-top:15px; text-align:right;">
<button id="cancelDownloadZip" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
<button id="confirmDownloadZip" class="btn btn-primary" data-i18n-key="download" data-default>Download</button>
</div>
</div>
</div>
</div>
<div id="fileList"></div>
</div>
<div id="fileListContainerSecondary" class="file-list-pane secondary-pane" style="display: none;">
<h2 id="fileListTitleSecondary" class="file-list-title" data-i18n-key="file_list_title">Files in (Root)</h2>
<div class="file-list-actions">
<div class="file-list-dual-hint" data-i18n-key="dual_pane_secondary_hint">Select a folder to open here.</div>
</div>
<div id="fileListSecondary" class="file-list-secondary-empty">
<div class="empty-state" data-i18n-key="dual_pane_secondary_empty">Select a folder to open in the right pane.</div>
</div>
</div>
</div>
</div> <!-- end container-fluid -->
</div> <!-- end mainColumn -->
</div> <!-- end main-wrapper -->
</div>
<!-- Download Progress Modal -->
<div id="downloadProgressModal" class="modal" style="display: none;">
<div class="modal-content" style="text-align: center; padding: 20px;">
<h4 id="downloadProgressTitle" data-i18n-key="preparing_download">
Preparing your download...
</h4>
<!-- spinner -->
<span class="material-icons download-spinner">autorenew</span>
<!-- these were missing -->
<progress id="downloadProgressBar" value="0" max="100" style="width:100%; height:1.5em; display:none;"></progress>
<p>
<span id="downloadProgressPercent" style="display:none;">0%</span>
</p>
</div>
</div>
<!-- Single File Download Modal -->
<div id="downloadFileModal" class="modal" style="display: none;">
<div class="modal-content" style="text-align: center; padding: 20px;">
<h4 data-i18n-key="download_file">Download File</h4>
<p data-i18n-key="confirm_or_change_filename">Confirm or change the download file name:</p>
<input type="text" id="downloadFileNameInput" class="form-control" data-i18n-placeholder="filename"
placeholder="Filename" />
<div style="margin-top: 15px; text-align: right;">
<button id="cancelDownloadFile" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
<button id="confirmSingleDownloadButton" class="btn btn-primary" data-i18n-key="download" data-default>Download</button>
</div>
</div>
</div>
<!-- Change Password, Add User, Remove User, Rename File, and Custom Confirm Modals (unchanged) -->
<div id="changePasswordModal" class="modal" style="display:none;">
<div class="modal-content" style="text-align: center; padding: 20px;">
<span id="closeChangePasswordModal" class="editor-close-btn">&times;</span>
<h3 data-i18n-key="change_password_title">Change Password</h3>
<input type="password" id="oldPassword" class="form-control" data-i18n-placeholder="old_password"
placeholder="Old Password" style="width:100%; margin: 5px 0;" />
<input type="password" id="newPassword" class="form-control" data-i18n-placeholder="new_password"
placeholder="New Password" style="width:100%; margin: 5px 0;" />
<input type="password" id="confirmPassword" class="form-control" data-i18n-placeholder="confirm_new_password"
placeholder="Confirm New Password" style="width:100%; margin: 5px 0;" />
<button id="saveNewPasswordBtn" class="btn btn-primary" data-i18n-key="save" style="width:100%;" data-default>Save</button>
</div>
</div>
<div id="addUserModal" class="modal" style="display:none;">
<div class="modal-content">
<h3 data-i18n-key="create_new_user_title">Create New User</h3>
<!-- 1) Add a form around these fields -->
<form id="addUserForm">
<label for="newUsername" data-i18n-key="username">Username:</label>
<input type="text" id="newUsername" class="form-control" required />
<label for="addUserPassword" data-i18n-key="password">Password:</label>
<input type="password" id="addUserPassword" class="form-control" required />
<div id="adminCheckboxContainer">
<input type="checkbox" id="isAdmin" />
<label for="isAdmin" data-i18n-key="grant_admin">Grant Admin Access</label>
</div>
<div class="button-container">
<!-- Cancel stays type="button" -->
<button type="button" id="cancelUserBtn" class="btn btn-secondary" data-i18n-key="cancel">
Cancel
</button>
<!-- Save becomes type="submit" -->
<button type="submit" id="saveUserBtn" class="btn btn-primary" data-i18n-key="save_user" data-default>
Save User
</button>
</div>
</form>
</div>
</div>
<div id="fileContextMenu" class="filr-menu" hidden role="menu" aria-label="File actions">
<button type="button" class="mi"
data-action="create_file"
data-when="always">
<i class="material-icons">note_add</i>
<span>Create file</span>
</button>
<div class="sep" data-when="always"></div>
<button type="button" class="mi"
data-action="delete_selected"
data-when="any">
<i class="material-icons">delete</i>
<span>Delete selected</span>
</button>
<button type="button" class="mi"
data-action="copy_selected"
data-when="any">
<i class="material-icons">content_copy</i>
<span>Copy selected</span>
</button>
<button type="button" class="mi"
data-action="move_selected"
data-when="any">
<i class="material-icons">drive_file_move</i>
<span>Move selected</span>
</button>
<button type="button" class="mi"
data-action="download_zip"
data-when="any">
<i class="material-icons">archive</i>
<span>Download Archive</span>
</button>
<!-- NEW: multi-download without ZIP -->
<button type="button" class="mi"
data-action="download_plain"
data-when="any">
<i class="material-icons">file_download</i>
<span>Download (no ZIP)</span>
</button>
<button type="button" class="mi"
data-action="extract_zip"
data-when="zip">
<i class="material-icons">unarchive</i>
<span>Extract Archive</span>
</button>
<div class="sep" data-when="any"></div>
<button type="button" class="mi"
data-action="tag_selected"
data-when="many">
<i class="material-icons">sell</i>
<span>Tag selected</span>
</button>
<button type="button" class="mi"
data-action="preview"
data-when="one">
<i class="material-icons">visibility</i>
<span>Preview</span>
</button>
<button type="button" class="mi"
data-action="edit"
data-when="can-edit">
<i class="material-icons">edit</i>
<span>Edit</span>
</button>
<button type="button" class="mi"
data-action="rename"
data-when="one">
<i class="material-icons">drive_file_rename_outline</i>
<span>Rename</span>
</button>
<button type="button" class="mi"
data-action="tag_file"
data-when="one">
<i class="material-icons">sell</i>
<span>Tag file</span>
</button>
<button type="button" class="mi"
data-action="share_file"
data-when="one">
<i class="material-icons">share</i>
<span>Share file</span>
</button>
</div>
<div id="removeUserModal" class="modal" style="display:none;">
<div class="modal-content">
<h3 data-i18n-key="remove_user_title">Remove User</h3>
<label for="removeUsernameSelect" data-i18n-key="select_user_remove">Select a user to remove:</label>
<select id="removeUsernameSelect" class="form-control"></select>
<div class="button-container">
<button id="cancelRemoveUserBtn" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
<button id="deleteUserBtn" class="btn btn-danger" data-i18n-key="delete_user">Delete User</button>
</div>
</div>
</div>
<div id="renameFileModal" class="modal" style="display:none;">
<div class="modal-content">
<h4 data-i18n-key="rename_file_title">Rename File</h4>
<input type="text" id="newFileName" class="form-control" data-i18n-placeholder="rename_file_placeholder"
placeholder="Enter new file name" style="margin-top:10px;" />
<div style="margin-top:15px; text-align:right;">
<button id="cancelRenameFile" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
<button id="submitRenameFile" class="btn btn-primary" data-i18n-key="rename" data-default>Rename</button>
</div>
</div>
</div>
<div id="customConfirmModal" class="modal" style="display:none;">
<div class="modal-content">
<p id="confirmMessage"></p>
<div class="modal-actions">
<button id="confirmYesBtn" class="btn btn-primary" data-i18n-key="yes" data-default>Yes</button>
<button id="confirmNoBtn" class="btn btn-secondary" data-i18n-key="no">No</button>
</div>
</div>
</div>
<!-- Upload Modal -->
<div id="uploadModal" class="modal" style="display:none;">
<div class="modal-content" style="max-width:900px;width:92vw;">
<div class="modal-header" style="display:flex;justify-content:space-between;align-items:center;">
<h3 style="margin:0;">Upload</h3>
<span id="closeUploadModal" class="editor-close-btn" role="button" aria-label="Close">&times;</span>
</div>
<div class="modal-body">
<!-- we will MOVE #uploadCard into here while open -->
<div id="uploadModalBody"></div>
</div>
</div>
</div>
</div>
<footer id="siteFooter" class="site-footer">
<span>
&copy; 2025
<a href="https://filerise.net" target="_blank" rel="noopener noreferrer">
FileRise
</a>
</span>
</footer>
</body>
</html>