Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9209f7a582 | ||
|
|
4a736b0224 |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,16 +1,37 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Changes 5/20/2025 1.3.6
|
## Changes 5/22/2025 v1.3.7
|
||||||
|
|
||||||
|
- `.folder-strip-container .folder-name` css added to center text below folder material icon.
|
||||||
|
- Override file share_url to always use current origin
|
||||||
|
- Update `fileList` css to keep file name wrapping tight.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes 5/21/2025
|
||||||
|
|
||||||
|
- **Drag & Drop to Folder Strip**
|
||||||
|
- Enabled dragging files from the file list directly onto the folder-strip items.
|
||||||
|
- Hooked up `folderDragOverHandler`, `folderDragLeaveHandler`, and `folderDropHandler` to `.folder-strip-container .folder-item`.
|
||||||
|
- On drop, files are moved via `/api/file/moveFiles.php` and the file list is refreshed.
|
||||||
|
|
||||||
|
- **Restore files from trash Toast Message**
|
||||||
|
- Changed the restore handlers so that the toast always reports the actual file(s) restored (e.g. “Restored file: foo.txt”) instead of “No trash record found.”
|
||||||
|
- Removed reliance on backend message payload and now generate the confirmation text client-side based on selected items.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes 5/20/2025 v1.3.6
|
||||||
|
|
||||||
- **domUtils.js**
|
- **domUtils.js**
|
||||||
- `updateFileActionButtons`
|
- `updateFileActionButtons`
|
||||||
- Hide selection buttons (`Delete Files`, `Copy Files`, `Move Files` & `Download ZIP`) until file is selected.
|
- Hide selection buttons (`Delete Files`, `Copy Files`, `Move Files` & `Download ZIP`) until file is selected.
|
||||||
- Hides `Extract ZIP` until selecting zip files
|
- Hide `Extract ZIP` until selecting zip files
|
||||||
- Hide `Create File` button when file list items are selected.
|
- Hide `Create File` button when file list items are selected.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Changes 5/19/2025 1.3.5
|
## Changes 5/19/2025 v1.3.5
|
||||||
|
|
||||||
### Added Folder strip & Create File
|
### Added Folder strip & Create File
|
||||||
|
|
||||||
|
|||||||
@@ -971,16 +971,15 @@ body.dark-mode #fileList table tr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--file-row-height: 48px; /* default, will be overwritten by your slider */
|
--file-row-height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Force each <tr> to be exactly the var() height */
|
|
||||||
#fileList table.table tbody tr {
|
#fileList table.table tbody tr {
|
||||||
height: var(--file-row-height) !important;
|
height: auto !important;
|
||||||
|
min-height: var(--file-row-height) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* And force each <td> to match, with no extra padding or line-height */
|
#fileList table.table tbody td:not(.file-name-cell) {
|
||||||
#fileList table.table tbody td {
|
|
||||||
height: var(--file-row-height) !important;
|
height: var(--file-row-height) !important;
|
||||||
line-height: var(--file-row-height) !important;
|
line-height: var(--file-row-height) !important;
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
@@ -988,6 +987,13 @@ body.dark-mode #fileList table tr {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#fileList table.table tbody td.file-name-cell {
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.2em !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===========================================================
|
/* ===========================================================
|
||||||
HEADINGS & FORM LABELS
|
HEADINGS & FORM LABELS
|
||||||
=========================================================== */
|
=========================================================== */
|
||||||
@@ -2261,13 +2267,20 @@ body.dark-mode .user-dropdown .user-menu .item:hover {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
color: inherit; /* icon will pick up text color */
|
color: inherit;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
.folder-strip-container .folder-item i.material-icons {
|
.folder-strip-container .folder-item i.material-icons {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
.folder-strip-container .folder-name {
|
||||||
|
text-align: center;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
max-width: 80px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.folder-strip-container .folder-item i.material-icons {
|
.folder-strip-container .folder-item i.material-icons {
|
||||||
color: currentColor;
|
color: currentColor;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { loadAdminConfigFunc } from './auth.js';
|
|||||||
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
|
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
|
||||||
import { sendRequest } from './networkUtils.js';
|
import { sendRequest } from './networkUtils.js';
|
||||||
|
|
||||||
const version = "v1.3.6";
|
const version = "v1.3.7";
|
||||||
const adminTitle = `${t("admin_panel")} <small style="font-size:12px;color:gray;">${version}</small>`;
|
const adminTitle = `${t("admin_panel")} <small style="font-size:12px;color:gray;">${version}</small>`;
|
||||||
|
|
||||||
// ————— Inject updated styles —————
|
// ————— Inject updated styles —————
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ import { bindFileListContextMenu } from './fileMenu.js';
|
|||||||
import { openDownloadModal } from './fileActions.js';
|
import { openDownloadModal } from './fileActions.js';
|
||||||
import { openTagModal, openMultiTagModal } from './fileTags.js';
|
import { openTagModal, openMultiTagModal } from './fileTags.js';
|
||||||
import { getParentFolder, updateBreadcrumbTitle, setupBreadcrumbDelegation } from './folderManager.js';
|
import { getParentFolder, updateBreadcrumbTitle, setupBreadcrumbDelegation } from './folderManager.js';
|
||||||
|
import {
|
||||||
|
folderDragOverHandler,
|
||||||
|
folderDragLeaveHandler,
|
||||||
|
folderDropHandler
|
||||||
|
} from './fileDragDrop.js';
|
||||||
|
|
||||||
export let fileData = [];
|
export let fileData = [];
|
||||||
export let sortOrder = { column: "uploaded", ascending: true };
|
export let sortOrder = { column: "uploaded", ascending: true };
|
||||||
@@ -190,7 +195,7 @@ window.updateRowHighlight = updateRowHighlight;
|
|||||||
export async function loadFileList(folderParam) {
|
export async function loadFileList(folderParam) {
|
||||||
const folder = folderParam || "root";
|
const folder = folderParam || "root";
|
||||||
const fileListContainer = document.getElementById("fileList");
|
const fileListContainer = document.getElementById("fileList");
|
||||||
const actionsContainer = document.getElementById("fileListActions");
|
const actionsContainer = document.getElementById("fileListActions");
|
||||||
|
|
||||||
// 1) show loader
|
// 1) show loader
|
||||||
fileListContainer.style.visibility = "hidden";
|
fileListContainer.style.visibility = "hidden";
|
||||||
@@ -207,15 +212,15 @@ export async function loadFileList(folderParam) {
|
|||||||
window.location.href = "/api/auth/logout.php";
|
window.location.href = "/api/auth/logout.php";
|
||||||
throw new Error("Unauthorized");
|
throw new Error("Unauthorized");
|
||||||
}
|
}
|
||||||
const data = await filesRes.json();
|
const data = await filesRes.json();
|
||||||
const folderRaw = await foldersRes.json();
|
const folderRaw = await foldersRes.json();
|
||||||
|
|
||||||
// --- build ONLY the *direct* children of current folder ---
|
// --- build ONLY the *direct* children of current folder ---
|
||||||
let subfolders = [];
|
let subfolders = [];
|
||||||
const hidden = new Set([ "profile_pics", "trash" ]);
|
const hidden = new Set(["profile_pics", "trash"]);
|
||||||
if (Array.isArray(folderRaw)) {
|
if (Array.isArray(folderRaw)) {
|
||||||
const allPaths = folderRaw.map(item => item.folder ?? item);
|
const allPaths = folderRaw.map(item => item.folder ?? item);
|
||||||
const depth = folder === "root" ? 1 : folder.split("/").length + 1;
|
const depth = folder === "root" ? 1 : folder.split("/").length + 1;
|
||||||
subfolders = allPaths
|
subfolders = allPaths
|
||||||
.filter(p => {
|
.filter(p => {
|
||||||
if (folder === "root") {
|
if (folder === "root") {
|
||||||
@@ -261,7 +266,7 @@ export async function loadFileList(folderParam) {
|
|||||||
data.files = data.files.map(f => {
|
data.files = data.files.map(f => {
|
||||||
f.fullName = (f.path || f.name).trim().toLowerCase();
|
f.fullName = (f.path || f.name).trim().toLowerCase();
|
||||||
f.editable = canEditFile(f.name);
|
f.editable = canEditFile(f.name);
|
||||||
f.folder = folder;
|
f.folder = folder;
|
||||||
return f;
|
return f;
|
||||||
});
|
});
|
||||||
fileData = data.files;
|
fileData = data.files;
|
||||||
@@ -294,13 +299,13 @@ export async function loadFileList(folderParam) {
|
|||||||
if (viewMode === "gallery") {
|
if (viewMode === "gallery") {
|
||||||
const w = window.innerWidth;
|
const w = window.innerWidth;
|
||||||
let maxCols;
|
let maxCols;
|
||||||
if (w < 600) maxCols = 1;
|
if (w < 600) maxCols = 1;
|
||||||
else if (w < 900) maxCols = 2;
|
else if (w < 900) maxCols = 2;
|
||||||
else if (w < 1200) maxCols = 4;
|
else if (w < 1200) maxCols = 4;
|
||||||
else maxCols = 6;
|
else maxCols = 6;
|
||||||
|
|
||||||
const currentCols = Math.min(
|
const currentCols = Math.min(
|
||||||
parseInt(localStorage.getItem("galleryColumns")||"3",10),
|
parseInt(localStorage.getItem("galleryColumns") || "3", 10),
|
||||||
maxCols
|
maxCols
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -319,7 +324,7 @@ export async function loadFileList(folderParam) {
|
|||||||
<span id="galleryColumnsValue" style="margin-left:6px;line-height:1;">${currentCols}</span>
|
<span id="galleryColumnsValue" style="margin-left:6px;line-height:1;">${currentCols}</span>
|
||||||
`;
|
`;
|
||||||
const gallerySlider = document.getElementById("galleryColumnsSlider");
|
const gallerySlider = document.getElementById("galleryColumnsSlider");
|
||||||
const galleryValue = document.getElementById("galleryColumnsValue");
|
const galleryValue = document.getElementById("galleryColumnsValue");
|
||||||
gallerySlider.oninput = e => {
|
gallerySlider.oninput = e => {
|
||||||
const v = +e.target.value;
|
const v = +e.target.value;
|
||||||
localStorage.setItem("galleryColumns", v);
|
localStorage.setItem("galleryColumns", v);
|
||||||
@@ -328,16 +333,16 @@ export async function loadFileList(folderParam) {
|
|||||||
?.style.setProperty("grid-template-columns", `repeat(${v},1fr)`);
|
?.style.setProperty("grid-template-columns", `repeat(${v},1fr)`);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const currentHeight = parseInt(localStorage.getItem("rowHeight")||"48",10);
|
const currentHeight = parseInt(localStorage.getItem("rowHeight") || "48", 10);
|
||||||
sliderContainer.innerHTML = `
|
sliderContainer.innerHTML = `
|
||||||
<label for="rowHeightSlider" style="margin-right:8px;line-height:1;">
|
<label for="rowHeightSlider" style="margin-right:8px;line-height:1;">
|
||||||
${t("row_height")}:
|
${t("row_height")}:
|
||||||
</label>
|
</label>
|
||||||
<input type="range" id="rowHeightSlider" min="31" max="60" value="${currentHeight}" style="vertical-align:middle;">
|
<input type="range" id="rowHeightSlider" min="30" max="60" value="${currentHeight}" style="vertical-align:middle;">
|
||||||
<span id="rowHeightValue" style="margin-left:6px;line-height:1;">${currentHeight}px</span>
|
<span id="rowHeightValue" style="margin-left:6px;line-height:1;">${currentHeight}px</span>
|
||||||
`;
|
`;
|
||||||
const rowSlider = document.getElementById("rowHeightSlider");
|
const rowSlider = document.getElementById("rowHeightSlider");
|
||||||
const rowValue = document.getElementById("rowHeightValue");
|
const rowValue = document.getElementById("rowHeightValue");
|
||||||
rowSlider.oninput = e => {
|
rowSlider.oninput = e => {
|
||||||
const v = e.target.value;
|
const v = e.target.value;
|
||||||
document.documentElement.style.setProperty("--file-row-height", v + "px");
|
document.documentElement.style.setProperty("--file-row-height", v + "px");
|
||||||
@@ -357,26 +362,31 @@ export async function loadFileList(folderParam) {
|
|||||||
}
|
}
|
||||||
if (window.showFoldersInList && subfolders.length) {
|
if (window.showFoldersInList && subfolders.length) {
|
||||||
strip.innerHTML = subfolders.map(sf => `
|
strip.innerHTML = subfolders.map(sf => `
|
||||||
<div class="folder-item" data-folder="${sf.full}">
|
<div class="folder-item" data-folder="${sf.full}" draggable="true">
|
||||||
<i class="material-icons">folder</i>
|
<i class="material-icons">folder</i>
|
||||||
<div class="folder-name">${escapeHTML(sf.name)}</div>
|
<div class="folder-name">${escapeHTML(sf.name)}</div>
|
||||||
</div>
|
</div>
|
||||||
`).join("");
|
`).join("");
|
||||||
strip.style.display = "flex";
|
strip.style.display = "flex";
|
||||||
|
|
||||||
strip.querySelectorAll(".folder-item").forEach(el => {
|
strip.querySelectorAll(".folder-item").forEach(el => {
|
||||||
|
// click‐to‐navigate
|
||||||
el.addEventListener("click", () => {
|
el.addEventListener("click", () => {
|
||||||
const dest = el.dataset.folder;
|
const dest = el.dataset.folder;
|
||||||
window.currentFolder = dest;
|
window.currentFolder = dest;
|
||||||
localStorage.setItem("lastOpenedFolder", dest);
|
localStorage.setItem("lastOpenedFolder", dest);
|
||||||
// sync breadcrumb & tree
|
|
||||||
updateBreadcrumbTitle(dest);
|
updateBreadcrumbTitle(dest);
|
||||||
document.querySelectorAll(".folder-option.selected")
|
document.querySelectorAll(".folder-option.selected")
|
||||||
.forEach(o => o.classList.remove("selected"));
|
.forEach(o => o.classList.remove("selected"));
|
||||||
document.querySelector(`.folder-option[data-folder="${dest}"]`)
|
document.querySelector(`.folder-option[data-folder="${dest}"]`)
|
||||||
?.classList.add("selected");
|
?.classList.add("selected");
|
||||||
// reload
|
|
||||||
loadFileList(dest);
|
loadFileList(dest);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// drag & drop handlers
|
||||||
|
el.addEventListener("dragover", folderDragOverHandler);
|
||||||
|
el.addEventListener("dragleave", folderDragLeaveHandler);
|
||||||
|
el.addEventListener("drop", folderDropHandler);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
strip.style.display = "none";
|
strip.style.display = "none";
|
||||||
|
|||||||
@@ -64,35 +64,26 @@ export function initializeApp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function loadCsrfToken() {
|
export function loadCsrfToken() {
|
||||||
return fetchWithCsrf('/api/auth/token.php', {
|
return fetchWithCsrf('/api/auth/token.php', { method: 'GET' })
|
||||||
method: 'GET'
|
|
||||||
})
|
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (!res.ok) {
|
if (!res.ok) throw new Error(`Token fetch failed with status ${res.status}`);
|
||||||
throw new Error(`Token fetch failed with status ${res.status}`);
|
|
||||||
}
|
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then(({ csrf_token, share_url }) => {
|
.then(({ csrf_token, share_url }) => {
|
||||||
// Update global and <meta>
|
|
||||||
window.csrfToken = csrf_token;
|
window.csrfToken = csrf_token;
|
||||||
let meta = document.querySelector('meta[name="csrf-token"]');
|
|
||||||
if (!meta) {
|
// update CSRF meta
|
||||||
meta = document.createElement('meta');
|
let meta = document.querySelector('meta[name="csrf-token"]') ||
|
||||||
meta.name = 'csrf-token';
|
Object.assign(document.head.appendChild(document.createElement('meta')), { name: 'csrf-token' });
|
||||||
document.head.appendChild(meta);
|
|
||||||
}
|
|
||||||
meta.content = csrf_token;
|
meta.content = csrf_token;
|
||||||
|
|
||||||
let shareMeta = document.querySelector('meta[name="share-url"]');
|
// force share_url to match wherever we're browsing
|
||||||
if (!shareMeta) {
|
const actualShare = window.location.origin;
|
||||||
shareMeta = document.createElement('meta');
|
let shareMeta = document.querySelector('meta[name="share-url"]') ||
|
||||||
shareMeta.name = 'share-url';
|
Object.assign(document.head.appendChild(document.createElement('meta')), { name: 'share-url' });
|
||||||
document.head.appendChild(shareMeta);
|
shareMeta.content = actualShare;
|
||||||
}
|
|
||||||
shareMeta.content = share_url;
|
|
||||||
|
|
||||||
return { csrf_token, share_url };
|
return { csrf_token, share_url: actualShare };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,15 +79,16 @@ export function setupTrashRestoreDelete() {
|
|||||||
body: JSON.stringify({ files })
|
body: JSON.stringify({ files })
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(() => {
|
||||||
if (data.success) {
|
// Always report what we actually restored
|
||||||
showToast(data.success);
|
if (files.length === 1) {
|
||||||
toggleVisibility("restoreFilesModal", false);
|
showToast(`Restored file: ${files[0]}`);
|
||||||
loadFileList(window.currentFolder);
|
|
||||||
loadFolderTree(window.currentFolder);
|
|
||||||
} else {
|
} else {
|
||||||
showToast(data.error);
|
showToast(`Restored files: ${files.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
toggleVisibility("restoreFilesModal", false);
|
||||||
|
loadFileList(window.currentFolder);
|
||||||
|
loadFolderTree(window.currentFolder);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error("Error restoring files:", err);
|
console.error("Error restoring files:", err);
|
||||||
@@ -119,16 +120,15 @@ export function setupTrashRestoreDelete() {
|
|||||||
body: JSON.stringify({ files })
|
body: JSON.stringify({ files })
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(() => {
|
||||||
if (data.success) {
|
if (files.length === 1) {
|
||||||
showToast(data.success);
|
showToast(`Restored file: ${files[0]}`);
|
||||||
toggleVisibility("restoreFilesModal", false);
|
|
||||||
loadFileList(window.currentFolder);
|
|
||||||
loadFolderTree(window.currentFolder);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
showToast(data.error);
|
showToast(`Restored files: ${files.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
toggleVisibility("restoreFilesModal", false);
|
||||||
|
loadFileList(window.currentFolder);
|
||||||
|
loadFolderTree(window.currentFolder);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error("Error restoring files:", err);
|
console.error("Error restoring files:", err);
|
||||||
|
|||||||
Reference in New Issue
Block a user