Skip to content

Commit 5ea34e4

Browse files
authored
release(v2.13.0): inline rename + video preview limits + folder tree perf (see #79)
1 parent d12c3eb commit 5ea34e4

17 files changed

Lines changed: 1044 additions & 93 deletions

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,44 @@
11
# Changelog
22

3+
## Changes 12/30/2025 (v2.13.0)
4+
5+
`release(v2.13.0): inline rename + video preview limits + folder tree perf (see #79)`
6+
7+
**Added**
8+
9+
- **Inline rename**:
10+
- File list inline rename (table view) + context-menu support
11+
- Folder tree inline rename (tree context menu + Rename button)
12+
- Keyboard shortcuts: **F2 rename**, plus **Ctrl/Cmd+Shift+N** (new folder)
13+
- **Admin Panel setting (Display):** Hover preview max video size (MB)
14+
- Applies to hover previews + Gallery video thumbnails
15+
- Folder APIs:
16+
- `GET /api/folder/getFolderList.php?counts=0` to skip metadata count reads (faster on large trees)
17+
- `GET /api/folder/listChildren.php?probe=0` to skip per-child “has subfolders / non-empty” probing (faster)
18+
19+
**Changed**
20+
21+
- Hover previews & Gallery video thumbs:
22+
- Use the new video size limit setting
23+
- Improved “no preview available” fallback when a frame can’t be decoded quickly
24+
- File streaming:
25+
- Improved HTTP Range support (incl. suffix ranges like `bytes=-500`)
26+
- Expanded safe inline rendering to allowlisted video/audio types when requested (`inline=1`)
27+
28+
**Fixed**
29+
30+
- Context menus now position correctly using `clientX/clientY` (more reliable across layouts).
31+
- Blank folder icons are repaired after drag/drop moves and dual-pane refreshes.
32+
- Admin “Folder Access” modal help text is now collapsible (“More/Less”) for readability.
33+
34+
**Performance**
35+
36+
- Reduced IO for large installs by:
37+
- Avoiding folder count reads when not needed (`counts=0`)
38+
- Avoiding directory iterator probes when not needed (`probe=0`)
39+
40+
---
41+
342
## Changes 12/29/2025 (v2.12.1)
443

544
`release(v2.12.1): folder summary depth + video thumbnails + dual-pane toggle fix`

public/api/folder/listChildren.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141

4242
$limit = max(1, min(2000, (int)($_GET['limit'] ?? 500)));
4343
$cursor = isset($_GET['cursor']) && $_GET['cursor'] !== '' ? (string)$_GET['cursor'] : null;
44+
$probeRaw = $_GET['probe'] ?? null;
45+
$probe = true;
46+
if ($probeRaw !== null) {
47+
$pv = strtolower((string)$probeRaw);
48+
if ($pv === '0' || $pv === 'false' || $pv === 'no') $probe = false;
49+
}
4450

45-
$res = FolderController::listChildren($folder, $username, $perms, $cursor, $limit);
51+
$res = FolderController::listChildren($folder, $username, $perms, $cursor, $limit, $probe);
4652
echo json_encode($res, JSON_UNESCAPED_SLASHES);

public/css/styles.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,24 @@ body:not(.dark-mode) .material-icons.pauseResumeBtn:hover{background-color: rgba
803803
:is(#fileList, #fileListSecondary) table.filr-table tbody tr:focus-within > td{outline: none;}
804804
:is(#fileList, #fileListSecondary) table.filr-table tbody tr:focus-within > td:first-child,
805805
:is(#fileList, #fileListSecondary) table.filr-table tbody tr:focus-within > td:last-child{outline: 2px solid #8ab4f8; outline-offset: -2px;}
806+
:is(#fileList, #fileListSecondary) table.filr-table tbody tr.inline-rename-active:focus-within > td{
807+
border-radius: 0;
808+
}
809+
:is(#fileList, #fileListSecondary) table.filr-table tbody tr.inline-rename-active:focus-within > td:first-child{
810+
border-top-left-radius: 8px;
811+
border-bottom-left-radius: 8px;
812+
}
813+
:is(#fileList, #fileListSecondary) table.filr-table tbody tr.inline-rename-active:focus-within > td:last-child{
814+
border-top-right-radius: 8px;
815+
border-bottom-right-radius: 8px;
816+
}
817+
:is(#fileList, #fileListSecondary) table.filr-table tbody tr.inline-rename-active:focus-within > td:first-child,
818+
:is(#fileList, #fileListSecondary) table.filr-table tbody tr.inline-rename-active:focus-within > td:last-child{
819+
outline: none;
820+
}
821+
:is(#fileList, #fileListSecondary) table.filr-table tbody tr.inline-rename-active > td{
822+
box-shadow: none !important;
823+
}
806824
#fileListTitle,
807825
#fileListTitleSecondary,
808826
.file-list-title{white-space: normal !important;

public/js/adminFolderAccess.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ let __allFoldersCache = null;
117117
async function getAllFolders(force = false) {
118118
if (!force && __allFoldersCache) return __allFoldersCache.slice();
119119

120-
const res = await fetch('/api/folder/getFolderList.php?ts=' + Date.now(), {
120+
const res = await fetch('/api/folder/getFolderList.php?counts=0&ts=' + Date.now(), {
121121
credentials: 'include',
122122
cache: 'no-store',
123123
headers: { 'Cache-Control': 'no-store' }
@@ -940,7 +940,13 @@ export function openUserPermissionsModal(initialUser = null) {
940940
<span id="closeUserPermissionsModal" class="editor-close-btn">&times;</span>
941941
<h3>${tf("folder_access", "Folder Access")}</h3>
942942
<div class="muted" style="margin:-4px 0 10px;">
943-
${tf("grant_folders_help", "Grant per-folder capabilities to each user. View (all) shows all contents; View (own) shows only the user's uploads. Write is file-level ops (upload/edit/rename/copy/delete/extract). Create is file-only; subfolders require Manage/Ownership. Manage/Ownership enables folder actions (create/rename/move/delete, grant access) and implies View (all), Write, and Share. Share File auto-enables View (own); Share Folder requires Manage/Ownership + View (all).")}
943+
<span class="grant-help-short">${tf("grant_folders_help_short", "Per-folder access. Create is file-only; subfolders need Manage/Ownership. Share Folder needs Manage + View (all).")}</span>
944+
<button type="button" class="btn btn-link btn-sm p-0 grant-help-toggle" aria-expanded="false" style="margin-left:6px;">
945+
${tf("help_more", "More")}
946+
</button>
947+
<span class="grant-help-full" style="display:none;">
948+
${tf("grant_folders_help", "Grant per-folder capabilities to each user. View (all) shows all contents; View (own) shows only the user's uploads. Write is file-level ops (upload/edit/rename/copy/delete/extract). Create is file-only; subfolders require Manage/Ownership. Manage/Ownership enables folder actions (create/rename/move/delete, grant access) and implies View (all), Write, and Share. Share File auto-enables View (own); Share Folder requires Manage/Ownership + View (all).")}
949+
</span>
944950
</div>
945951
<div id="userPermissionsList" style="max-height: 82vh; min-height: 420px; overflow-y: auto; margin-bottom: 15px;">
946952
</div>
@@ -982,6 +988,19 @@ export function openUserPermissionsModal(initialUser = null) {
982988
showToast(tf("error_updating_permissions", "Error updating permissions"), "error");
983989
}
984990
});
991+
const helpToggle = userPermissionsModal.querySelector('.grant-help-toggle');
992+
if (helpToggle) {
993+
helpToggle.addEventListener('click', () => {
994+
const expanded = helpToggle.getAttribute('aria-expanded') === 'true';
995+
const next = !expanded;
996+
const shortText = userPermissionsModal.querySelector('.grant-help-short');
997+
const fullText = userPermissionsModal.querySelector('.grant-help-full');
998+
if (shortText) shortText.style.display = next ? 'none' : 'inline';
999+
if (fullText) fullText.style.display = next ? 'inline' : 'none';
1000+
helpToggle.setAttribute('aria-expanded', next ? 'true' : 'false');
1001+
helpToggle.textContent = next ? tf('help_less', 'Less') : tf('help_more', 'More');
1002+
});
1003+
}
9851004
} else {
9861005
userPermissionsModal.style.display = "flex";
9871006
}

public/js/adminPanel.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,7 @@ function captureInitialAdminConfig() {
11041104
brandingHeaderBgDark: (document.getElementById("brandingHeaderBgDark")?.value || "").trim(),
11051105
brandingFooterHtml: (document.getElementById("brandingFooterHtml")?.value || "").trim(),
11061106
hoverPreviewMaxImageMb: (document.getElementById("hoverPreviewMaxImageMb")?.value || "").trim(),
1107+
hoverPreviewMaxVideoMb: (document.getElementById("hoverPreviewMaxVideoMb")?.value || "").trim(),
11071108
fileListSummaryDepth: (document.getElementById("fileListSummaryDepth")?.value || "").trim(),
11081109

11091110
clamavScanUploads: !!document.getElementById("clamavScanUploads")?.checked,
@@ -1145,6 +1146,7 @@ function hasUnsavedChanges() {
11451146
getVal("brandingHeaderBgDark") !== (o.brandingHeaderBgDark || "") ||
11461147
getVal("brandingFooterHtml") !== (o.brandingFooterHtml || "") ||
11471148
getVal("hoverPreviewMaxImageMb") !== (o.hoverPreviewMaxImageMb || "") ||
1149+
getVal("hoverPreviewMaxVideoMb") !== (o.hoverPreviewMaxVideoMb || "") ||
11481150
getVal("fileListSummaryDepth") !== (o.fileListSummaryDepth || "") ||
11491151
getChk("clamavScanUploads") !== o.clamavScanUploads ||
11501152
getChk("proSearchEnabled") !== o.proSearchEnabled ||
@@ -2232,6 +2234,10 @@ export function openAdminPanel() {
22322234
1,
22332235
Math.min(50, parseInt(displayCfg.hoverPreviewMaxImageMb || 8, 10) || 8)
22342236
);
2237+
const hoverPreviewMaxVideoMb = Math.max(
2238+
1,
2239+
Math.min(2048, parseInt(displayCfg.hoverPreviewMaxVideoMb || 200, 10) || 200)
2240+
);
22352241
const rawSummaryDepth = parseInt(displayCfg.fileListSummaryDepth, 10);
22362242
const fileListSummaryDepth = Math.max(
22372243
0,
@@ -2503,7 +2509,7 @@ export function openAdminPanel() {
25032509
</div>
25042510
</label>
25052511
<small class="text-muted d-block mb-1">
2506-
${tf("hover_preview_max_image_help", "Applies to hover previews and gallery thumbnails; larger values can increase bandwidth and memory use.")}
2512+
${tf("hover_preview_max_image_help", "Applies to hover previews and gallery thumbnails. Default 8 MB; higher values increase bandwidth and memory use.")}
25072513
</small>
25082514
<input
25092515
type="number"
@@ -2516,6 +2522,27 @@ export function openAdminPanel() {
25162522
/>
25172523
</div>
25182524
2525+
<!-- Display: Hover preview max video size -->
2526+
<div class="form-group" style="margin-top:16px;">
2527+
<label for="hoverPreviewMaxVideoMb">
2528+
<div class="admin-subsection-title" style="margin-top:2px;">
2529+
${tf("hover_preview_max_video_mb", "Hover preview max video size (MB)")}
2530+
</div>
2531+
</label>
2532+
<small class="text-muted d-block mb-1">
2533+
${tf("hover_preview_max_video_help", "Applies to hover previews and gallery thumbnails. Default 200 MB; higher values can increase bandwidth on large videos.")}
2534+
</small>
2535+
<input
2536+
type="number"
2537+
id="hoverPreviewMaxVideoMb"
2538+
class="form-control"
2539+
min="1"
2540+
max="2048"
2541+
step="1"
2542+
value="${hoverPreviewMaxVideoMb}"
2543+
/>
2544+
</div>
2545+
25192546
<!-- Display: File list summary depth -->
25202547
<div class="form-group" style="margin-top:16px;">
25212548
<label for="fileListSummaryDepth">
@@ -4102,6 +4129,13 @@ function handleSave() {
41024129
parseInt(document.getElementById("hoverPreviewMaxImageMb")?.value || "8", 10) || 8
41034130
)
41044131
),
4132+
hoverPreviewMaxVideoMb: Math.max(
4133+
1,
4134+
Math.min(
4135+
2048,
4136+
parseInt(document.getElementById("hoverPreviewMaxVideoMb")?.value || "200", 10) || 200
4137+
)
4138+
),
41054139
fileListSummaryDepth: Math.max(
41064140
0,
41074141
Math.min(

public/js/adminPortals.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ const __portalSubmissionsCache = {};
227227
async function loadPortalFolderList() {
228228
if (__portalFolderListLoaded) return __portalFolderOptions;
229229
try {
230-
const res = await fetch('/api/folder/getFolderList.php', { credentials: 'include' });
230+
const res = await fetch('/api/folder/getFolderList.php?counts=0', { credentials: 'include' });
231231
const data = await res.json();
232232
let list = data;
233233

public/js/adminSponsor.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const DEFAULT_SUPPORTERS = [
3030
'Rob Parker',
3131
'Aaron W.',
3232
'C-Fu',
33-
'peterchia'
33+
'peterchia',
34+
'Edisto Pirates of SC'
3435
];
3536

3637
/**

public/js/fileActions.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
formatFolderName,
66
fileData,
77
downloadSelectedFilesIndividually,
8+
startInlineRenameFromContext,
89
MAX_NONZIP_MULTI_DOWNLOAD
910
} from './fileListView.js?v={{APP_QVER}}';
1011
import { refreshFolderIcon, updateRecycleBinState } from './folderManager.js?v={{APP_QVER}}';
@@ -705,7 +706,7 @@ export async function loadCopyMoveFolderListForModal(dropdownId, preferredFolder
705706
if (window.userFolderOnly) {
706707
const username = localStorage.getItem("username") || "root";
707708
try {
708-
const response = await fetch("/api/folder/getFolderList.php?restricted=1");
709+
const response = await fetch("/api/folder/getFolderList.php?restricted=1&counts=0");
709710
let folders = await response.json();
710711
if (Array.isArray(folders) && folders.length && typeof folders[0] === "object" && folders[0].folder) {
711712
folders = folders.map(item => item.folder);
@@ -736,7 +737,7 @@ export async function loadCopyMoveFolderListForModal(dropdownId, preferredFolder
736737
}
737738

738739
try {
739-
const response = await fetch("/api/folder/getFolderList.php");
740+
const response = await fetch("/api/folder/getFolderList.php?counts=0");
740741
let folders = await response.json();
741742
if (Array.isArray(folders) && folders.length && typeof folders[0] === "object" && folders[0].folder) {
742743
folders = folders.map(item => item.folder);
@@ -896,7 +897,20 @@ export function handleRenameSelected(e) {
896897
return;
897898
}
898899
const file = files[0];
899-
renameFile(file.name, file.folder || window.currentFolder || "root");
900+
const folder = file.folder || window.currentFolder || "root";
901+
902+
// Prefer inline rename in table view when we can resolve a row.
903+
try {
904+
if (window.viewMode === "table" && typeof startInlineRenameFromContext === "function") {
905+
const checked = getActiveSelectedFileCheckboxes();
906+
const row = checked.length ? checked[0].closest("tr") : null;
907+
if (row && startInlineRenameFromContext(file, row)) {
908+
return;
909+
}
910+
}
911+
} catch (err) { /* ignore */ }
912+
913+
renameFile(file.name, folder);
900914
}
901915

902916
export function handleShareSelected(e) {

public/js/fileDragDrop.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// fileDragDrop.js
22
import { showToast } from './domUtils.js?v={{APP_QVER}}';
3-
import { loadFileList, cancelHoverPreview } from './fileListView.js?v={{APP_QVER}}';
3+
import { loadFileList, cancelHoverPreview, repairBlankFolderIcons } from './fileListView.js?v={{APP_QVER}}';
44
import {
55
getParentFolder,
66
syncTreeAfterFolderMove,
@@ -31,6 +31,14 @@ function invalidateFolderStats(folders) {
3131
console.warn('folderStatsInvalidated failed', e);
3232
}
3333
}
34+
function scheduleBlankFolderIconRepair() {
35+
try {
36+
const kick = () => { try { repairBlankFolderIcons({ force: true }); } catch (e) {} };
37+
if (typeof queueMicrotask === 'function') queueMicrotask(kick);
38+
setTimeout(kick, 80);
39+
setTimeout(kick, 250);
40+
} catch (e) { /* ignore */ }
41+
}
3442
function getNameFromAny(el) {
3543
const row = getRowEl(el);
3644
if (!row) return null;
@@ -248,6 +256,7 @@ export async function folderDropHandler(event) {
248256

249257
// Let folderManager handle tree refresh + selection + file list reload
250258
await syncTreeAfterFolderMove(source, dstParent);
259+
scheduleBlankFolderIconRepair();
251260

252261
} catch (e) {
253262
console.error('Error moving folder:', e);
@@ -308,7 +317,7 @@ export async function folderDropHandler(event) {
308317
// keep stats fresh for source + dest
309318
invalidateFolderStats([sourceFolder, dropFolder]);
310319

311-
loadFileList(window.currentFolder || sourceFolder);
320+
loadFileList(window.currentFolder || sourceFolder).finally(scheduleBlankFolderIconRepair);
312321
} else {
313322
const err = (data && (data.error || data.message)) || `HTTP ${res.status}`;
314323
showToast('Error moving file(s): ' + err);
@@ -317,4 +326,4 @@ export async function folderDropHandler(event) {
317326
console.error('Error moving file(s):', e);
318327
showToast('Error moving file(s).');
319328
}
320-
}
329+
}

0 commit comments

Comments
 (0)