Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76f5ed5c96 | ||
|
|
18f588dc24 | ||
|
|
491c686762 | ||
|
|
25303df677 | ||
|
|
ae0d63b86f | ||
|
|
41ade2e205 |
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,5 +1,27 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Changes 4/27/2025 1.2.7
|
||||||
|
|
||||||
|
- **Select-All** checkbox now correctly toggles all `.file-checkbox` inputs
|
||||||
|
- Updated `toggleAllCheckboxes(masterCheckbox)` to call `updateRowHighlight()` on each row so selections get the `.row-selected` highlight
|
||||||
|
- **Master checkbox sync** in toolbar
|
||||||
|
- Enhanced `updateFileActionButtons()` to set the header checkbox to checked, unchecked, or indeterminate based on how many files are selected
|
||||||
|
- Fixed Pagination controls & Items-per-page dropdown
|
||||||
|
- Fixed `#advancedSearchToggle` in both `renderFileTable()` and `renderGalleryView()`
|
||||||
|
- **Shared folder gallery view logic**
|
||||||
|
- Introduced new `public/js/sharedFolderView.js` containing all DOMContentLoaded wiring, `toggleViewMode()`, gallery rendering, and event listeners
|
||||||
|
- Embedded a non-executing JSON payload in `shareFolder.php`
|
||||||
|
- **`FolderController::shareFolder()` / `shareFolder.php`**
|
||||||
|
- Removed all inline `onclick="…"` attributes and inline `<script>` blocks
|
||||||
|
- Added `<script type="application/json" id="shared-data">…</script>` to export `$token` and `$files`
|
||||||
|
- Added `<script src="/js/sharedFolderView.js" defer></script>` to load the external view logic
|
||||||
|
- **Styling updates**
|
||||||
|
- Added `.toggle-btn` CSS for blue header-style toggle button and applied it in JS
|
||||||
|
- Added `.pagination a:hover { background-color: #0056b3; }` to match button hover
|
||||||
|
- Tweaked `body` padding and `header h1` margins to reduce whitespace above header
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Changes 4/26/2025 1.2.6
|
## Changes 4/26/2025 1.2.6
|
||||||
|
|
||||||
**Apache / Dockerfile (CSP)**
|
**Apache / Dockerfile (CSP)**
|
||||||
@@ -49,6 +71,8 @@
|
|||||||
- **Controller**: Updated `FolderController::shareFolder()` (folderController) to include the gallery-view toggle script block intact, ensuring the “Switch to Gallery View” button works when sharing folders.
|
- **Controller**: Updated `FolderController::shareFolder()` (folderController) to include the gallery-view toggle script block intact, ensuring the “Switch to Gallery View” button works when sharing folders.
|
||||||
- **UI (fileListView.js)**: Refactored `renderGalleryView` to remove all inline `onclick=` handlers; switched to using data-attributes and `addEventListener()` for preview, download, edit and rename buttons, fully CSP-compliant.
|
- **UI (fileListView.js)**: Refactored `renderGalleryView` to remove all inline `onclick=` handlers; switched to using data-attributes and `addEventListener()` for preview, download, edit and rename buttons, fully CSP-compliant.
|
||||||
- Moved logout button handler out of inline `<script>` in `index.html` and into the `DOMContentLoaded` init in **main.js** (via `auth.js`), so it now attaches reliably after the CSRF token is loaded and DOM is ready.
|
- Moved logout button handler out of inline `<script>` in `index.html` and into the `DOMContentLoaded` init in **main.js** (via `auth.js`), so it now attaches reliably after the CSRF token is loaded and DOM is ready.
|
||||||
|
- Added Content-Security-Policy for `<Files "api.php">` block to allow embedding the ReDoc iframe.
|
||||||
|
- Extracted inline ReDoc init into `public/js/redoc-init.js` and updated `public/api.php` to use deferred `<script>` tags.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -208,7 +232,7 @@
|
|||||||
Refactored to:
|
Refactored to:
|
||||||
1. Fetch CSRF
|
1. Fetch CSRF
|
||||||
2. POST credentials to `/api/auth/auth.php`
|
2. POST credentials to `/api/auth/auth.php`
|
||||||
3. On `totp_required`, re‑fetch CSRF *again* before calling `openTOTPLoginModal()`
|
3. On `totp_required`, re‑fetch CSRF again before calling `openTOTPLoginModal()`
|
||||||
4. Handle full logins vs. TOTP flows cleanly.
|
4. Handle full logins vs. TOTP flows cleanly.
|
||||||
|
|
||||||
- **TOTP handlers update**
|
- **TOTP handlers update**
|
||||||
@@ -1154,7 +1178,7 @@ The enhancements extend the existing drag-and-drop functionality by adding a hea
|
|||||||
- Adjusted file preview and icon styling for better alignment.
|
- Adjusted file preview and icon styling for better alignment.
|
||||||
- Centered the header and optimized the layout for a clean, modern appearance.
|
- Centered the header and optimized the layout for a clean, modern appearance.
|
||||||
|
|
||||||
*This changelog and feature summary reflect the improvements made during the refactor from a monolithic utils file to modular ES6 components, along with enhancements in UI responsiveness, sorting, file uploads, and file management operations.*
|
This changelog and feature summary reflect the improvements made during the refactor from a monolithic utils file to modular ES6 components, along with enhancements in UI responsiveness, sorting, file uploads, and file management operations.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -121,13 +121,17 @@ RUN cat <<'EOF' > /etc/apache2/sites-available/000-default.conf
|
|||||||
Require all denied
|
Require all denied
|
||||||
</FilesMatch>
|
</FilesMatch>
|
||||||
|
|
||||||
|
<Files "api.php">
|
||||||
|
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.redoc.ly; style-src 'self' 'unsafe-inline'; worker-src 'self' https://cdn.redoc.ly blob:; connect-src 'self'; img-src 'self' data: blob:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';"
|
||||||
|
</Files>
|
||||||
|
|
||||||
ErrorLog /var/www/metadata/log/error.log
|
ErrorLog /var/www/metadata/log/error.log
|
||||||
CustomLog /var/www/metadata/log/access.log combined
|
CustomLog /var/www/metadata/log/access.log combined
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Enable required modules
|
# Enable required modules
|
||||||
RUN a2enmod rewrite headers proxy proxy_fcgi expires deflate
|
RUN a2enmod rewrite headers proxy proxy_fcgi expires deflate ssl
|
||||||
|
|
||||||
EXPOSE 80 443
|
EXPOSE 80 443
|
||||||
COPY start.sh /usr/local/bin/start.sh
|
COPY start.sh /usr/local/bin/start.sh
|
||||||
|
|||||||
@@ -108,16 +108,16 @@ FileRise will be accessible at `http://localhost:8080` (or your server’s IP).
|
|||||||
|
|
||||||
If you prefer to run FileRise on a traditional web server (LAMP stack or similar):
|
If you prefer to run FileRise on a traditional web server (LAMP stack or similar):
|
||||||
|
|
||||||
- **Requirements:** PHP 8.1 or higher, Apache (with mod_php) or another web server configured for PHP. Ensure PHP extensions json, curl, and zip are enabled. No database needed.
|
- **Requirements:** PHP 8.3 or higher, Apache (with mod_php) or another web server configured for PHP. Ensure PHP extensions json, curl, and zip are enabled. No database needed.
|
||||||
- **Download Files:** Clone this repo or download the [latest release archive](https://github.com/error311/FileRise/releases).
|
- **Download Files:** Clone this repo or download the [latest release archive](https://github.com/error311/FileRise/releases).
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
git clone https://github.com/error311/FileRise.git
|
git clone https://github.com/error311/FileRise.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Place the files into your web server’s directory (e.g., `/var/www/public`). It can be in a subfolder (just adjust the `BASE_URL` in config as below).
|
Place the files into your web server’s directory (e.g., `/var/www/`). It can be in a subfolder (just adjust the `BASE_URL` in config as below).
|
||||||
|
|
||||||
- **Composer Dependencies:** If you plan to use OIDC (SSO login), install Composer and run `composer install` in the FileRise directory. (This pulls in a couple of PHP libraries like jumbojett/openid-connect for OAuth support.)
|
- **Composer Dependencies:** Install Composer and run `composer install` in the FileRise directory. (This pulls in a couple of PHP libraries like jumbojett/openid-connect for OAuth support.)
|
||||||
|
|
||||||
- **Folder Permissions:** Ensure the server can write to the following directories (create them if they don’t exist):
|
- **Folder Permissions:** Ensure the server can write to the following directories (create them if they don’t exist):
|
||||||
|
|
||||||
|
|||||||
@@ -19,17 +19,13 @@ if (isset($_GET['spec'])) {
|
|||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<title>FileRise API Docs</title>
|
<title>FileRise API Docs</title>
|
||||||
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"
|
<script defer src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"
|
||||||
integrity="sha384-4vOjrBu7SuDWXcAw1qFznVLA/sKL+0l4nn+J1HY8w7cpa6twQEYuh4b0Cwuo7CyX"
|
integrity="sha384-4vOjrBu7SuDWXcAw1qFznVLA/sKL+0l4nn+J1HY8w7cpa6twQEYuh4b0Cwuo7CyX"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
<script defer src="/js/redoc-init.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<redoc spec-url="api.php?spec=1"></redoc>
|
<redoc spec-url="api.php?spec=1"></redoc>
|
||||||
<div id="redoc-container"></div>
|
<div id="redoc-container"></div>
|
||||||
<script>
|
|
||||||
if (!customElements.get('redoc')) {
|
|
||||||
Redoc.init('api.php?spec=1', {}, document.getElementById('redoc-container'));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -3,7 +3,7 @@ import { sendRequest } from './networkUtils.js';
|
|||||||
import { t, applyTranslations, setLocale } from './i18n.js';
|
import { t, applyTranslations, setLocale } from './i18n.js';
|
||||||
import { loadAdminConfigFunc } from './auth.js';
|
import { loadAdminConfigFunc } from './auth.js';
|
||||||
|
|
||||||
const version = "v1.2.6"; // Update this version string as needed
|
const version = "v1.2.7"; // Update this version string as needed
|
||||||
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>`;
|
||||||
|
|
||||||
let lastLoginData = null;
|
let lastLoginData = null;
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ export function toggleAllCheckboxes(masterCheckbox) {
|
|||||||
const checkboxes = document.querySelectorAll(".file-checkbox");
|
const checkboxes = document.querySelectorAll(".file-checkbox");
|
||||||
checkboxes.forEach(chk => {
|
checkboxes.forEach(chk => {
|
||||||
chk.checked = masterCheckbox.checked;
|
chk.checked = masterCheckbox.checked;
|
||||||
|
updateRowHighlight(chk);
|
||||||
});
|
});
|
||||||
updateFileActionButtons(); // update buttons based on current selection
|
updateFileActionButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateFileActionButtons() {
|
export function updateFileActionButtons() {
|
||||||
@@ -38,6 +39,21 @@ export function updateFileActionButtons() {
|
|||||||
const zipBtn = document.getElementById("downloadZipBtn");
|
const zipBtn = document.getElementById("downloadZipBtn");
|
||||||
const extractZipBtn = document.getElementById("extractZipBtn");
|
const extractZipBtn = document.getElementById("extractZipBtn");
|
||||||
|
|
||||||
|
// keep the “select all” in sync ——
|
||||||
|
const master = document.getElementById("selectAll");
|
||||||
|
if (master) {
|
||||||
|
if (selectedCheckboxes.length === fileCheckboxes.length) {
|
||||||
|
master.checked = true;
|
||||||
|
master.indeterminate = false;
|
||||||
|
} else if (selectedCheckboxes.length === 0) {
|
||||||
|
master.checked = false;
|
||||||
|
master.indeterminate = false;
|
||||||
|
} else {
|
||||||
|
master.checked = false;
|
||||||
|
master.indeterminate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fileCheckboxes.length === 0) {
|
if (fileCheckboxes.length === 0) {
|
||||||
if (copyBtn) copyBtn.style.display = "none";
|
if (copyBtn) copyBtn.style.display = "none";
|
||||||
if (moveBtn) moveBtn.style.display = "none";
|
if (moveBtn) moveBtn.style.display = "none";
|
||||||
@@ -271,8 +287,6 @@ export function toggleRowSelection(event, fileName) {
|
|||||||
const start = Math.min(currentIndex, lastIndex);
|
const start = Math.min(currentIndex, lastIndex);
|
||||||
const end = Math.max(currentIndex, lastIndex);
|
const end = Math.max(currentIndex, lastIndex);
|
||||||
|
|
||||||
// If neither CTRL nor Meta is pressed, you might choose
|
|
||||||
// to clear existing selections. For this example we leave existing selections intact.
|
|
||||||
for (let i = start; i <= end; i++) {
|
for (let i = start; i <= end; i++) {
|
||||||
const cb = allRows[i].querySelector(".file-checkbox");
|
const cb = allRows[i].querySelector(".file-checkbox");
|
||||||
if (cb) {
|
if (cb) {
|
||||||
|
|||||||
@@ -340,8 +340,48 @@ export function renderFileTable(folder, container) {
|
|||||||
|
|
||||||
fileListContent.innerHTML = combinedTopHTML + headerHTML + rowsHTML + bottomControlsHTML;
|
fileListContent.innerHTML = combinedTopHTML + headerHTML + rowsHTML + bottomControlsHTML;
|
||||||
|
|
||||||
|
// pagination clicks
|
||||||
|
const prevBtn = document.getElementById("prevPageBtn");
|
||||||
|
if (prevBtn) prevBtn.addEventListener("click", () => {
|
||||||
|
if (window.currentPage > 1) {
|
||||||
|
window.currentPage--;
|
||||||
|
renderFileTable(folder, container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const nextBtn = document.getElementById("nextPageBtn");
|
||||||
|
if (nextBtn) nextBtn.addEventListener("click", () => {
|
||||||
|
// totalPages is computed above in this scope
|
||||||
|
if (window.currentPage < totalPages) {
|
||||||
|
window.currentPage++;
|
||||||
|
renderFileTable(folder, container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ADD: advanced search toggle
|
||||||
|
const advToggle = document.getElementById("advancedSearchToggle");
|
||||||
|
if (advToggle) advToggle.addEventListener("click", () => {
|
||||||
|
toggleAdvancedSearch();
|
||||||
|
});
|
||||||
|
|
||||||
|
// items-per-page selector
|
||||||
|
const itemsSelect = document.getElementById("itemsPerPageSelect");
|
||||||
|
if (itemsSelect) itemsSelect.addEventListener("change", e => {
|
||||||
|
window.itemsPerPage = parseInt(e.target.value, 10);
|
||||||
|
localStorage.setItem("itemsPerPage", window.itemsPerPage);
|
||||||
|
window.currentPage = 1;
|
||||||
|
renderFileTable(folder, container);
|
||||||
|
});
|
||||||
|
|
||||||
|
// hook up the master checkbox
|
||||||
|
const selectAll = document.getElementById("selectAll");
|
||||||
|
if (selectAll) {
|
||||||
|
selectAll.addEventListener("change", () => {
|
||||||
|
toggleAllCheckboxes(selectAll);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 1) Row-click selects the row
|
// 1) Row-click selects the row
|
||||||
fileListContent.querySelectorAll("tbody tr").forEach(row => {
|
fileListContent.querySelectorAll("tbody tr").forEach(row => {
|
||||||
row.addEventListener("click", e => {
|
row.addEventListener("click", e => {
|
||||||
// grab the underlying checkbox value
|
// grab the underlying checkbox value
|
||||||
const cb = row.querySelector(".file-checkbox");
|
const cb = row.querySelector(".file-checkbox");
|
||||||
@@ -575,7 +615,7 @@ export function renderGalleryView(folder, container) {
|
|||||||
style="position:absolute; top:5px; left:5px; width:16px; height:16px;"></label>
|
style="position:absolute; top:5px; left:5px; width:16px; height:16px;"></label>
|
||||||
|
|
||||||
<div class="gallery-preview" style="cursor:pointer;"
|
<div class="gallery-preview" style="cursor:pointer;"
|
||||||
data-preview-url="${folderPath+encodeURIComponent(file.name)}?t=${Date.now()}"
|
data-preview-url="${folderPath + encodeURIComponent(file.name)}?t=${Date.now()}"
|
||||||
data-preview-name="${file.name}">
|
data-preview-name="${file.name}">
|
||||||
${thumbnail}
|
${thumbnail}
|
||||||
</div>
|
</div>
|
||||||
@@ -590,20 +630,20 @@ export function renderGalleryView(folder, container) {
|
|||||||
<div class="button-wrap" style="display:flex; justify-content:center; gap:5px; margin-top:5px;">
|
<div class="button-wrap" style="display:flex; justify-content:center; gap:5px; margin-top:5px;">
|
||||||
<button type="button" class="btn btn-sm btn-success download-btn"
|
<button type="button" class="btn btn-sm btn-success download-btn"
|
||||||
data-download-name="${escapeHTML(file.name)}"
|
data-download-name="${escapeHTML(file.name)}"
|
||||||
data-download-folder="${file.folder||"root"}"
|
data-download-folder="${file.folder || "root"}"
|
||||||
title="${t('download')}">
|
title="${t('download')}">
|
||||||
<i class="material-icons">file_download</i>
|
<i class="material-icons">file_download</i>
|
||||||
</button>
|
</button>
|
||||||
${file.editable ? `
|
${file.editable ? `
|
||||||
<button type="button" class="btn btn-sm edit-btn"
|
<button type="button" class="btn btn-sm edit-btn"
|
||||||
data-edit-name="${escapeHTML(file.name)}"
|
data-edit-name="${escapeHTML(file.name)}"
|
||||||
data-edit-folder="${file.folder||"root"}"
|
data-edit-folder="${file.folder || "root"}"
|
||||||
title="${t('edit')}">
|
title="${t('edit')}">
|
||||||
<i class="material-icons">edit</i>
|
<i class="material-icons">edit</i>
|
||||||
</button>` : ""}
|
</button>` : ""}
|
||||||
<button type="button" class="btn btn-sm btn-warning rename-btn"
|
<button type="button" class="btn btn-sm btn-warning rename-btn"
|
||||||
data-rename-name="${escapeHTML(file.name)}"
|
data-rename-name="${escapeHTML(file.name)}"
|
||||||
data-rename-folder="${file.folder||"root"}"
|
data-rename-folder="${file.folder || "root"}"
|
||||||
title="${t('rename')}">
|
title="${t('rename')}">
|
||||||
<i class="material-icons">drive_file_rename_outline</i>
|
<i class="material-icons">drive_file_rename_outline</i>
|
||||||
</button>
|
</button>
|
||||||
@@ -629,6 +669,40 @@ export function renderGalleryView(folder, container) {
|
|||||||
|
|
||||||
// --- Now wire up all behaviors without inline handlers ---
|
// --- Now wire up all behaviors without inline handlers ---
|
||||||
|
|
||||||
|
// ADD: pagination buttons for gallery
|
||||||
|
const prevBtn = document.getElementById("prevPageBtn");
|
||||||
|
if (prevBtn) prevBtn.addEventListener("click", () => {
|
||||||
|
if (window.currentPage > 1) {
|
||||||
|
window.currentPage--;
|
||||||
|
renderGalleryView(folder, container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const nextBtn = document.getElementById("nextPageBtn");
|
||||||
|
if (nextBtn) nextBtn.addEventListener("click", () => {
|
||||||
|
if (window.currentPage < totalPages) {
|
||||||
|
window.currentPage++;
|
||||||
|
renderGalleryView(folder, container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ←— ADD: advanced search toggle
|
||||||
|
const advToggle = document.getElementById("advancedSearchToggle");
|
||||||
|
if (advToggle) advToggle.addEventListener("click", () => {
|
||||||
|
toggleAdvancedSearch();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ←— ADD: wire up context-menu in gallery
|
||||||
|
bindFileListContextMenu();
|
||||||
|
|
||||||
|
// ADD: items-per-page selector for gallery
|
||||||
|
const itemsSelect = document.getElementById("itemsPerPageSelect");
|
||||||
|
if (itemsSelect) itemsSelect.addEventListener("change", e => {
|
||||||
|
window.itemsPerPage = parseInt(e.target.value, 10);
|
||||||
|
localStorage.setItem("itemsPerPage", window.itemsPerPage);
|
||||||
|
window.currentPage = 1;
|
||||||
|
renderGalleryView(folder, container);
|
||||||
|
});
|
||||||
|
|
||||||
// cache images on load
|
// cache images on load
|
||||||
fileListContent.querySelectorAll('.gallery-thumbnail').forEach(img => {
|
fileListContent.querySelectorAll('.gallery-thumbnail').forEach(img => {
|
||||||
const key = img.dataset.cacheKey;
|
const key = img.dataset.cacheKey;
|
||||||
|
|||||||
6
public/js/redoc-init.js
Normal file
6
public/js/redoc-init.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// public/js/redoc-init.js
|
||||||
|
if (!customElements.get('redoc')) {
|
||||||
|
Redoc.init(window.location.origin + '/api.php?spec=1',
|
||||||
|
{},
|
||||||
|
document.getElementById('redoc-container'));
|
||||||
|
}
|
||||||
60
public/js/sharedFolderView.js
Normal file
60
public/js/sharedFolderView.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// sharedFolderView.js
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
let viewMode = 'list';
|
||||||
|
const payload = JSON.parse(
|
||||||
|
document.getElementById('shared-data').textContent
|
||||||
|
);
|
||||||
|
const token = payload.token;
|
||||||
|
const filesData = payload.files;
|
||||||
|
const downloadBase = `${window.location.origin}/api/folder/downloadSharedFile.php?token=${encodeURIComponent(token)}&file=`;
|
||||||
|
|
||||||
|
function toggleViewMode() {
|
||||||
|
const listEl = document.getElementById('listViewContainer');
|
||||||
|
const galleryEl = document.getElementById('galleryViewContainer');
|
||||||
|
const btn = document.getElementById('toggleBtn');
|
||||||
|
if (btn) {
|
||||||
|
btn.classList.add('toggle-btn');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewMode === 'list') {
|
||||||
|
viewMode = 'gallery';
|
||||||
|
listEl.style.display = 'none';
|
||||||
|
renderGalleryView();
|
||||||
|
galleryEl.style.display = 'block';
|
||||||
|
btn.textContent = 'Switch to List View';
|
||||||
|
} else {
|
||||||
|
viewMode = 'list';
|
||||||
|
galleryEl.style.display = 'none';
|
||||||
|
listEl.style.display = 'block';
|
||||||
|
btn.textContent = 'Switch to Gallery View';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('toggleBtn').addEventListener('click', toggleViewMode);
|
||||||
|
|
||||||
|
function renderGalleryView() {
|
||||||
|
const galleryContainer = document.getElementById('galleryViewContainer');
|
||||||
|
let html = '<div class="shared-gallery-container">';
|
||||||
|
filesData.forEach(file => {
|
||||||
|
const url = downloadBase + encodeURIComponent(file);
|
||||||
|
const ext = file.split('.').pop().toLowerCase();
|
||||||
|
const thumb = /^(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/.test(ext)
|
||||||
|
? `<img src="${url}" alt="${file}">`
|
||||||
|
: `<span class="material-icons">insert_drive_file</span>`;
|
||||||
|
html += `
|
||||||
|
<div class="shared-gallery-card">
|
||||||
|
<div class="gallery-preview" data-url="${url}" style="cursor:pointer;">${thumb}</div>
|
||||||
|
<div class="gallery-info"><span class="gallery-file-name">${file}</span></div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
galleryContainer.innerHTML = html;
|
||||||
|
|
||||||
|
galleryContainer.querySelectorAll('.gallery-preview')
|
||||||
|
.forEach(el => el.addEventListener('click', () => {
|
||||||
|
window.location.href = el.dataset.url;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.renderGalleryView = renderGalleryView;
|
||||||
|
});
|
||||||
@@ -550,13 +550,18 @@ class FolderController
|
|||||||
body {
|
body {
|
||||||
background: #f2f2f2;
|
background: #f2f2f2;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
padding: 20px;
|
padding: 0px 20px 20px 20px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@@ -661,6 +666,28 @@ class FolderController
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-btn {
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-btn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination a:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination span {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -670,7 +697,7 @@ class FolderController
|
|||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- Toggle Button -->
|
<!-- Toggle Button -->
|
||||||
<button id="toggleBtn" class="toggle-btn" onclick="toggleViewMode()">Switch to Gallery View</button>
|
<button id="toggleBtn" class="toggle-btn">Switch to Gallery View</button>
|
||||||
|
|
||||||
<!-- List View Container -->
|
<!-- List View Container -->
|
||||||
<div id="listViewContainer">
|
<div id="listViewContainer">
|
||||||
@@ -757,86 +784,14 @@ class FolderController
|
|||||||
<div class="footer">
|
<div class="footer">
|
||||||
© <?php echo date("Y"); ?> FileRise. All rights reserved.
|
© <?php echo date("Y"); ?> FileRise. All rights reserved.
|
||||||
</div>
|
</div>
|
||||||
|
<!-- non-executing JSON payload, never blocked by CSP -->
|
||||||
<script>
|
<script type="application/json" id="shared-data">
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
{
|
||||||
// JavaScript for toggling view modes (list/gallery) and wiring up gallery clicks
|
"token": <?php echo json_encode($token, JSON_HEX_TAG); ?>,
|
||||||
var viewMode = 'list';
|
"files": <?php echo json_encode($files, JSON_HEX_TAG); ?>
|
||||||
var token = '<?php echo addslashes($token); ?>';
|
|
||||||
var filesData = <?php echo json_encode($files); ?>;
|
|
||||||
|
|
||||||
// Build the download URL base
|
|
||||||
var downloadBase = window.location.origin +
|
|
||||||
'/api/folder/downloadSharedFile.php?token=' +
|
|
||||||
encodeURIComponent(token) +
|
|
||||||
'&file=';
|
|
||||||
|
|
||||||
function toggleViewMode() {
|
|
||||||
var listEl = document.getElementById('listViewContainer');
|
|
||||||
var galleryEl = document.getElementById('galleryViewContainer');
|
|
||||||
var btn = document.getElementById('toggleBtn');
|
|
||||||
|
|
||||||
if (viewMode === 'list') {
|
|
||||||
viewMode = 'gallery';
|
|
||||||
listEl.style.display = 'none';
|
|
||||||
renderGalleryView();
|
|
||||||
galleryEl.style.display = 'block';
|
|
||||||
btn.textContent = 'Switch to List View';
|
|
||||||
} else {
|
|
||||||
viewMode = 'list';
|
|
||||||
galleryEl.style.display = 'none';
|
|
||||||
listEl.style.display = 'block';
|
|
||||||
btn.textContent = 'Switch to Gallery View';
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Wire up the toggle button
|
|
||||||
document.getElementById('toggleBtn')
|
|
||||||
.addEventListener('click', toggleViewMode);
|
|
||||||
|
|
||||||
function renderGalleryView() {
|
|
||||||
var galleryContainer = document.getElementById('galleryViewContainer');
|
|
||||||
var html = '<div class="shared-gallery-container">';
|
|
||||||
|
|
||||||
filesData.forEach(function(file) {
|
|
||||||
var encodedName = encodeURIComponent(file);
|
|
||||||
var fileUrl = downloadBase + encodedName;
|
|
||||||
var ext = file.split('.').pop().toLowerCase();
|
|
||||||
var thumb;
|
|
||||||
|
|
||||||
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'].indexOf(ext) >= 0) {
|
|
||||||
thumb = '<img src="' + fileUrl + '" alt="' + file + '">';
|
|
||||||
} else {
|
|
||||||
thumb = '<span class="material-icons">insert_drive_file</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html +=
|
|
||||||
'<div class="shared-gallery-card">' +
|
|
||||||
'<div class="gallery-preview" data-url="' + fileUrl + '" style="cursor:pointer;">' +
|
|
||||||
thumb +
|
|
||||||
'</div>' +
|
|
||||||
'<div class="gallery-info">' +
|
|
||||||
'<span class="gallery-file-name">' + file + '</span>' +
|
|
||||||
'</div>' +
|
|
||||||
'</div>';
|
|
||||||
});
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
galleryContainer.innerHTML = html;
|
|
||||||
|
|
||||||
// Wire up each thumbnail click
|
|
||||||
galleryContainer.querySelectorAll('.gallery-preview')
|
|
||||||
.forEach(function(el) {
|
|
||||||
el.addEventListener('click', function() {
|
|
||||||
window.location.href = el.dataset.url;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expose for manual invocation if needed
|
|
||||||
window.renderGalleryView = renderGalleryView;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/js/sharedFolderView.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user