diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5833d28..a0542fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
# Changelog
+## Changes 11/21/2025 (v1.9.14)
+
+release(v1.9.14): inline folder rows, synced folder icons, and compact theme polish
+
+- Add ACL-aware folder stats and byte counts in FolderModel::countVisible()
+- Show subfolders inline as rows above files in table view (Explorer-style)
+- Page folders + files together and wire folder rows into existing DnD and context menu flows
+- Add folder action buttons (move/rename/color/share) with capability checks from /api/folder/capabilities.php
+- Cache folder capabilities and owners to avoid repeat calls per row
+- Add user settings to toggle folder strip and inline folder rows (stored in localStorage)
+- Default itemsPerPage to 50 and remember current page across renders
+- Sync inline folder icon size to file row height and tweak vertical alignment for different row heights
+- Update table headers + i18n keys to use Name / Size / Modified / Created / Owner labels
+- Compact and consolidate light/dark theme CSS, search pill, pagination, and font-size controls
+- Tighten file action button hit areas and add specific styles for folder move/rename buttons
+
+---
+
## Changes 11/20/2025 (v1.9.13)
release(v1.9.13): style(ui): compact dual-theme polish for lists, inputs, search & modals
diff --git a/public/css/styles.css b/public/css/styles.css
index 8945feb..ad7ff6d 100644
--- a/public/css/styles.css
+++ b/public/css/styles.css
@@ -614,7 +614,8 @@ body:not(.dark-mode) .material-icons.pauseResumeBtn:hover{background-color: rgba
#fileList button.edit-btn{background-color: #007bff;
color: white;}
.rename-btn .material-icons,
- #renameFolderBtn .material-icons{color: black !important;}
+ #renameFolderBtn .material-icons,
+ .folder-rename-btn .material-icons{color: black !important;}
#fileList table{background-color: transparent;
border-collapse: collapse !important;
border-spacing: 0 !important;
@@ -818,9 +819,34 @@ label{font-size: 0.9rem;}
.folder-actions .btn,
.folder-actions .material-icons{transition: none;}
}
-#moveFolderBtn{background-color: #ff9800;
+#moveFolderBtn,
+.folder-move-btn{background-color: #ff9800;
border-color: #ff9800;
- color: #fff;}
+ color: #fff;
+ }
+ #moveFolderBtn:hover:not(:disabled):not(.disabled),
+.folder-move-btn:hover:not(:disabled):not(.disabled) {
+ background-color: #fb8c00; /* slightly darker */
+ border-color: #fb8c00;
+}
+
+/* Active/pressed (only when enabled) */
+#moveFolderBtn:active:not(:disabled):not(.disabled),
+.folder-move-btn:active:not(:disabled):not(.disabled) {
+ background-color: #f57c00;
+ border-color: #f57c00;
+}
+
+/* Disabled state (both attribute + .disabled class) */
+#moveFolderBtn:disabled,
+#moveFolderBtn.disabled,
+.folder-move-btn:disabled,
+.folder-move-btn.disabled {
+ background-color: #ffb74d;
+ border-color: #ffb74d;
+ color: #fff;
+ opacity: 0.55;
+}
.row-selected{background-color: #f2f2f2 !important;}
.dark-mode .row-selected{background-color: #444 !important;
color: #fff !important;}
@@ -947,7 +973,8 @@ label{font-size: 0.9rem;}
transform: none !important;
box-shadow: none !important;}
}
-.btn-group.btn-group-sm[aria-label="File actions"] .btn{padding: .2rem !important;
+
+.btn-group.btn-group-sm[aria-label="File actions"] .btn{padding: .8rem !important;
width: 32px;
height: 32px;
line-height: 1 !important;
@@ -978,6 +1005,7 @@ label{font-size: 0.9rem;}
.btn-group.btn-group-sm[aria-label="File actions"] .btn .material-symbols-rounded{transition: none !important;
transform: none !important;}
}
+
.breadcrumb-link{cursor: pointer;
color: #007bff;
text-decoration: underline;}
@@ -1693,8 +1721,6 @@ body.dark-mode .folder-strip-container .folder-item:hover{background-color: rgba
--filr-folder-stroke:#a87312;
--filr-paper-fill: #ffffff;
--filr-paper-stroke: #9fb3d6;
-
-
--row-h: 28px;
--twisty: 24px;
--twisty-gap: -5px;
@@ -1841,7 +1867,6 @@ body.dark-mode #folderTreeContainer .folder-icon .lock-keyhole{fill: rgba(255,25
align-items: center;
gap: 8px;
justify-content: center;
- border-radius: 10px;
border: 1px solid var(--tree-ghost-border);
background: var(--tree-ghost-bg);
color: var(--tree-ghost-fg);
@@ -1971,653 +1996,99 @@ body.dark-mode #folderTreeContainer .folder-icon .lock-keyhole{fill: rgba(255,25
/* ============================================
FileRise polish – compact theme layer
============================================ */
-
-/* Tokens */
-:root {
- --filr-radius-lg: 14px;
- --filr-radius-xl: 18px;
- --filr-shadow-soft: 0 12px 35px rgba(15,23,42,.14);
- --filr-shadow-subtle: 0 8px 20px rgba(15,23,42,.10);
- --filr-header-blur: 18px;
- --filr-transition-fast: 150ms ease-out;
- --filr-transition-med: 220ms cubic-bezier(.22,.61,.36,1);
-
- /* Dark theme */
- --fr-bg-dark: #0f0f0f;
- --fr-surface-dark: #212121;
- --fr-surface-dark-2: #181818;
- --fr-border-dark: #303030;
- --fr-muted-dark: #aaaaaa;
-
- /* Light theme */
- --fr-bg-light: #f9f9f9;
- --fr-surface-light: #ffffff;
- --fr-surface-light-2: #f1f1f1;
- --fr-border-light: #e5e5e5;
- --fr-muted-light: #606060;
-}
-
-/* Pro badge */
-.btn-pro-admin {
- background: linear-gradient(135deg, #ff9800, #ff5722);
- border-color: #ff9800;
- color: #1b0f00 !important;
- font-weight: 600;
- box-shadow: 0 0 10px rgba(255,152,0,.4);
-}
-
-/* Toast base shape (colors themed below) */
-#customToast {
- border-radius: 999px;
-}
-
-/* Folder tree row rounding */
-#folderTreeContainer .folder-row { border-radius: 8px; }
-
-/* Buttons – pill style + hover lift */
-.btn,
-#customChooseBtn {
- border-radius: 999px;
- font-weight: 500;
- border: 1px solid transparent;
- transition:
- background-color var(--filr-transition-fast),
- box-shadow var(--filr-transition-fast),
- transform var(--filr-transition-fast),
- border-color var(--filr-transition-fast);
-}
-
-/* Upload / create / primary: shared shadow in light + dark */
-.btn-primary,
-#createBtn,
-#uploadBtn {
- box-shadow: 0 2px 4px rgba(0,0,0,.6);
-}
-
-.btn-primary:hover,
-#createBtn:hover,
-#uploadBtn:hover {
- filter: brightness(1.04);
- transform: translateY(-1px);
- box-shadow: 0 10px 22px rgba(0,140,180,.28);
-}
-
-/* Destructive buttons */
-#deleteSelectedBtn,
-#deleteAllBtn,
-#deleteTrashSelectedBtn {
- border-color: rgba(248,113,113,.9);
- box-shadow: 0 8px 18px rgba(248,113,113,.35);
-}
-
-/* Forms & inputs – base */
-input[type="text"],
-input[type="password"],
-input[type="email"],
-input[type="url"],
-select,
-textarea {
- border-radius: 10px;
- padding: 8px 10px;
- font-size: .92rem;
- transition:
- border-color var(--filr-transition-fast),
- box-shadow var(--filr-transition-fast),
- background-color var(--filr-transition-fast);
-}
-
-input:focus,
-select:focus,
-textarea:focus {
- outline: none;
- border-color: var(--filr-accent-500);
- box-shadow: 0 0 0 1px var(--filr-accent-ring);
-}
-
-/* Modals – subtle blur baseline (overridden per theme) */
-.modal {
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
-}
-
-/* Core elevated surfaces */
-#fileListContainer,
-#uploadCard,
-#folderManagementCard,
-.card,
-.admin-panel-content {
- border-radius: var(--filr-radius-xl);
- border: 1px solid rgba(15,23,42,.06);
- background: #ffffff;
- box-shadow: var(--filr-shadow-subtle);
-}
-
-/* Full-height body */
-body { min-height: 100vh; }
-
-/* ============================================
- Dark theme
-============================================ */
-
-body.dark-mode {
- background: var(--fr-bg-dark) !important;
- color: #f1f1f1 !important;
- background-image: none !important;
-}
-
-/* Main surfaces */
-body.dark-mode #fileListContainer,
-body.dark-mode #uploadCard,
-body.dark-mode #folderManagementCard,
-body.dark-mode .card,
-body.dark-mode .admin-panel-content,
-body.dark-mode .media-topbar {
- background: var(--fr-surface-dark) !important;
- border-color: var(--fr-border-dark) !important;
- box-shadow: 0 1px 4px rgba(0,0,0,.9) !important;
- backdrop-filter: none !important;
- -webkit-backdrop-filter: none !important;
-}
-
-/* Remove inner “glass” highlight if present */
-body.dark-mode #fileListContainer::before,
-body.dark-mode #uploadCard::before,
-body.dark-mode #folderManagementCard::before,
-body.dark-mode .card::before,
-body.dark-mode .admin-panel-content::before {
- box-shadow: none !important;
-}
-
-/* Section headers */
-body.dark-mode .card-header,
-body.dark-mode .custom-folder-card-body .drag-header {
- background: var(--fr-surface-dark-2) !important;
- border-bottom: 1px solid var(--fr-border-dark) !important;
-}
-
-/* File list header / rows */
-body.dark-mode #fileList table thead th {
- background: var(--fr-surface-dark-2) !important;
- border-bottom: 1px solid var(--fr-border-dark) !important;
-}
-
-body.dark-mode #fileList table.filr-table tbody tr:hover:not(.selected,.row-selected,.selected-row,.is-selected)>td {
- background: rgba(255,255,255,.04) !important;
- box-shadow: none !important;
-}
-
-body.dark-mode #fileList table.filr-table tbody tr.selected>td,
-body.dark-mode #fileList table.filr-table tbody tr.row-selected>td,
-body.dark-mode #fileList table.filr-table tbody tr.selected-row>td,
-body.dark-mode #fileList table.filr-table tbody tr.is-selected>td {
- background: rgba(62,166,255,.16) !important;
- box-shadow: none !important;
-}
-
-/* Dark modals */
-body.dark-mode .modal {
- background-color: rgba(0,0,0,.65) !important;
- backdrop-filter: none !important;
- -webkit-backdrop-filter: none !important;
-}
-
-body.dark-mode .modal .modal-content,
-body.dark-mode .editor-modal,
-body.dark-mode .image-preview-modal-content,
-body.dark-mode #restoreFilesModal .modal-content,
-body.dark-mode #downloadProgressModal .modal-content {
- background: var(--fr-surface-dark) !important;
- border-radius: 12px !important;
- border: 1px solid var(--fr-border-dark) !important;
- box-shadow: 0 8px 24px rgba(0,0,0,.9) !important;
-}
-
-body.dark-mode .modal .modal-content::before,
-body.dark-mode .editor-modal::before,
-body.dark-mode .image-preview-modal-content::before,
-body.dark-mode #restoreFilesModal .modal-content::before,
-body.dark-mode #downloadProgressModal .modal-content::before {
- box-shadow: none !important;
-}
-
-/* Dark inputs */
-body.dark-mode input[type="text"],
-body.dark-mode input[type="password"],
-body.dark-mode input[type="email"],
-body.dark-mode input[type="url"],
-body.dark-mode select,
-body.dark-mode textarea {
- background: #121212 !important;
- border-color: #3d3d3d !important;
- color: #f1f1f1 !important;
-}
-
-body.dark-mode input::placeholder,
-body.dark-mode textarea::placeholder { color: #777 !important; }
-
-body.dark-mode input:focus,
-body.dark-mode select:focus,
-body.dark-mode textarea:focus {
- border-color: #3ea6ff !important;
- box-shadow: 0 0 0 1px rgba(62,166,255,.7) !important;
-}
-
-/* Dark destructive buttons */
-body.dark-mode #deleteSelectedBtn,
-body.dark-mode #deleteAllBtn,
-body.dark-mode #deleteTrashSelectedBtn {
- background-color: #b3261e !important;
- border-color: #b3261e !important;
- box-shadow: 0 4px 10px rgba(0,0,0,.7) !important;
-}
-
-/* Dark folder strip */
-body.dark-mode .folder-strip-container.folder-strip-mobile {
- background: var(--fr-surface-dark-2) !important;
- border: 1px solid var(--fr-border-dark) !important;
-}
-
-/* Dark toast */
-body.dark-mode #customToast {
- background: #212121 !important;
- border: 1px solid var(--fr-border-dark) !important;
- box-shadow: 0 8px 20px rgba(0,0,0,.9) !important;
-}
-
-/* Dark meta text */
-body.dark-mode #fileSummary { color: var(--fr-muted-dark) !important; }
-
-/* Menus & panels (dark) */
-body.dark-mode #createMenu,
-body.dark-mode .user-dropdown .user-menu,
-body.dark-mode #fileContextMenu,
-body.dark-mode #folderContextMenu,
-body.dark-mode #folderManagerContextMenu,
-body.dark-mode #adminPanelModal .modal-content,
-body.dark-mode #userPermissionsModal .modal-content,
-body.dark-mode #userFlagsModal .modal-content,
-body.dark-mode #userGroupsModal .modal-content,
-body.dark-mode #userPanelModal .modal-content,
-body.dark-mode #groupAclModal .modal-content,
-body.dark-mode .editor-modal,
-body.dark-mode #filePreviewModal .modal-content,
-body.dark-mode #loginForm,
-body.dark-mode .editor-header {
- background: var(--fr-surface-dark) !important;
- border: 1px solid var(--fr-border-dark) !important;
- color: #f1f1f1 !important;
- border-radius: 12px !important;
- box-shadow: 0 8px 24px rgba(0,0,0,.9) !important;
-}
-
-body.dark-mode .user-dropdown .user-menu,
-body.dark-mode #createMenu,
-body.dark-mode #fileContextMenu,
-body.dark-mode #folderContextMenu,
-body.dark-mode #folderManagerContextMenu {
- background-clip: padding-box;
-}
-
-/* ============================================
- Light theme
-============================================ */
-
-body:not(.dark-mode) {
- background: var(--fr-bg-light) !important;
- color: #111 !important;
- background-image: none !important;
-}
-
-/* Light surfaces */
-body:not(.dark-mode) #fileListContainer,
-body:not(.dark-mode) #uploadCard,
-body:not(.dark-mode) #folderManagementCard,
-body:not(.dark-mode) .card,
-body:not(.dark-mode) .admin-panel-content {
- background: var(--fr-surface-light) !important;
- border-color: var(--fr-border-light) !important;
- box-shadow: 0 3px 8px rgba(0,0,0,.04) !important;
- backdrop-filter: none !important;
- -webkit-backdrop-filter: none !important;
-}
-
-/* Remove inner highlight */
-body:not(.dark-mode) #fileListContainer::before,
-body:not(.dark-mode) #uploadCard::before,
-body:not(.dark-mode) #folderManagementCard::before,
-body:not(.dark-mode) .card::before,
-body:not(.dark-mode) .admin-panel-content::before {
- box-shadow: none !important;
-}
-
-/* Light section headers */
-body:not(.dark-mode) .card-header,
-body:not(.dark-mode) .custom-folder-card-body .drag-header {
- background: var(--fr-surface-light-2) !important;
- border-bottom: 1px solid var(--fr-border-light) !important;
-}
-
-/* Light file list */
-body:not(.dark-mode) #fileList table thead th {
- background: var(--fr-surface-light-2) !important;
- border-bottom: 1px solid var(--fr-border-light) !important;
-}
-
-body:not(.dark-mode) #fileList table.filr-table tbody tr:hover:not(.selected,.row-selected,.selected-row,.is-selected)>td {
- background: rgba(0,0,0,.02) !important;
- box-shadow: none !important;
-}
-
-body:not(.dark-mode) #fileList table.filr-table tbody tr.selected>td,
-body:not(.dark-mode) #fileList table.filr-table tbody tr.row-selected>td,
-body:not(.dark-mode) #fileList table.filr-table tbody tr.selected-row>td,
-body:not(.dark-mode) #fileList table.filr-table tbody tr.is-selected>td {
- background: rgba(33,150,243,.12) !important;
- box-shadow: none !important;
-}
-
-/* Light modals */
-body:not(.dark-mode) .modal {
- background-color: rgba(0,0,0,.4) !important;
- backdrop-filter: none !important;
- -webkit-backdrop-filter: none !important;
-}
-
-body:not(.dark-mode) .modal .modal-content,
-body:not(.dark-mode) .editor-modal,
-body:not(.dark-mode) .image-preview-modal-content,
-body:not(.dark-mode) #restoreFilesModal .modal-content,
-body:not(.dark-mode) #downloadProgressModal .modal-content {
- background: var(--fr-surface-light) !important;
- border-radius: 12px !important;
- border: 1px solid var(--fr-border-light) !important;
- box-shadow: 0 8px 24px rgba(0,0,0,.18) !important;
-}
-
-body:not(.dark-mode) .modal .modal-content::before,
-body:not(.dark-mode) .editor-modal::before,
-body:not(.dark-mode) .image-preview-modal-content::before,
-body:not(.dark-mode) #restoreFilesModal .modal-content::before,
-body:not(.dark-mode) #downloadProgressModal .modal-content::before {
- box-shadow: none !important;
-}
-
-/* Light inputs */
-body:not(.dark-mode) input[type="text"],
-body:not(.dark-mode) input[type="password"],
-body:not(.dark-mode) input[type="email"],
-body:not(.dark-mode) input[type="url"],
-body:not(.dark-mode) select,
-body:not(.dark-mode) textarea {
- background: #fff !important;
- border-color: #d0d0d0 !important;
- color: #111 !important;
-}
-
-body:not(.dark-mode) input::placeholder,
-body:not(.dark-mode) textarea::placeholder {
- color: #9e9e9e !important;
-}
-
-body:not(.dark-mode) input:focus,
-body:not(.dark-mode) select:focus,
-body:not(.dark-mode) textarea:focus {
- border-color: #2196f3 !important;
- box-shadow: 0 0 0 1px rgba(33,150,243,.55) !important;
-}
-
-/* Light destructive buttons */
-body:not(.dark-mode) #deleteSelectedBtn,
-body:not(.dark-mode) #deleteAllBtn,
-body:not(.dark-mode) #deleteTrashSelectedBtn {
- box-shadow: 0 2px 6px rgba(244,67,54,.3) !important;
-}
-
-/* Light folder strip */
-body:not(.dark-mode) .folder-strip-container.folder-strip-mobile {
- background: #f1f1f1 !important;
- border: 1px solid var(--fr-border-light) !important;
-}
-
-/* Light toast */
-body:not(.dark-mode) #customToast {
- background: #212121 !important;
- color: #fff !important;
- border: 1px solid #000 !important;
- box-shadow: 0 8px 18px rgba(0,0,0,.45) !important;
-}
-
-/* Light meta text */
-body:not(.dark-mode) #fileSummary { color: var(--fr-muted-light) !important; }
-
-/* Menus & panels (light) */
-body:not(.dark-mode) #createMenu,
-body:not(.dark-mode) .user-dropdown .user-menu,
-body:not(.dark-mode) #fileContextMenu,
-body:not(.dark-mode) #folderContextMenu,
-body:not(.dark-mode) #folderManagerContextMenu,
-body:not(.dark-mode) #adminPanelModal .modal-content,
-body:not(.dark-mode) #userPermissionsModal .modal-content,
-body:not(.dark-mode) #userFlagsModal .modal-content,
-body:not(.dark-mode) #userGroupsModal .modal-content,
-body:not(.dark-mode) #userPanelModal .modal-content,
-body:not(.dark-mode) #groupAclModal .modal-content,
-body:not(.dark-mode) .editor-modal,
-body:not(.dark-mode) #filePreviewModal .modal-content,
-body:not(.dark-mode) #loginForm
-body:not(.dark-mode) .editor-header{
- background: var(--fr-surface-light) !important;
- border: 1px solid var(--fr-border-light) !important;
- color: #111 !important;
- border-radius: 12px !important;
- box-shadow: 0 4px 12px rgba(0,0,0,.12) !important;
-}
-
-/* ============================================
- Search group / advanced search / pagination
-============================================ */
-
-/* Search icon + input */
-#searchIcon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 38px;
- height: 36px;
- padding: 0;
- border-radius: 999px 0 0 999px;
- border: 1px solid rgba(0,0,0,.18);
- border-right: none;
- background: #fff;
- cursor: pointer;
- box-shadow: none;
- transform: none;
-}
-
-#searchIcon .material-icons {
- font-size: 20px;
- line-height: 1;
- color: #555;
-}
-
-#searchIcon:hover { background: #f5f5f5; }
-
-#searchIcon + #searchInput {
- height: 36px;
- border-radius: 0 999px 999px 0;
- border-left: none;
- padding-top: 6px;
- padding-bottom: 6px;
-}
-
-/* Dark search */
-body.dark-mode #searchIcon {
- background: #212121;
- border-color: #3d3d3d;
-}
-body.dark-mode #searchIcon .material-icons { color: #f1f1f1; }
-body.dark-mode #searchIcon:hover { background: #303030; }
-body.dark-mode #searchIcon + #searchInput { border-left: none; }
-
-/* Advanced search toggle */
-#advancedSearchToggle {
- border-radius: 999px;
- border: 1px solid #d0d0d0;
- padding: 6px 12px;
- font-size: .9rem;
- background: #f5f5f5;
- color: #333;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- gap: 4px;
- margin-right: 8px;
- transition: background .15s ease, box-shadow .15s ease, transform .1s ease;
-}
-#advancedSearchToggle:hover,
-#advancedSearchToggle:focus-visible {
- background: #e8e8e8;
- box-shadow: 0 1px 4px rgba(0,0,0,.16);
- outline: none;
- transform: translateY(-1px);
-}
-.dark-mode #advancedSearchToggle {
- background: #2a2a2a;
- border-color: #444;
- color: #f1f1f1;
-}
-.dark-mode #advancedSearchToggle:hover,
-.dark-mode #advancedSearchToggle:focus-visible {
- background: #333;
- box-shadow: 0 1px 4px rgba(0,0,0,.5);
-}
-
-/* Prev / Next pagination */
-.custom-prev-next-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 64px;
- padding: 6px 14px;
- font-size: 13px;
- font-weight: 500;
- border-radius: 999px;
- border: 1px solid rgba(0,0,0,.14);
- background: #f1f1f1;
- color: #111;
- cursor: pointer;
- transition:
- background-color 140ms ease-out,
- border-color 140ms ease-out,
- box-shadow 140ms ease-out,
- transform 120ms ease-out;
-}
-.custom-prev-next-btn:not(:disabled):hover {
- background: #e5e5e5;
- border-color: rgba(0,0,0,.22);
- box-shadow: 0 2px 6px rgba(0,0,0,.18);
- transform: translateY(-1px);
-}
-.custom-prev-next-btn:not(:disabled):active {
- transform: translateY(0);
- box-shadow: 0 1px 3px rgba(0,0,0,.25);
-}
-.custom-prev-next-btn:disabled {
- opacity: .5;
- cursor: default;
- box-shadow: none;
-}
-body.dark-mode .custom-prev-next-btn {
- background: #212121;
- border-color: #3d3d3d;
- color: #f1f1f1;
-}
-body.dark-mode .custom-prev-next-btn:not(:disabled):hover {
- background: #2a2a2a;
- border-color: #4a4a4a;
- box-shadow: 0 2px 6px rgba(0,0,0,.7);
-}
-
-/* Normalize normal inputs (everything except the search pill) */
-input[type="text"]:not(#searchInput),
-input[type="password"],
-input[type="email"],
-input[type="url"],
-input[type="number"],
-textarea,
-select {
- border: 1px solid rgba(148,163,184,.6) !important;
- border-radius: 10px;
- background: #ffffff;
- box-sizing: border-box;
-}
-/* Compact font-size controls (A- / A+) */
-#decreaseFont,
-#increaseFont {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- margin-top: 5px;
- height: 24px;
- min-width: 30px;
- padding: 2px 8px;
-
- font-size: 11px;
- font-weight: 500;
- line-height: 1;
- border-radius: 999px;
-
- border: 1px solid rgba(0, 0, 0, 0.16);
- background: #f5f5f5;
- color: #222;
-
- cursor: pointer;
- margin-left: 4px;
-
- transition:
- background-color 140ms ease-out,
- border-color 140ms ease-out,
- box-shadow 140ms ease-out,
- transform 120ms ease-out;
-}
-
-/* Hover / active */
-#decreaseFont:not(:disabled):hover,
-#increaseFont:not(:disabled):hover {
- background: #e8e8e8;
- border-color: rgba(0, 0, 0, 0.24);
- box-shadow: 0 1px 4px rgba(0,0,0,0.18);
- transform: translateY(-1px);
-}
-
-#decreaseFont:not(:disabled):active,
-#increaseFont:not(:disabled):active {
- transform: translateY(5);
- box-shadow: 0 1px 2px rgba(0,0,0,0.25);
-}
-
-/* Disabled */
-#decreaseFont:disabled,
-#increaseFont:disabled {
- opacity: 0.5;
- cursor: default;
- box-shadow: none;
-}
-
-/* Dark mode tweaks */
-body.dark-mode #decreaseFont,
-body.dark-mode #increaseFont {
- background: #212121;
- border-color: #3d3d3d;
- color: #f1f1f1;
-}
-
-body.dark-mode #decreaseFont:not(:disabled):hover,
-body.dark-mode #increaseFont:not(:disabled):hover {
- background: #2a2a2a;
- border-color: #4a4a4a;
- box-shadow: 0 1px 4px rgba(0,0,0,0.7);
-}
-#closeEditorX {
-margin-right: 10px;
-}
\ No newline at end of file
+:root{--filr-radius-lg:14px;--filr-radius-xl:18px;--filr-shadow-soft:0 12px 35px rgba(15,23,42,.14);--filr-shadow-subtle:0 8px 20px rgba(15,23,42,.10);--filr-header-blur:18px;--filr-transition-fast:150ms ease-out;--filr-transition-med:220ms cubic-bezier(.22,.61,.36,1);--fr-bg-dark:#0f0f0f;--fr-surface-dark:#212121;--fr-surface-dark-2:#181818;--fr-border-dark:#303030;--fr-muted-dark:#aaaaaa;--fr-bg-light:#f9f9f9;--fr-surface-light:#ffffff;--fr-surface-light-2:#f1f1f1;--fr-border-light:#e5e5e5;--fr-muted-light:#606060}
+.btn-pro-admin{background:linear-gradient(135deg,#ff9800,#ff5722);border-color:#ff9800;color:#1b0f00!important;font-weight:600;box-shadow:0 0 10px rgba(255,152,0,.4)}
+#customToast{border-radius:999px}
+#folderTreeContainer .folder-row{border-radius:8px}
+.btn,#customChooseBtn, #colorFolderModal .btn-ghost, #cancelMoveFolder, #confirmMoveFolder, #cancelRenameFolder, #submitRenameFolder, #cancelDeleteFolder, #confirmDeleteFolder, #cancelCreateFolder, #submitCreateFolder{border-radius:999px;font-weight:500;border:1px solid transparent;transition:background-color var(--filr-transition-fast),box-shadow var(--filr-transition-fast),transform var(--filr-transition-fast),border-color var(--filr-transition-fast)}
+.btn-primary,#createBtn,#uploadBtn,#submitCreateFolder,#submitRenameFolder,#confirmMoveFolder{box-shadow:0 2px 4px rgba(0,0,0,.6)}
+.btn-primary:hover,#createBtn:hover,#uploadBtn:hover,#submitCreateFolder:hover,#submitRenameFolder:hover,#confirmMoveFolder:hover{filter:brightness(1.04);transform:translateY(-1px);box-shadow:0 10px 22px rgba(0,140,180,.28)}
+#deleteSelectedBtn,#deleteAllBtn,#deleteTrashSelectedBtn,#deleteFolderBtn,#confirmDeleteFolder{border-color:rgba(248,113,113,.9);box-shadow:0 8px 18px rgba(248,113,113,.35)}
+input[type=text],input[type=password],input[type=email],input[type=url],select,textarea{border-radius:10px;padding:8px 10px;font-size:.92rem;transition:border-color var(--filr-transition-fast),box-shadow var(--filr-transition-fast),background-color var(--filr-transition-fast)}
+input:focus,select:focus,textarea:focus{outline:none;border-color:var(--filr-accent-500);box-shadow:0 0 0 1px var(--filr-accent-ring)}
+.modal{backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
+#fileListContainer,#uploadCard,#folderManagementCard,.card,.admin-panel-content{border-radius:var(--filr-radius-xl);border:1px solid rgba(15,23,42,.06);background:#ffffff;box-shadow:var(--filr-shadow-subtle)}
+body{min-height:100vh}
+body.dark-mode{background:var(--fr-bg-dark)!important;color:#f1f1f1!important;background-image:none!important}
+body.dark-mode #fileListContainer,body.dark-mode #uploadCard,body.dark-mode #folderManagementCard,body.dark-mode .card,body.dark-mode .admin-panel-content,body.dark-mode .media-topbar{background:var(--fr-surface-dark)!important;border-color:var(--fr-border-dark)!important;box-shadow:0 1px 4px rgba(0,0,0,.9)!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}
+body.dark-mode #fileListContainer::before,body.dark-mode #uploadCard::before,body.dark-mode #folderManagementCard::before,body.dark-mode .card::before,body.dark-mode .admin-panel-content::before{box-shadow:none!important}
+body.dark-mode .card-header,body.dark-mode .custom-folder-card-body .drag-header{background:var(--fr-surface-dark-2)!important;border-bottom:1px solid var(--fr-border-dark)!important}
+body.dark-mode #fileList table thead th{background:var(--fr-surface-dark-2)!important;border-bottom:1px solid var(--fr-border-dark)!important}
+body.dark-mode #fileList table.filr-table tbody tr.selected>td,body.dark-mode #fileList table.filr-table tbody tr.row-selected>td,body.dark-mode #fileList table.filr-table tbody tr.selected-row>td,body.dark-mode #fileList table.filr-table tbody tr.is-selected>td{background:rgba(62,166,255,.16)!important;box-shadow:none!important}
+body.dark-mode .modal{background-color:rgba(0,0,0,.65)!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}
+body.dark-mode .modal .modal-content,body.dark-mode .editor-modal,body.dark-mode .image-preview-modal-content,body.dark-mode #restoreFilesModal .modal-content,body.dark-mode #downloadProgressModal .modal-content{background:var(--fr-surface-dark)!important;border-radius:12px!important;border:1px solid var(--fr-border-dark)!important;box-shadow:0 8px 24px rgba(0,0,0,.9)!important}
+body.dark-mode .modal .modal-content::before,body.dark-mode .editor-modal::before,body.dark-mode .image-preview-modal-content::before,body.dark-mode #restoreFilesModal .modal-content::before,body.dark-mode #downloadProgressModal .modal-content::before{box-shadow:none!important}
+body.dark-mode input[type=text],body.dark-mode input[type=password],body.dark-mode input[type=email],body.dark-mode input[type=url],body.dark-mode select,body.dark-mode textarea{background:#121212!important;border-color:#3d3d3d!important;color:#f1f1f1!important}
+body.dark-mode input::placeholder,body.dark-mode textarea::placeholder{color:#777!important}
+body.dark-mode input:focus,body.dark-mode select:focus,body.dark-mode textarea:focus{border-color:#3ea6ff!important;box-shadow:0 0 0 1px rgba(62,166,255,.7)!important}
+body.dark-mode #deleteSelectedBtn,body.dark-mode #deleteAllBtn,body.dark-mode #deleteTrashSelectedBtn,#deleteFolderBtn,#confirmDeleteFolder{background-color:#b3261e!important;border-color:#b3261e!important;box-shadow:0 4px 10px rgba(0,0,0,.7)!important}
+body.dark-mode .folder-strip-container.folder-strip-mobile{background:var(--fr-surface-dark-2)!important;border:1px solid var(--fr-border-dark)!important}
+body.dark-mode #customToast{background:#212121!important;border:1px solid var(--fr-border-dark)!important;box-shadow:0 8px 20px rgba(0,0,0,.9)!important}
+body.dark-mode #fileSummary{color:var(--fr-muted-dark)!important}
+body.dark-mode #createMenu,body.dark-mode .user-dropdown .user-menu,body.dark-mode #fileContextMenu,body.dark-mode #folderContextMenu,body.dark-mode #folderManagerContextMenu,body.dark-mode #adminPanelModal .modal-content,body.dark-mode #userPermissionsModal .modal-content,body.dark-mode #userFlagsModal .modal-content,body.dark-mode #userGroupsModal .modal-content,body.dark-mode #userPanelModal .modal-content,body.dark-mode #groupAclModal .modal-content,body.dark-mode .editor-modal,body.dark-mode #filePreviewModal .modal-content,body.dark-mode #loginForm,body.dark-mode .editor-header{background:var(--fr-surface-dark)!important;border:1px solid var(--fr-border-dark)!important;color:#f1f1f1!important;border-radius:12px!important;box-shadow:0 8px 24px rgba(0,0,0,.9)!important}
+body.dark-mode .user-dropdown .user-menu,body.dark-mode #createMenu,body.dark-mode #fileContextMenu,body.dark-mode #folderContextMenu,body.dark-mode #folderManagerContextMenu{background-clip:padding-box}
+body:not(.dark-mode){background:var(--fr-bg-light)!important;color:#111!important;background-image:none!important}
+body:not(.dark-mode) #fileListContainer,body:not(.dark-mode) #uploadCard,body:not(.dark-mode) #folderManagementCard,body:not(.dark-mode) .card,body:not(.dark-mode) .admin-panel-content{background:var(--fr-surface-light)!important;border-color:var(--fr-border-light)!important;box-shadow:0 3px 8px rgba(0,0,0,.04)!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}
+body:not(.dark-mode) #fileListContainer::before,body:not(.dark-mode) #uploadCard::before,body:not(.dark-mode) #folderManagementCard::before,body:not(.dark-mode) .card::before,body:not(.dark-mode) .admin-panel-content::before{box-shadow:none!important}
+body:not(.dark-mode) .card-header,body:not(.dark-mode) .custom-folder-card-body .drag-header{background:var(--fr-surface-light-2)!important;border-bottom:1px solid var(--fr-border-light)!important}
+body:not(.dark-mode) #fileList table thead th{background:var(--fr-surface-light-2)!important;border-bottom:1px solid var(--fr-border-light)!important}
+body:not(.dark-mode) #fileList table.filr-table tbody tr.selected>td,body:not(.dark-mode) #fileList table.filr-table tbody tr.row-selected>td,body:not(.dark-mode) #fileList table.filr-table tbody tr.selected-row>td,body:not(.dark-mode) #fileList table.filr-table tbody tr.is-selected>td{background:rgba(33,150,243,.12)!important;box-shadow:none!important}
+body:not(.dark-mode) .modal{background-color:rgba(0,0,0,.4)!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}
+body:not(.dark-mode) .modal .modal-content,body:not(.dark-mode) .editor-modal,body:not(.dark-mode) .image-preview-modal-content,body:not(.dark-mode) #restoreFilesModal .modal-content,body:not(.dark-mode) #downloadProgressModal .modal-content{background:var(--fr-surface-light)!important;border-radius:12px!important;border:1px solid var(--fr-border-light)!important;box-shadow:0 8px 24px rgba(0,0,0,.18)!important}
+body:not(.dark-mode) .modal .modal-content::before,body:not(.dark-mode) .editor-modal::before,body:not(.dark-mode) .image-preview-modal-content::before,body:not(.dark-mode) #restoreFilesModal .modal-content::before,body:not(.dark-mode) #downloadProgressModal .modal-content::before{box-shadow:none!important}
+body:not(.dark-mode) input[type=text],body:not(.dark-mode) input[type=password],body:not(.dark-mode) input[type=email],body:not(.dark-mode) input[type=url],body:not(.dark-mode) select,body:not(.dark-mode) textarea{background:#fff!important;border-color:#d0d0d0!important;color:#111!important}
+body:not(.dark-mode) input::placeholder,body:not(.dark-mode) textarea::placeholder{color:#9e9e9e!important}
+body:not(.dark-mode) input:focus,body:not(.dark-mode) select:focus,body:not(.dark-mode) textarea:focus{border-color:#2196f3!important;box-shadow:0 0 0 1px rgba(33,150,243,.55)!important}
+body:not(.dark-mode) #deleteSelectedBtn,body:not(.dark-mode) #deleteAllBtn,body:not(.dark-mode) #deleteTrashSelectedBtn{box-shadow:0 2px 6px rgba(244,67,54,.3)!important}
+body:not(.dark-mode) .folder-strip-container.folder-strip-mobile{background:#f1f1f1!important;border:1px solid var(--fr-border-light)!important}
+body:not(.dark-mode) #customToast{background:#212121!important;color:#fff!important;border:1px solid #000!important;box-shadow:0 8px 18px rgba(0,0,0,.45)!important}
+body:not(.dark-mode) #fileSummary{color:var(--fr-muted-light)!important}
+body:not(.dark-mode) #createMenu,body:not(.dark-mode) .user-dropdown .user-menu,body:not(.dark-mode) #fileContextMenu,body:not(.dark-mode) #folderContextMenu,body:not(.dark-mode) #folderManagerContextMenu,body:not(.dark-mode) #adminPanelModal .modal-content,body:not(.dark-mode) #userPermissionsModal .modal-content,body:not(.dark-mode) #userFlagsModal .modal-content,body:not(.dark-mode) #userGroupsModal .modal-content,body:not(.dark-mode) #userPanelModal .modal-content,body:not(.dark-mode) #groupAclModal .modal-content,body:not(.dark-mode) .editor-modal,body:not(.dark-mode) #filePreviewModal .modal-content,body:not(.dark-mode) #loginForm,body:not(.dark-mode) .editor-header{background:var(--fr-surface-light)!important;border:1px solid var(--fr-border-light)!important;color:#111!important;border-radius:12px!important;box-shadow:0 4px 12px rgba(0,0,0,.12)!important}
+#searchIcon{display:inline-flex;align-items:center;justify-content:center;width:38px;height:36px;padding:0;border-radius:999px 0 0 999px;border:1px solid rgba(0,0,0,.18);border-right:none;background:#fff;cursor:pointer;box-shadow:none;transform:none}
+#searchIcon .material-icons{font-size:20px;line-height:1;color:#555}
+#searchIcon:hover{background:#f5f5f5}
+#searchIcon+#searchInput{height:36px;border-radius:0 999px 999px 0;border-left:none;padding-top:6px;padding-bottom:6px}
+body.dark-mode #searchIcon{background:#212121;border-color:#3d3d3d}
+body.dark-mode #searchIcon .material-icons{color:#f1f1f1}
+body.dark-mode #searchIcon:hover{background:#303030}
+body.dark-mode #searchIcon+#searchInput{border-left:none}
+#advancedSearchToggle{border-radius:999px;border:1px solid #d0d0d0;padding:6px 12px;font-size:.9rem;background:#f5f5f5;color:#333;cursor:pointer;display:inline-flex;align-items:center;gap:4px;margin-right:8px;transition:background .15s ease,box-shadow .15s ease,transform .1s ease}
+#advancedSearchToggle:hover,#advancedSearchToggle:focus-visible{background:#e8e8e8;box-shadow:0 1px 4px rgba(0,0,0,.16);outline:none;transform:translateY(-1px)}
+.dark-mode #advancedSearchToggle{background:#2a2a2a;border-color:#444;color:#f1f1f1}
+.dark-mode #advancedSearchToggle:hover,.dark-mode #advancedSearchToggle:focus-visible{background:#333;box-shadow:0 1px 4px rgba(0,0,0,.5)}
+.custom-prev-next-btn{display:inline-flex;align-items:center;justify-content:center;min-width:64px;padding:6px 14px;font-size:13px;font-weight:500;border-radius:999px;border:1px solid rgba(0,0,0,.14);background:#f1f1f1;color:#111;cursor:pointer;transition:background-color 140ms ease-out,border-color 140ms ease-out,box-shadow 140ms ease-out,transform 120ms ease-out}
+.custom-prev-next-btn:not(:disabled):hover{background:#e5e5e5;border-color:rgba(0,0,0,.22);box-shadow:0 2px 6px rgba(0,0,0,.18);transform:translateY(-1px)}
+.custom-prev-next-btn:not(:disabled):active{transform:translateY(0);box-shadow:0 1px 3px rgba(0,0,0,.25)}
+.custom-prev-next-btn:disabled{opacity:.5;cursor:default;box-shadow:none}
+body.dark-mode .custom-prev-next-btn{background:#212121;border-color:#3d3d3d;color:#f1f1f1}
+body.dark-mode .custom-prev-next-btn:not(:disabled):hover{background:#2a2a2a;border-color:#4a4a4a;box-shadow:0 2px 6px rgba(0,0,0,.7)}
+input[type=text]:not(#searchInput),input[type=password],input[type=email],input[type=url],input[type=number],textarea,select{border:1px solid rgba(148,163,184,.6)!important;border-radius:10px;background:#ffffff;box-sizing:border-box}
+#decreaseFont,#increaseFont{display:inline-flex;align-items:center;justify-content:center;margin-top:5px;height:24px;min-width:30px;padding:2px 8px;font-size:11px;font-weight:500;line-height:1;border-radius:999px;border:1px solid rgba(0,0,0,.16);background:#f5f5f5;color:#222;cursor:pointer;margin-left:4px;transition:background-color 140ms ease-out,border-color 140ms ease-out,box-shadow 140ms ease-out,transform 120ms ease-out}
+#decreaseFont:not(:disabled):hover,#increaseFont:not(:disabled):hover{background:#e8e8e8;border-color:rgba(0,0,0,.24);box-shadow:0 1px 4px rgba(0,0,0,.18);transform:translateY(-1px)}
+#decreaseFont:not(:disabled):active,#increaseFont:not(:disabled):active{transform:translateY(5px);box-shadow:0 1px 2px rgba(0,0,0,.25)}
+#decreaseFont:disabled,#increaseFont:disabled{opacity:.5;cursor:default;box-shadow:none}
+body.dark-mode #decreaseFont,body.dark-mode #increaseFont{background:#212121;border-color:#3d3d3d;color:#f1f1f1}
+body.dark-mode #decreaseFont:not(:disabled):hover,body.dark-mode #increaseFont:not(:disabled):hover{background:#2a2a2a;border-color:#4a4a4a;box-shadow:0 1px 4px rgba(0,0,0,.7)}
+#closeEditorX{margin-right:10px}
+#fileList .folder-row-icon .folder-front{fill:var(--filr-folder-front,#f6b84e);stroke:var(--filr-folder-stroke,#a87312);stroke-width:.5;stroke-linejoin:round;stroke-linecap:round}
+#fileList .folder-row-icon .folder-back{fill:var(--filr-folder-back,#fcd68a);stroke:var(--filr-folder-stroke,#a87312);stroke-width:.5;stroke-linejoin:round;stroke-linecap:round}
+#fileList .folder-row-icon .paper{fill:#fff;stroke:#b2c2db;stroke-width:1;vector-effect:non-scaling-stroke}
+#fileList .folder-row-icon .paper-fold{fill:#b2c2db}
+#fileList .folder-row-icon .paper-line{stroke:#b2c2db;stroke-width:1;stroke-linecap:round;fill:none;vector-effect:non-scaling-stroke}
+#fileList .folder-row-icon .paper-ink{stroke:#4da3ff;stroke-width:.9;stroke-linecap:round;stroke-linejoin:round;fill:none;opacity:.85}
+#fileList .folder-row-icon .lip-highlight{fill:none;vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round}
+#fileList .folder-row-name{font-weight:500;margin-right:4px}
+#fileList .folder-row-meta{margin-left:4px;opacity:.75;font-size:.86em}
+#fileList tbody tr.folder-row{height:var(--file-row-height,44px);cursor:pointer}
+#fileList tbody tr.folder-row .folder-name-cell{padding-top:0;padding-bottom:0}
+#fileList tbody tr.folder-row .folder-row-inner{cursor:inherit}
+#fileList tbody tr.folder-row .folder-icon-cell{text-align:left;vertical-align:middle}
+#fileList tbody tr.folder-row .folder-row-icon svg{display:block}
+.folder-row-icon{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin-right:8px;position:relative;left:-8px;top:5px}
+.folder-row-inner{display:flex;align-items:center}
+#fileList table.filr-table th.checkbox-col,#fileList table.filr-table td.checkbox-col,#fileList table.filr-table td.folder-icon-cell{width:30px!important;max-width:30px!important}
+#fileList tr.folder-row.folder-row-droptarget{background:var(--filr-accent-50,rgba(250,204,21,.12));box-shadow:inset 0 0 0 1px var(--filr-accent-400,rgba(250,204,21,.6))}
+#fileList tr.folder-row.folder-row-droptarget .folder-row-name{font-weight:600}
+#fileList table.filr-table tbody tr.folder-row>td{padding-top:0!important;padding-bottom:0!important}
+#fileList table.filr-table tbody tr.folder-row>td.folder-icon-cell{overflow:visible}
+#fileList tr.folder-row .folder-row-inner,#fileList tr.folder-row .folder-row-name{cursor:inherit}
\ No newline at end of file
diff --git a/public/js/appCore.js b/public/js/appCore.js
index 905b1c3..a373b20 100644
--- a/public/js/appCore.js
+++ b/public/js/appCore.js
@@ -90,7 +90,8 @@ export function initializeApp() {
window.currentFolder = last ? last : "root";
const stored = localStorage.getItem('showFoldersInList');
- window.showFoldersInList = stored === null ? true : stored === 'true';
+ // default: false (unchecked)
+ window.showFoldersInList = stored === 'true';
// Load public site config early (safe subset)
loadAdminConfigFunc();
@@ -99,6 +100,7 @@ export function initializeApp() {
initTagSearch();
+ /*
// Hook DnD relay from fileList area into upload area
const fileListArea = document.getElementById('fileList');
@@ -146,7 +148,7 @@ export function initializeApp() {
uploadArea.dispatchEvent(new Event('drop', { bubbles: true, cancelable: true }));
}
});
- }
+ }*/
// App subsystems
initDragAndDrop();
diff --git a/public/js/authModals.js b/public/js/authModals.js
index 1cc8868..1eebfbe 100644
--- a/public/js/authModals.js
+++ b/public/js/authModals.js
@@ -351,30 +351,73 @@ export async function openUserPanel() {
langFs.appendChild(langSel);
content.appendChild(langFs);
- // --- Display fieldset: “Show folders above files” ---
+ // --- Display fieldset: strip + inline folder rows ---
const dispFs = document.createElement('fieldset');
dispFs.style.marginBottom = '15px';
+
const dispLegend = document.createElement('legend');
dispLegend.textContent = t('display');
dispFs.appendChild(dispLegend);
- const dispLabel = document.createElement('label');
- dispLabel.style.cursor = 'pointer';
- const dispCb = document.createElement('input');
- dispCb.type = 'checkbox';
- dispCb.id = 'showFoldersInList';
- dispCb.style.verticalAlign = 'middle';
- const stored = localStorage.getItem('showFoldersInList');
- dispCb.checked = stored === null ? true : stored === 'true';
- dispLabel.appendChild(dispCb);
- dispLabel.append(` ${t('show_folders_above_files')}`);
- dispFs.appendChild(dispLabel);
+
+ // 1) Show folder strip above list
+ const stripLabel = document.createElement('label');
+ stripLabel.style.cursor = 'pointer';
+ stripLabel.style.display = 'block';
+ stripLabel.style.marginBottom = '4px';
+
+ const stripCb = document.createElement('input');
+ stripCb.type = 'checkbox';
+ stripCb.id = 'showFoldersInList';
+ stripCb.style.verticalAlign = 'middle';
+
+ {
+ const storedStrip = localStorage.getItem('showFoldersInList');
+ // default: unchecked
+ stripCb.checked = storedStrip === null ? false : storedStrip === 'true';
+ }
+
+ stripLabel.appendChild(stripCb);
+ stripLabel.append(` ${t('show_folders_above_files')}`);
+ dispFs.appendChild(stripLabel);
+
+ // 2) Show inline folder rows above files in table view
+ const inlineLabel = document.createElement('label');
+ inlineLabel.style.cursor = 'pointer';
+ inlineLabel.style.display = 'block';
+
+ const inlineCb = document.createElement('input');
+ inlineCb.type = 'checkbox';
+ inlineCb.id = 'showInlineFolders';
+ inlineCb.style.verticalAlign = 'middle';
+
+ {
+ const storedInline = localStorage.getItem('showInlineFolders');
+ inlineCb.checked = storedInline === null ? true : storedInline === 'true';
+ }
+
+ inlineLabel.appendChild(inlineCb);
+ // you’ll want a string like this in i18n:
+ // "show_inline_folders": "Show folders inline (above files)"
+ inlineLabel.append(` ${t('show_inline_folders') || 'Show folders inline (above files)'}`);
+ dispFs.appendChild(inlineLabel);
+
content.appendChild(dispFs);
- dispCb.addEventListener('change', () => {
- window.showFoldersInList = dispCb.checked;
- localStorage.setItem('showFoldersInList', dispCb.checked);
- // re‐load the entire file list (and strip) in one go:
- loadFileList(window.currentFolder);
+ // Handlers: toggle + refresh list
+ stripCb.addEventListener('change', () => {
+ window.showFoldersInList = stripCb.checked;
+ localStorage.setItem('showFoldersInList', stripCb.checked);
+ if (typeof window.loadFileList === 'function') {
+ window.loadFileList(window.currentFolder || 'root');
+ }
+ });
+
+ inlineCb.addEventListener('change', () => {
+ window.showInlineFolders = inlineCb.checked;
+ localStorage.setItem('showInlineFolders', inlineCb.checked);
+ if (typeof window.loadFileList === 'function') {
+ window.loadFileList(window.currentFolder || 'root');
+ }
});
// wire up image‐input change
@@ -425,6 +468,18 @@ export async function openUserPanel() {
modal.querySelector('#userTOTPEnabled').checked = totp_enabled;
modal.querySelector('#languageSelector').value = localStorage.getItem('language') || 'en';
modal.querySelector('h3').textContent = `${t('user_panel')} (${username})`;
+
+ // sync display toggles from localStorage
+ const stripCb = modal.querySelector('#showFoldersInList');
+ const inlineCb = modal.querySelector('#showInlineFolders');
+ if (stripCb) {
+ const storedStrip = localStorage.getItem('showFoldersInList');
+ stripCb.checked = storedStrip === null ? false : storedStrip === 'true';
+ }
+ if (inlineCb) {
+ const storedInline = localStorage.getItem('showInlineFolders');
+ inlineCb.checked = storedInline === null ? true : storedInline === 'true';
+ }
}
// show
diff --git a/public/js/domUtils.js b/public/js/domUtils.js
index 700856f..d8749a6 100644
--- a/public/js/domUtils.js
+++ b/public/js/domUtils.js
@@ -160,11 +160,11 @@ export function buildFileTableHeader(sortOrder) {
diff --git a/public/js/fileListView.js b/public/js/fileListView.js
index ee9aeed..4c1bb62 100644
--- a/public/js/fileListView.js
+++ b/public/js/fileListView.js
@@ -240,16 +240,29 @@ window.addEventListener('resize', () => {
if (strip) applyFolderStripLayout(strip);
});
-// Listen once: update strip + tree when folder color changes
+// Listen once: update strip + tree + inline rows when folder color changes
window.addEventListener('folderColorChanged', (e) => {
const { folder } = e.detail || {};
if (!folder) return;
- // Update the strip (if that folder is currently shown)
+ // 1) Update the strip (if that folder is currently shown)
repaintStripIcon(folder);
- // And refresh the tree icon too (existing function)
+ // 2) Refresh the tree icon (existing function)
try { refreshFolderIcon(folder); } catch { }
+
+ // 3) Repaint any inline folder rows in the file table
+ try {
+ const safeFolder = CSS.escape(folder);
+ document
+ .querySelectorAll(`#fileList tr.folder-row[data-folder="${safeFolder}"]`)
+ .forEach(row => {
+ // reuse the same helper we used when injecting inline rows
+ attachStripIconAsync(row, folder, 28);
+ });
+ } catch {
+ // CSS.escape might not exist on very old browsers; fail silently
+ }
});
// Hide "Edit" for files >10 MiB
@@ -259,11 +272,25 @@ const MAX_EDIT_BYTES = 10 * 1024 * 1024;
let __fileListReqSeq = 0;
window.itemsPerPage = parseInt(
- localStorage.getItem('itemsPerPage') || window.itemsPerPage || '10',
+ localStorage.getItem('itemsPerPage') || window.itemsPerPage || '50',
10
);
window.currentPage = window.currentPage || 1;
window.viewMode = localStorage.getItem("viewMode") || "table";
+window.currentSubfolders = window.currentSubfolders || [];
+
+// Default folder display settings from localStorage
+try {
+ const storedStrip = localStorage.getItem('showFoldersInList');
+ const storedInline = localStorage.getItem('showInlineFolders');
+
+ window.showFoldersInList = storedStrip === null ? true : storedStrip === 'true';
+ window.showInlineFolders = storedInline === null ? true : storedInline === 'true';
+} catch {
+ // if localStorage blows up, fall back to both enabled
+ window.showFoldersInList = true;
+ window.showInlineFolders = true;
+}
// Global flag for advanced search mode.
window.advancedSearchEnabled = false;
@@ -387,7 +414,6 @@ function attachStripIconAsync(hostEl, fullPath, size = 28) {
const back = _lighten(hex, 14);
const stroke = _darken(hex, 22);
- // apply vars on the tile (or icon span)
hostEl.style.setProperty('--filr-folder-front', front);
hostEl.style.setProperty('--filr-folder-back', back);
hostEl.style.setProperty('--filr-folder-stroke', stroke);
@@ -395,15 +421,26 @@ function attachStripIconAsync(hostEl, fullPath, size = 28) {
const iconSpan = hostEl.querySelector('.folder-svg');
if (!iconSpan) return;
+ // 1) initial "empty" icon
iconSpan.dataset.kind = 'empty';
- iconSpan.innerHTML = folderSVG('empty'); // size is baked into viewBox; add a size arg if you prefer
+ iconSpan.innerHTML = folderSVG('empty');
+
+ // make sure this brand-new SVG is sized correctly
+ try { syncFolderIconSizeToRowHeight(); } catch {}
+
const url = `/api/folder/isEmpty.php?folder=${encodeURIComponent(fullPath)}&t=${Date.now()}`;
- _fetchJSONWithTimeout(url, 2500).then(({ folders = 0, files = 0 }) => {
- if ((folders + files) > 0 && iconSpan.dataset.kind !== 'paper') {
- iconSpan.dataset.kind = 'paper';
- iconSpan.innerHTML = folderSVG('paper');
- }
- }).catch(() => { });
+ _fetchJSONWithTimeout(url, 2500)
+ .then(({ folders = 0, files = 0 }) => {
+ if ((folders + files) > 0 && iconSpan.dataset.kind !== 'paper') {
+ // 2) swap to "paper" icon
+ iconSpan.dataset.kind = 'paper';
+ iconSpan.innerHTML = folderSVG('paper');
+
+ // re-apply sizing to this new SVG too
+ try { syncFolderIconSizeToRowHeight(); } catch {}
+ }
+ })
+ .catch(() => { /* ignore */ });
}
/* -----------------------------
@@ -426,6 +463,56 @@ async function safeJson(res) {
}
return body ?? {};
}
+
+// --- Folder capabilities + owner cache ----------------------
+const _folderCapsCache = new Map();
+
+async function fetchFolderCaps(folder) {
+ if (!folder) return null;
+ if (_folderCapsCache.has(folder)) {
+ return _folderCapsCache.get(folder);
+ }
+ try {
+ const res = await fetch(
+ `/api/folder/capabilities.php?folder=${encodeURIComponent(folder)}`,
+ { credentials: 'include' }
+ );
+ const data = await safeJson(res);
+ _folderCapsCache.set(folder, data || null);
+
+ if (data && (data.owner || data.user)) {
+ _folderOwnerCache.set(folder, data.owner || data.user || "");
+ }
+ return data || null;
+ } catch {
+ _folderCapsCache.set(folder, null);
+ return null;
+ }
+}
+
+// --- Folder owner cache + helper ----------------------
+const _folderOwnerCache = new Map();
+
+async function fetchFolderOwner(folder) {
+ if (!folder) return "";
+ if (_folderOwnerCache.has(folder)) {
+ return _folderOwnerCache.get(folder);
+ }
+
+ try {
+ const res = await fetch(
+ `/api/folder/capabilities.php?folder=${encodeURIComponent(folder)}`,
+ { credentials: 'include' }
+ );
+ const data = await safeJson(res);
+ const owner = data && (data.owner || data.user || "");
+ _folderOwnerCache.set(folder, owner || "");
+ return owner || "";
+ } catch {
+ _folderOwnerCache.set(folder, "");
+ return "";
+ }
+}
// ---- Viewed badges (table + gallery) ----
// ---------- Badge factory (center text vertically) ----------
function makeBadge(state) {
@@ -917,6 +1004,13 @@ export async function loadFileList(folderParam) {
document.documentElement.style.setProperty("--file-row-height", v + "px");
localStorage.setItem("rowHeight", v);
rowValue.textContent = v + "px";
+ // mark compact mode for very low heights
+ if (v <= 32) {
+ document.documentElement.setAttribute('data-row-compact', '1');
+ } else {
+ document.documentElement.removeAttribute('data-row-compact');
+ }
+ syncFolderIconSizeToRowHeight();
};
}
}
@@ -966,6 +1060,9 @@ export async function loadFileList(folderParam) {
return !hidden.has(lower) && !lower.startsWith("resumable_");
});
+ // Expose for inline folder rows in table view
+ window.currentSubfolders = subfolders;
+
let strip = document.getElementById("folderStripContainer");
if (!strip) {
strip = document.createElement("div");
@@ -976,6 +1073,11 @@ export async function loadFileList(folderParam) {
// NEW: paged + responsive strip
renderFolderStripPaged(strip, subfolders);
+
+ // Re-render table view once folders are known so they appear inline above files
+ if (window.viewMode === "table" && reqId === __fileListReqSeq) {
+ renderFileTable(folder);
+ }
} catch {
// ignore folder errors; rows already rendered
}
@@ -1000,24 +1102,456 @@ export async function loadFileList(folderParam) {
}
}
+
+function injectInlineFolderRows(fileListContent, folder, pageSubfolders) {
+ const table = fileListContent.querySelector('table.filr-table');
+
+ // Use the paged subfolders if provided, otherwise fall back to all
+ const subfolders = Array.isArray(pageSubfolders) && pageSubfolders.length
+ ? pageSubfolders
+ : (Array.isArray(window.currentSubfolders) ? window.currentSubfolders : []);
+
+ if (!table || !subfolders.length) return;
+
+ const thead = table.tHead;
+ const tbody = table.tBodies && table.tBodies[0];
+ if (!thead || !tbody) return;
+
+ const headerRow = thead.rows[0];
+ if (!headerRow) return;
+
+ const headerCells = Array.from(headerRow.cells);
+ const colCount = headerCells.length;
+
+ // --- Column indices -------------------------------------------------------
+ let checkboxIdx = headerCells.findIndex(th =>
+ th.classList.contains("checkbox-col") ||
+ th.querySelector('input[type="checkbox"]')
+ );
+
+ let nameIdx = headerCells.findIndex(th =>
+ (th.dataset && th.dataset.column === "name") ||
+ /\bname\b/i.test((th.textContent || "").trim())
+ );
+ if (nameIdx < 0) {
+ nameIdx = Math.min(1, colCount - 1); // fallback to 2nd col
+ }
+
+ let sizeIdx = headerCells.findIndex(th =>
+ (th.dataset && (th.dataset.column === "size" || th.dataset.column === "filesize")) ||
+ /\bsize\b/i.test((th.textContent || "").trim())
+ );
+ if (sizeIdx < 0) sizeIdx = -1;
+
+ let uploaderIdx = headerCells.findIndex(th =>
+ (th.dataset && th.dataset.column === "uploader") ||
+ /\buploader\b/i.test((th.textContent || "").trim())
+ );
+ if (uploaderIdx < 0) uploaderIdx = -1;
+
+ let actionsIdx = headerCells.findIndex(th =>
+ (th.dataset && th.dataset.column === "actions") ||
+ /\bactions?\b/i.test((th.textContent || "").trim()) ||
+ /\bactions?-col\b/i.test(th.className || "")
+ );
+ if (actionsIdx < 0) actionsIdx = -1;
+
+ // Remove any previous folder rows
+ tbody.querySelectorAll("tr.folder-row").forEach(tr => tr.remove());
+
+
+
+ const firstDataRow = tbody.firstElementChild;
+
+ subfolders.forEach(sf => {
+ const tr = document.createElement("tr");
+ tr.classList.add("folder-row");
+ tr.dataset.folder = sf.full;
+
+ for (let i = 0; i < colCount; i++) {
+ const td = document.createElement("td");
+
+// *** copy header classes so responsive breakpoints match file rows ***
+// but strip Bootstrap margin helpers (ml-2 / mx-2) so we don't get a big gap
+const headerClass = headerCells[i] && headerCells[i].className;
+if (headerClass) {
+ td.className = headerClass;
+ td.classList.remove("ml-2", "mx-2");
+}
+
+ // 1) icon / checkbox column
+ if (i === checkboxIdx) {
+ td.classList.add("folder-icon-cell");
+ td.style.textAlign = "left";
+ td.style.verticalAlign = "middle";
+
+ const iconSpan = document.createElement("span");
+ iconSpan.className = "folder-svg folder-row-icon";
+ td.appendChild(iconSpan);
+
+ // 2) name column
+ } else if (i === nameIdx) {
+ td.classList.add("name-cell", "file-name-cell", "folder-name-cell");
+
+ const wrap = document.createElement("div");
+ wrap.className = "folder-row-inner";
+
+ const nameSpan = document.createElement("span");
+ nameSpan.className = "folder-row-name";
+ nameSpan.textContent = sf.name || sf.full;
+
+ const metaSpan = document.createElement("span");
+ metaSpan.className = "folder-row-meta";
+ metaSpan.textContent = ""; // "(15 folders, 19 files)" later
+
+ wrap.appendChild(nameSpan);
+ wrap.appendChild(metaSpan);
+ td.appendChild(wrap);
+
+ // 3) size column
+ } else if (i === sizeIdx) {
+ td.classList.add("folder-size-cell");
+ td.textContent = "…"; // placeholder until we load stats
+
+ // 4) uploader / owner column
+ } else if (i === uploaderIdx) {
+ td.classList.add("uploader-cell", "folder-uploader-cell");
+ td.textContent = ""; // filled asynchronously with owner
+
+ // 5) actions column
+ } else if (i === actionsIdx) {
+ td.classList.add("folder-actions-cell");
+
+ const group = document.createElement("div");
+ group.className = "btn-group btn-group-sm folder-actions-group";
+ group.setAttribute("role", "group");
+group.setAttribute("aria-label", "File actions");
+
+const makeActionBtn = (iconName, titleKey, btnClass, actionKey, handler) => {
+ const btn = document.createElement("button");
+ btn.type = "button";
+
+ // base classes – same size as file actions
+ btn.className = `btn ${btnClass} py-1`;
+
+ // kill any Bootstrap margin helpers that got passed in
+ btn.classList.remove("ml-2", "mx-2");
+
+ btn.setAttribute("data-folder-action", actionKey);
+ btn.setAttribute("data-i18n-title", titleKey);
+ btn.title = t(titleKey);
+
+ const icon = document.createElement("i");
+ icon.className = "material-icons";
+ icon.textContent = iconName;
+ btn.appendChild(icon);
+
+ btn.addEventListener("click", e => {
+ e.stopPropagation();
+ window.currentFolder = sf.full;
+ try { localStorage.setItem("lastOpenedFolder", sf.full); } catch {}
+ handler();
+ });
+
+ // start disabled; caps logic will enable
+ btn.disabled = true;
+ btn.style.pointerEvents = "none";
+ btn.style.opacity = "0.5";
+
+ group.appendChild(btn);
+};
+
+makeActionBtn("drive_file_move", "move_folder", "btn-warning folder-move-btn", "move", () => openMoveFolderUI());
+makeActionBtn("palette", "color_folder", "btn-color-folder","color", () => openColorFolderModal(sf.full));
+makeActionBtn("drive_file_rename_outline", "rename_folder", "btn-warning folder-rename-btn", "rename", () => openRenameFolderModal());
+makeActionBtn("share", "share_folder", "btn-secondary", "share", () => openFolderShareModal(sf.full));
+
+ td.appendChild(group);
+ }
+
+ // IMPORTANT: always append the cell, no matter which column we're in
+ tr.appendChild(td);
+ }
+
+ // click → navigate, same as before
+ tr.addEventListener("click", e => {
+ if (e.button !== 0) return;
+ const dest = sf.full;
+ if (!dest) return;
+
+ window.currentFolder = dest;
+ try { localStorage.setItem("lastOpenedFolder", dest); } catch { }
+
+ updateBreadcrumbTitle(dest);
+
+ document.querySelectorAll(".folder-option.selected")
+ .forEach(o => o.classList.remove("selected"));
+ const treeNode = document.querySelector(
+ `.folder-option[data-folder="${CSS.escape(dest)}"]`
+ );
+ if (treeNode) treeNode.classList.add("selected");
+
+ const strip = document.getElementById("folderStripContainer");
+ if (strip) {
+ strip.querySelectorAll(".folder-item.selected")
+ .forEach(i => i.classList.remove("selected"));
+ const stripItem = strip.querySelector(
+ `.folder-item[data-folder="${CSS.escape(dest)}"]`
+ );
+ if (stripItem) stripItem.classList.add("selected");
+ }
+
+ loadFileList(dest);
+ });
+
+
+ // DnD + context menu – keep existing logic, but also add a visual highlight
+ tr.addEventListener("dragover", e => {
+ folderDragOverHandler(e);
+ tr.classList.add("folder-row-droptarget");
+ });
+
+ tr.addEventListener("dragleave", e => {
+ folderDragLeaveHandler(e);
+ tr.classList.remove("folder-row-droptarget");
+ });
+
+ tr.addEventListener("drop", e => {
+ folderDropHandler(e);
+ tr.classList.remove("folder-row-droptarget");
+ });
+
+ tr.addEventListener("contextmenu", e => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const dest = sf.full;
+ if (!dest) return;
+
+ window.currentFolder = dest;
+ try { localStorage.setItem("lastOpenedFolder", dest); } catch { }
+
+ const menuItems = [
+ { label: t("create_folder"), action: () => document.getElementById("createFolderModal").style.display = "block" },
+ { label: t("move_folder"), action: () => openMoveFolderUI() },
+ { label: t("rename_folder"), action: () => openRenameFolderModal() },
+ { label: t("color_folder"), action: () => openColorFolderModal(dest) },
+ { label: t("folder_share"), action: () => openFolderShareModal(dest) },
+ { label: t("delete_folder"), action: () => openDeleteFolderModal() }
+ ];
+ showFolderManagerContextMenu(e.pageX, e.pageY, menuItems);
+ });
+
+ // insert row above first file row
+ tbody.insertBefore(tr, firstDataRow || null);
+
+ // ----- ICON: color + alignment (size is driven by row height) -----
+attachStripIconAsync(tr, sf.full);
+const iconSpan = tr.querySelector(".folder-row-icon");
+if (iconSpan) {
+ iconSpan.style.display = "inline-flex";
+ iconSpan.style.alignItems = "center";
+ iconSpan.style.justifyContent = "flex-start";
+ iconSpan.style.marginLeft = "0px"; // small left nudge
+ iconSpan.style.marginTop = "0px"; // small down nudge
+}
+
+ // ----- FOLDER STATS + OWNER + CAPS (keep your existing code below here) -----
+ const sizeCellIndex = (sizeIdx >= 0 && sizeIdx < tr.cells.length) ? sizeIdx : -1;
+ const nameCellIndex = (nameIdx >= 0 && nameIdx < tr.cells.length) ? nameIdx : -1;
+
+ const url = `/api/folder/isEmpty.php?folder=${encodeURIComponent(sf.full)}&t=${Date.now()}`;
+ _fetchJSONWithTimeout(url, 2500).then(stats => {
+ if (!stats) return;
+
+ const foldersCount = Number.isFinite(stats.folders) ? stats.folders : 0;
+ const filesCount = Number.isFinite(stats.files) ? stats.files : 0;
+ const bytes = Number.isFinite(stats.bytes)
+ ? stats.bytes
+ : (Number.isFinite(stats.sizeBytes) ? stats.sizeBytes : null);
+
+ let pieces = [];
+ if (foldersCount) pieces.push(`${foldersCount} folder${foldersCount === 1 ? "" : "s"}`);
+ if (filesCount) pieces.push(`${filesCount} file${filesCount === 1 ? "" : "s"}`);
+ if (!pieces.length) pieces.push("0 items");
+ const countLabel = pieces.join(", ");
+
+ if (nameCellIndex >= 0) {
+ const nameCell = tr.cells[nameCellIndex];
+ if (nameCell) {
+ const metaSpan = nameCell.querySelector(".folder-row-meta");
+ if (metaSpan) metaSpan.textContent = ` (${countLabel})`;
+ }
+ }
+
+ if (sizeCellIndex >= 0) {
+ const sizeCell = tr.cells[sizeCellIndex];
+ if (sizeCell) {
+ let sizeLabel = "—";
+ if (bytes != null && bytes >= 0) {
+ sizeLabel = formatSize(bytes);
+ }
+ sizeCell.textContent = sizeLabel;
+ sizeCell.title = `${countLabel}${bytes != null && bytes >= 0 ? " • " + sizeLabel : ""}`;
+ }
+ }
+ }).catch(() => {
+ if (sizeCellIndex >= 0) {
+ const sizeCell = tr.cells[sizeCellIndex];
+ if (sizeCell && !sizeCell.textContent) sizeCell.textContent = "—";
+ }
+ });
+
+ // OWNER + action permissions
+ if (uploaderIdx >= 0 || actionsIdx >= 0) {
+ fetchFolderCaps(sf.full).then(caps => {
+ if (!caps || !document.body.contains(tr)) return;
+
+ if (uploaderIdx >= 0 && uploaderIdx < tr.cells.length) {
+ const uploaderCell = tr.cells[uploaderIdx];
+ if (uploaderCell) {
+ const owner = caps.owner || caps.user || "";
+ uploaderCell.textContent = owner || "";
+ }
+ }
+
+ if (actionsIdx >= 0 && actionsIdx < tr.cells.length) {
+ const actCell = tr.cells[actionsIdx];
+ if (!actCell) return;
+
+ actCell.querySelectorAll('button[data-folder-action]').forEach(btn => {
+ const action = btn.getAttribute('data-folder-action');
+ let enabled = false;
+ switch (action) {
+ case "move":
+ enabled = !!caps.canMoveFolder;
+ break;
+ case "color":
+ enabled = !!caps.canRename; // same gate as tree “color” button
+ break;
+ case "rename":
+ enabled = !!caps.canRename;
+ break;
+ case "share":
+ enabled = !!caps.canShareFolder;
+ break;
+ }
+ if (enabled === undefined) {
+ enabled = true; // fallback so admin still gets buttons even if a flag is missing
+ }
+ if (enabled) {
+ btn.disabled = false;
+ btn.style.pointerEvents = "";
+ btn.style.opacity = "";
+ } else {
+ btn.disabled = true;
+ btn.style.pointerEvents = "none";
+ btn.style.opacity = "0.5";
+ }
+ });
+ }
+ }).catch(() => { /* ignore */ });
+ }
+ });
+ syncFolderIconSizeToRowHeight();
+}
+function syncFolderIconSizeToRowHeight() {
+ const cs = getComputedStyle(document.documentElement);
+ const raw = cs.getPropertyValue('--file-row-height') || '48px';
+ const rowH = parseInt(raw, 10) || 60;
+
+ const FUDGE = 5;
+ const MAX_GROWTH_ROW = 44; // after this, stop growing the icon
+
+ const BASE_ROW_FOR_OFFSET = 40; // where icon looks centered
+ const OFFSET_FACTOR = 0.25;
+
+ // cap growth for size, like you already do
+ const effectiveRow = Math.min(rowH, MAX_GROWTH_ROW);
+
+ const boxSize = Math.max(25, Math.min(35, effectiveRow - 20 + FUDGE));
+ const scale = 1.20;
+
+ // use your existing offset curve
+ const clampedForOffset = Math.max(30, Math.min(60, rowH));
+ let offsetY = (clampedForOffset - BASE_ROW_FOR_OFFSET) * OFFSET_FACTOR;
+
+ // 30–44: untouched (you said this range is perfect)
+ // 45–60: same curve, but shifted up slightly
+ if (rowH > 53) {
+ offsetY -= 3;
+ }
+
+ document.querySelectorAll('#fileList .folder-row-icon').forEach(iconSpan => {
+ iconSpan.style.width = boxSize + 'px';
+ iconSpan.style.height = boxSize + 'px';
+ iconSpan.style.overflow = 'visible';
+
+ const svg = iconSpan.querySelector('svg');
+ if (!svg) return;
+
+ svg.setAttribute('width', String(boxSize));
+ svg.setAttribute('height', String(boxSize));
+ svg.style.transformOrigin = 'left center';
+ svg.style.transform = `translateY(${offsetY}px) scale(${scale})`;
+ });
+}
/**
* Render table view
*/
export function renderFileTable(folder, container, subfolders) {
const fileListContent = container || document.getElementById("fileList");
const searchTerm = (window.currentSearchTerm || "").toLowerCase();
- const itemsPerPageSetting = parseInt(localStorage.getItem("itemsPerPage") || "10", 10);
+ const itemsPerPageSetting = parseInt(localStorage.getItem("itemsPerPage") || "50", 10);
let currentPage = window.currentPage || 1;
+ // Files (filtered by search)
const filteredFiles = searchFiles(searchTerm);
- const totalFiles = filteredFiles.length;
- const totalPages = Math.ceil(totalFiles / itemsPerPageSetting);
+ // Inline folders: sort once (Explorer-style A→Z)
+ const allSubfolders = Array.isArray(window.currentSubfolders)
+ ? window.currentSubfolders
+ : [];
+ const subfoldersSorted = [...allSubfolders].sort((a, b) =>
+ (a.name || "").localeCompare(b.name || "", undefined, { sensitivity: "base" })
+ );
+
+ const totalFiles = filteredFiles.length;
+ const totalFolders = subfoldersSorted.length;
+ const totalRows = totalFiles + totalFolders;
+ const hasFolders = totalFolders > 0;
+
+ // Pagination is now over (folders + files)
+ const totalPages = totalRows > 0
+ ? Math.ceil(totalRows / itemsPerPageSetting)
+ : 1;
+
if (currentPage > totalPages) {
- currentPage = totalPages > 0 ? totalPages : 1;
+ currentPage = totalPages;
window.currentPage = currentPage;
}
+ const startRow = (currentPage - 1) * itemsPerPageSetting;
+ const endRow = Math.min(startRow + itemsPerPageSetting, totalRows);
+
+ // Figure out which folders + files belong to THIS page
+ const pageFolders = [];
+ const pageFiles = [];
+
+ for (let rowIndex = startRow; rowIndex < endRow; rowIndex++) {
+ if (rowIndex < totalFolders) {
+ pageFolders.push(subfoldersSorted[rowIndex]);
+ } else {
+ const fileIdx = rowIndex - totalFolders;
+ const file = filteredFiles[fileIdx];
+ if (file) pageFiles.push(file);
+ }
+ }
+
+ // Stable id per file row on this page
+ const rowIdFor = (file, idx) =>
+ `${encodeURIComponent(file.name)}-p${currentPage}-${idx}`;
+
// We pass a harmless "base" string to keep buildFileTableRow happy,
// then we will FIX the preview/thumbnail URLs to the API below.
const fakeBase = "#/";
@@ -1040,19 +1574,16 @@ export function renderFileTable(folder, container, subfolders) {
return `
- ${t("file_name")} ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""}
-
-
-
-
+ ${t("name")} ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""}
+
+
+
+
${t("actions")}
| )([\s\S]*?)(<\/td>)/, (m, open, inner, close) => { - // keep the original filename content, then add your tag badges, then close return `${open}${inner}${tagBadgesHTML}${close}`; } ); }); - } else { - rowsHTML += ` | |||||||
| No files found. | |||||||
| ${t("no_files_found") || "No files found."} | |||||||