Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5610cf156 | ||
|
|
ae932a9aa9 | ||
|
|
a106d47f77 | ||
|
|
41d464a4b3 | ||
|
|
9e69f19e23 | ||
|
|
1df7bc3f87 | ||
|
|
e5f9831d73 | ||
|
|
553bc84404 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,2 +1,3 @@
|
|||||||
|
---
|
||||||
github: [error311]
|
github: [error311]
|
||||||
ko_fi: error311
|
ko_fi: error311
|
||||||
|
|||||||
56
.github/workflows/sync-changelog.yml
vendored
56
.github/workflows/sync-changelog.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: Sync Changelog to Docker Repo
|
name: Bump version and sync Changelog to Docker Repo
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -10,35 +10,69 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
bump_and_sync:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout FileRise
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
- name: Extract version from commit message
|
||||||
path: file-rise
|
id: ver
|
||||||
|
run: |
|
||||||
|
MSG="${{ github.event.head_commit.message }}"
|
||||||
|
if [[ "$MSG" =~ release\((v[0-9]+\.[0-9]+\.[0-9]+)\) ]]; then
|
||||||
|
echo "version=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
|
||||||
|
echo "Found version: ${BASH_REMATCH[1]}"
|
||||||
|
else
|
||||||
|
echo "version=" >> $GITHUB_OUTPUT
|
||||||
|
echo "No release(vX.Y.Z) tag in commit message; skipping bump."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Update public/js/version.js
|
||||||
|
if: steps.ver.outputs.version != ''
|
||||||
|
run: |
|
||||||
|
cat > public/js/version.js <<'EOF'
|
||||||
|
// generated by CI
|
||||||
|
window.APP_VERSION = '${{ steps.ver.outputs.version }}';
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Commit version.js (if changed)
|
||||||
|
if: steps.ver.outputs.version != ''
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git add public/js/version.js
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No changes to commit"
|
||||||
|
else
|
||||||
|
git commit -m "chore: set APP_VERSION to ${{ steps.ver.outputs.version }}"
|
||||||
|
git push
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Checkout filerise-docker
|
- name: Checkout filerise-docker
|
||||||
|
if: steps.ver.outputs.version != ''
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: error311/filerise-docker
|
repository: error311/filerise-docker
|
||||||
token: ${{ secrets.PAT_TOKEN }}
|
token: ${{ secrets.PAT_TOKEN }}
|
||||||
path: docker-repo
|
path: docker-repo
|
||||||
|
|
||||||
- name: Copy CHANGELOG.md
|
- name: Copy CHANGELOG.md and write VERSION
|
||||||
|
if: steps.ver.outputs.version != ''
|
||||||
run: |
|
run: |
|
||||||
cp file-rise/CHANGELOG.md docker-repo/CHANGELOG.md
|
cp CHANGELOG.md docker-repo/CHANGELOG.md
|
||||||
|
echo "${{ steps.ver.outputs.version }}" > docker-repo/VERSION
|
||||||
|
|
||||||
- name: Commit & push
|
- name: Commit & push to docker repo
|
||||||
|
if: steps.ver.outputs.version != ''
|
||||||
working-directory: docker-repo
|
working-directory: docker-repo
|
||||||
run: |
|
run: |
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
git add CHANGELOG.md
|
git add CHANGELOG.md VERSION
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
echo "No changes to commit"
|
echo "No changes to commit"
|
||||||
else
|
else
|
||||||
git commit -m "chore: sync CHANGELOG.md from FileRise"
|
git commit -m "chore: sync CHANGELOG.md and VERSION (${{ steps.ver.outputs.version }}) from FileRise"
|
||||||
git push origin main
|
git push origin main
|
||||||
fi
|
fi
|
||||||
|
|||||||
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,5 +1,49 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Changes 10/24/2025 (v1.6.6)
|
||||||
|
|
||||||
|
release(v1.6.6): header-mounted toggle, dark-mode polish, persistent layout, and ACL fix
|
||||||
|
|
||||||
|
- dragAndDrop: mount zones toggle beside header logo (absolute, non-scrolling);
|
||||||
|
stop click propagation so it doesn’t trigger the logo link; theme-aware styling
|
||||||
|
- live updates via MutationObserver; snapshot card locations on drop and restore
|
||||||
|
on load (prevents sidebar reset); guard first-run defaults with
|
||||||
|
`layoutDefaultApplied_v1`; small/medium layout tweaks & refactors.
|
||||||
|
- CSS: switch toggle icon to CSS variable (`--toggle-icon-color`) with dark-mode
|
||||||
|
override; remove hardcoded `!important`.
|
||||||
|
- API (capabilities.php): remove unused `disableUpload` flag from `canUpload`
|
||||||
|
and flags payload to resolve undefined variable warning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes 10/24/2025 (v1.6.5)
|
||||||
|
|
||||||
|
release(v1.6.5): fix PHP warning and upload-flag check in capabilities.php
|
||||||
|
|
||||||
|
- Fix undefined variable: use $disableUpload consistently
|
||||||
|
- Harden flag read: (bool)($perms['disableUpload'] ?? false)
|
||||||
|
- Prevents warning and ensures Upload capability is computed correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes 10/24/2025 (v1.6.4)
|
||||||
|
|
||||||
|
release(v1.6.4): runtime version injection + CI bump/sync; caching tweaks
|
||||||
|
|
||||||
|
- Add public/js/version.js (default "dev") and load it before main.js.
|
||||||
|
- adminPanel.js: replace hard-coded string with `window.APP_VERSION || "dev"`.
|
||||||
|
- public/.htaccess: add no-cache for js/version.js
|
||||||
|
- GitHub Actions: replace sync job with “Bump version and sync Changelog to Docker Repo”.
|
||||||
|
- Parse commit msg `release(vX.Y.Z)` -> set step output `version`.
|
||||||
|
- Write `public/js/version.js` with `window.APP_VERSION = '<version>'`.
|
||||||
|
- Commit/push version.js if changed.
|
||||||
|
- Mirror CHANGELOG.md to filerise-docker and write a VERSION file with `<version>`.
|
||||||
|
- Guard all steps with `if: steps.ver.outputs.version != ''` to no-op on non-release commits.
|
||||||
|
|
||||||
|
This wires the UI version label to CI, keeps dev builds showing “dev”, and feeds the Docker repo with CHANGELOG + VERSION for builds.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Changes 10/24/2025 (v1.6.3)
|
## Changes 10/24/2025 (v1.6.3)
|
||||||
|
|
||||||
release(v1.6.3): drag/drop card persistence, admin UX fixes, and docs (closes #58)
|
release(v1.6.3): drag/drop card persistence, admin UX fixes, and docs (closes #58)
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ RewriteEngine On
|
|||||||
<FilesMatch "\.(js|css)$">
|
<FilesMatch "\.(js|css)$">
|
||||||
Header set Cache-Control "public, max-age=3600, must-revalidate"
|
Header set Cache-Control "public, max-age=3600, must-revalidate"
|
||||||
</FilesMatch>
|
</FilesMatch>
|
||||||
|
# version.js should always revalidate (it changes on releases)
|
||||||
|
<FilesMatch "^js/version\.js$">
|
||||||
|
Header set Cache-Control "no-cache, no-store, must-revalidate"
|
||||||
|
Header set Pragma "no-cache"
|
||||||
|
Header set Expires "0"
|
||||||
|
</FilesMatch>
|
||||||
</IfModule>
|
</IfModule>
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ if ($folder !== 'root') {
|
|||||||
$perms = loadPermsFor($username);
|
$perms = loadPermsFor($username);
|
||||||
$isAdmin = ACL::isAdmin($perms);
|
$isAdmin = ACL::isAdmin($perms);
|
||||||
$readOnly = !empty($perms['readOnly']);
|
$readOnly = !empty($perms['readOnly']);
|
||||||
$disableUp = !empty($perms['disableUpload']);
|
|
||||||
$inScope = inUserFolderScope($folder, $username, $perms, $isAdmin);
|
$inScope = inUserFolderScope($folder, $username, $perms, $isAdmin);
|
||||||
|
|
||||||
// --- ACL base abilities ---
|
// --- ACL base abilities ---
|
||||||
@@ -178,7 +177,7 @@ $gShareFolder = $isAdmin || ACL::canShareFolder($username, $perms, $folder);
|
|||||||
|
|
||||||
// --- Apply scope + flags to effective UI actions ---
|
// --- Apply scope + flags to effective UI actions ---
|
||||||
$canView = $canViewBase && $inScope; // keep scope for folder-only
|
$canView = $canViewBase && $inScope; // keep scope for folder-only
|
||||||
$canUpload = $gUploadBase && !$readOnly && !$disableUpload && $inScope;
|
$canUpload = $gUploadBase && !$readOnly && $inScope;
|
||||||
$canCreate = $canManageBase && !$readOnly && $inScope; // Create **folder**
|
$canCreate = $canManageBase && !$readOnly && $inScope; // Create **folder**
|
||||||
$canRename = $canManageBase && !$readOnly && $inScope; // Rename **folder**
|
$canRename = $canManageBase && !$readOnly && $inScope; // Rename **folder**
|
||||||
$canDelete = $gDeleteBase && !$readOnly && $inScope;
|
$canDelete = $gDeleteBase && !$readOnly && $inScope;
|
||||||
@@ -213,7 +212,6 @@ echo json_encode([
|
|||||||
'flags' => [
|
'flags' => [
|
||||||
//'folderOnly' => !empty($perms['folderOnly']) || !empty($perms['userFolderOnly']) || !empty($perms['UserFolderOnly']),
|
//'folderOnly' => !empty($perms['folderOnly']) || !empty($perms['userFolderOnly']) || !empty($perms['UserFolderOnly']),
|
||||||
'readOnly' => $readOnly,
|
'readOnly' => $readOnly,
|
||||||
'disableUpload' => $disableUp,
|
|
||||||
],
|
],
|
||||||
'owner' => $owner,
|
'owner' => $owner,
|
||||||
|
|
||||||
|
|||||||
@@ -2318,11 +2318,14 @@ body.dark-mode { --perm-caret: #ccc; } /* dark */
|
|||||||
background-color 160ms cubic-bezier(.2,.0,.2,1);
|
background-color 160ms cubic-bezier(.2,.0,.2,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root { --toggle-icon-color: #333; }
|
||||||
|
body.dark-mode { --toggle-icon-color: #eee; }
|
||||||
|
|
||||||
#zonesToggleFloating .material-icons,
|
#zonesToggleFloating .material-icons,
|
||||||
#zonesToggleFloating .material-icons-outlined,
|
#zonesToggleFloating .material-icons-outlined,
|
||||||
#sidebarToggleFloating .material-icons,
|
#sidebarToggleFloating .material-icons,
|
||||||
#sidebarToggleFloating .material-icons-outlined {
|
#sidebarToggleFloating .material-icons-outlined {
|
||||||
color: #333 !important;
|
color: var(--toggle-icon-color);
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -563,6 +563,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="js/version.js"></script>
|
||||||
<script type="module" src="js/main.js"></script>
|
<script type="module" src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,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.6.3";
|
const version = window.APP_VERSION || "dev";
|
||||||
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>`;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,25 @@ const KNOWN_CARD_IDS = ['uploadCard', 'folderManagementCard'];
|
|||||||
|
|
||||||
const CARD_IDS = ['uploadCard', 'folderManagementCard'];
|
const CARD_IDS = ['uploadCard', 'folderManagementCard'];
|
||||||
|
|
||||||
|
function isDarkMode() {
|
||||||
|
return document.body.classList.contains('dark-mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
function themeToggleButton(btn) {
|
||||||
|
if (!btn) return;
|
||||||
|
if (isDarkMode()) {
|
||||||
|
btn.style.background = '#2c2c2c';
|
||||||
|
btn.style.border = '1px solid #555';
|
||||||
|
btn.style.boxShadow = '0 2px 6px rgba(0,0,0,.35)';
|
||||||
|
btn.style.color = '#e0e0e0'; // <- material icon inherits this
|
||||||
|
} else {
|
||||||
|
btn.style.background = '#fff';
|
||||||
|
btn.style.border = '1px solid #ccc';
|
||||||
|
btn.style.boxShadow = '0 2px 6px rgba(0,0,0,.15)';
|
||||||
|
btn.style.color = '#222'; // <- material icon inherits this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getKnownCards() {
|
function getKnownCards() {
|
||||||
return CARD_IDS
|
return CARD_IDS
|
||||||
.map(id => document.getElementById(id))
|
.map(id => document.getElementById(id))
|
||||||
@@ -133,6 +152,34 @@ function removeHeaderIconForCard(card) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applySnapshotIfPresent() {
|
||||||
|
const snap = readZonesSnapshot();
|
||||||
|
const keys = Object.keys(snap || {});
|
||||||
|
if (!keys.length) return false;
|
||||||
|
|
||||||
|
const sidebar = getSidebar();
|
||||||
|
const leftCol = document.getElementById('leftCol');
|
||||||
|
const rightCol = document.getElementById('rightCol');
|
||||||
|
|
||||||
|
getKnownCards().forEach(card => {
|
||||||
|
const destId = snap[card.id];
|
||||||
|
const dest =
|
||||||
|
destId === 'leftCol' ? leftCol :
|
||||||
|
destId === 'rightCol' ? rightCol :
|
||||||
|
destId === 'sidebarDropArea' ? sidebar : null;
|
||||||
|
if (dest) {
|
||||||
|
// clear sticky widths if coming from sidebar/header
|
||||||
|
card.style.width = '';
|
||||||
|
card.style.minWidth = '';
|
||||||
|
dest.appendChild(card);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// prevent first-run default from stomping this on reload
|
||||||
|
localStorage.setItem('layoutDefaultApplied_v1', '1');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// New: small-screen detector
|
// New: small-screen detector
|
||||||
function isSmallScreen() { return window.innerWidth < MEDIUM_MIN; }
|
function isSmallScreen() { return window.innerWidth < MEDIUM_MIN; }
|
||||||
|
|
||||||
@@ -321,17 +368,70 @@ function applySidebarCollapsed() {
|
|||||||
sidebar.style.display = collapsed ? 'none' : 'block';
|
sidebar.style.display = collapsed ? 'none' : 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHeaderHost() {
|
||||||
|
// 1) exact structure you shared
|
||||||
|
let host = document.querySelector('.header-container .header-left');
|
||||||
|
// 2) fallback to header root
|
||||||
|
if (!host) host = document.querySelector('.header-container');
|
||||||
|
// 3) last resort
|
||||||
|
if (!host) host = document.querySelector('header');
|
||||||
|
return host || document.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountHeaderToggle(btn) {
|
||||||
|
const host = document.querySelector('.header-left');
|
||||||
|
const logoA = host?.querySelector('a');
|
||||||
|
if (!host) return;
|
||||||
|
|
||||||
|
// ensure positioning context
|
||||||
|
if (getComputedStyle(host).position === 'static') host.style.position = 'relative';
|
||||||
|
|
||||||
|
if (logoA) {
|
||||||
|
logoA.insertAdjacentElement('afterend', btn); // sibling of <a>, not inside it
|
||||||
|
} else {
|
||||||
|
host.appendChild(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(btn.style, {
|
||||||
|
position: 'absolute',
|
||||||
|
left: '100px', // adjust position beside the logo
|
||||||
|
top: '10px',
|
||||||
|
zIndex: '10010',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function ensureZonesToggle() {
|
function ensureZonesToggle() {
|
||||||
let btn = document.getElementById('sidebarToggleFloating');
|
let btn = document.getElementById('sidebarToggleFloating');
|
||||||
|
const host = getHeaderHost();
|
||||||
|
if (!host) return;
|
||||||
|
|
||||||
|
// ensure the host is a positioning context
|
||||||
|
const hostStyle = getComputedStyle(host);
|
||||||
|
if (hostStyle.position === 'static') {
|
||||||
|
host.style.position = 'relative';
|
||||||
|
}
|
||||||
|
|
||||||
if (!btn) {
|
if (!btn) {
|
||||||
btn = document.createElement('button');
|
btn = document.createElement('button');
|
||||||
|
|
||||||
btn.id = 'sidebarToggleFloating';
|
btn.id = 'sidebarToggleFloating';
|
||||||
btn.type = 'button';
|
btn.type = 'button'; // not a submit
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation(); // don't bubble into the <a href="index.html">
|
||||||
|
setSidebarCollapsed(!isSidebarCollapsed());
|
||||||
|
updateSidebarToggleUI(); // refresh icon/title
|
||||||
|
});
|
||||||
|
['mousedown','mouseup','pointerdown','pointerup'].forEach(evt =>
|
||||||
|
btn.addEventListener(evt, (e) => e.stopPropagation())
|
||||||
|
);
|
||||||
btn.setAttribute('aria-label', 'Toggle panels');
|
btn.setAttribute('aria-label', 'Toggle panels');
|
||||||
|
|
||||||
Object.assign(btn.style, {
|
Object.assign(btn.style, {
|
||||||
position: 'fixed',
|
position: 'absolute', // <-- key change (was fixed)
|
||||||
left: `${TOGGLE_LEFT_PX}px`,
|
top: '8px', // adjust to line up with header content
|
||||||
top: `${TOGGLE_TOP_PX}px`,
|
left: '100px', // place to the right of your logo; tweak as needed
|
||||||
zIndex: '1000',
|
zIndex: '1000',
|
||||||
width: '38px',
|
width: '38px',
|
||||||
height: '38px',
|
height: '38px',
|
||||||
@@ -344,13 +444,31 @@ function ensureZonesToggle() {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
padding: '0',
|
padding: '0',
|
||||||
lineHeight: '0',
|
lineHeight: '0'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// dark-mode polish (optional)
|
||||||
|
if (document.body.classList.contains('dark-mode')) {
|
||||||
|
btn.style.background = '#2c2c2c';
|
||||||
|
btn.style.border = '1px solid #555';
|
||||||
|
btn.style.boxShadow = '0 2px 6px rgba(0,0,0,.35)';
|
||||||
|
btn.style.color = '#e0e0e0';
|
||||||
|
}
|
||||||
|
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
setZonesCollapsed(!isZonesCollapsed());
|
setZonesCollapsed(!isZonesCollapsed());
|
||||||
});
|
});
|
||||||
document.body.appendChild(btn);
|
|
||||||
|
// Insert right after the logo if present, else just append to host
|
||||||
|
const afterLogo = host.querySelector('.header-logo');
|
||||||
|
if (afterLogo && afterLogo.parentNode) {
|
||||||
|
afterLogo.parentNode.insertBefore(btn, afterLogo.nextSibling);
|
||||||
|
} else {
|
||||||
|
host.appendChild(btn);
|
||||||
}
|
}
|
||||||
|
themeToggleButton(btn);
|
||||||
|
}
|
||||||
|
|
||||||
updateZonesToggleUI();
|
updateZonesToggleUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,8 +494,21 @@ function updateZonesToggleUI() {
|
|||||||
iconEl.style.transform = 'rotate(0deg)';
|
iconEl.style.transform = 'rotate(0deg)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
themeToggleButton(btn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(function watchThemeChanges() {
|
||||||
|
const obs = new MutationObserver((muts) => {
|
||||||
|
for (const m of muts) {
|
||||||
|
if (m.type === 'attributes' && m.attributeName === 'class') {
|
||||||
|
const btn = document.getElementById('sidebarToggleFloating');
|
||||||
|
if (btn) themeToggleButton(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
obs.observe(document.body, { attributes: true });
|
||||||
|
})();
|
||||||
|
|
||||||
// create a small floating toggle button (no HTML edits needed)
|
// create a small floating toggle button (no HTML edits needed)
|
||||||
function ensureSidebarToggle() {
|
function ensureSidebarToggle() {
|
||||||
const sidebar = getSidebar();
|
const sidebar = getSidebar();
|
||||||
@@ -433,55 +564,61 @@ export function loadSidebarOrder() {
|
|||||||
const sidebar = getSidebar();
|
const sidebar = getSidebar();
|
||||||
if (!sidebar) return;
|
if (!sidebar) return;
|
||||||
|
|
||||||
|
const defaultAppliedKey = 'layoutDefaultApplied_v1';
|
||||||
|
const defaultAlready = localStorage.getItem(defaultAppliedKey) === '1';
|
||||||
|
|
||||||
const orderStr = localStorage.getItem('sidebarOrder');
|
const orderStr = localStorage.getItem('sidebarOrder');
|
||||||
const headerOrderStr = localStorage.getItem('headerOrder');
|
const headerOrderStr = localStorage.getItem('headerOrder');
|
||||||
const defaultAppliedKey = 'layoutDefaultApplied_v1'; // bump if logic changes
|
|
||||||
|
|
||||||
|
if (applySnapshotIfPresent()) {
|
||||||
|
updateTopZoneLayout();
|
||||||
|
updateSidebarVisibility();
|
||||||
|
applyZonesCollapsed();
|
||||||
|
ensureZonesToggle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// One-time default: if no saved order and no header order,
|
// Only apply the one-time default if *not* initialized yet
|
||||||
// put cards into the sidebar on all ≥ MEDIUM_MIN screens.
|
if (!defaultAlready &&
|
||||||
if ((!orderStr || !JSON.parse(orderStr || '[]').length) &&
|
((!orderStr || !JSON.parse(orderStr || '[]').length) &&
|
||||||
(!headerOrderStr || !JSON.parse(headerOrderStr || '[]').length)) {
|
(!headerOrderStr || !JSON.parse(headerOrderStr || '[]').length))) {
|
||||||
|
|
||||||
const isLargeEnough = window.innerWidth >= MEDIUM_MIN;
|
const isLargeEnough = window.innerWidth >= MEDIUM_MIN;
|
||||||
if (isLargeEnough) {
|
if (isLargeEnough) {
|
||||||
const mainWrapper = document.querySelector('.main-wrapper');
|
const mainWrapper = document.querySelector('.main-wrapper');
|
||||||
if (mainWrapper) mainWrapper.style.display = 'flex';
|
if (mainWrapper) mainWrapper.style.display = 'flex';
|
||||||
|
|
||||||
const moved = [];
|
const moved = [];
|
||||||
['uploadCard', 'folderManagementCard'].forEach(id => {
|
['uploadCard', 'folderManagementCard'].forEach(id => {
|
||||||
const card = document.getElementById(id);
|
const card = document.getElementById(id);
|
||||||
if (card && card.parentNode?.id !== 'sidebarDropArea') {
|
if (card && card.parentNode?.id !== 'sidebarDropArea') {
|
||||||
// clear any sticky widths from header/top
|
|
||||||
card.style.width = '';
|
card.style.width = '';
|
||||||
card.style.minWidth = '';
|
card.style.minWidth = '';
|
||||||
getSidebar().appendChild(card);
|
getSidebar().appendChild(card);
|
||||||
animateVerticalSlide(card);
|
animateVerticalSlide(card);
|
||||||
moved.push(id);
|
moved.push(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (moved.length) {
|
if (moved.length) {
|
||||||
localStorage.setItem('sidebarOrder', JSON.stringify(moved));
|
localStorage.setItem('sidebarOrder', JSON.stringify(moved));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No sidebar order saved yet: if user has header icons saved, do nothing (they've customized)
|
// Mark initialized so this default never fires again
|
||||||
|
localStorage.setItem(defaultAppliedKey, '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user has header icons saved, honor that and bail
|
||||||
const headerOrder = JSON.parse(headerOrderStr || '[]');
|
const headerOrder = JSON.parse(headerOrderStr || '[]');
|
||||||
if (Array.isArray(headerOrder) && headerOrder.length > 0) {
|
if (Array.isArray(headerOrder) && headerOrder.length > 0) {
|
||||||
updateSidebarVisibility();
|
updateSidebarVisibility();
|
||||||
//applySidebarCollapsed();
|
|
||||||
//ensureSidebarToggle();
|
|
||||||
applyZonesCollapsed();
|
applyZonesCollapsed();
|
||||||
ensureZonesToggle();
|
ensureZonesToggle();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// One-time default: on medium screens, start cards in the sidebar
|
if (!defaultAlready && isMediumScreen()) {
|
||||||
const alreadyApplied = localStorage.getItem(defaultAppliedKey) === '1';
|
|
||||||
if (!alreadyApplied && isMediumScreen()) {
|
|
||||||
const mainWrapper = document.querySelector('.main-wrapper');
|
const mainWrapper = document.querySelector('.main-wrapper');
|
||||||
if (mainWrapper) mainWrapper.style.display = 'flex';
|
if (mainWrapper) mainWrapper.style.display = 'flex';
|
||||||
|
|
||||||
@@ -498,13 +635,11 @@ if (moved.length) {
|
|||||||
|
|
||||||
if (moved.length) {
|
if (moved.length) {
|
||||||
localStorage.setItem('sidebarOrder', JSON.stringify(moved));
|
localStorage.setItem('sidebarOrder', JSON.stringify(moved));
|
||||||
localStorage.setItem(defaultAppliedKey, '1');
|
localStorage.setItem(defaultAppliedKey, '1'); // mark initialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSidebarVisibility();
|
updateSidebarVisibility();
|
||||||
//applySidebarCollapsed();
|
|
||||||
//ensureSidebarToggle();
|
|
||||||
applyZonesCollapsed();
|
applyZonesCollapsed();
|
||||||
ensureZonesToggle();
|
ensureZonesToggle();
|
||||||
}
|
}
|
||||||
@@ -553,7 +688,10 @@ function updateSidebarVisibility() {
|
|||||||
|
|
||||||
// Save order and update toggle visibility
|
// Save order and update toggle visibility
|
||||||
saveSidebarOrder();
|
saveSidebarOrder();
|
||||||
ensureZonesToggle(); // will hide/remove the button if no cards
|
// Mark layout initialized so the first-run default won't fire on reload
|
||||||
|
localStorage.setItem('layoutDefaultApplied_v1', '1');
|
||||||
|
|
||||||
|
ensureZonesToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW: Save header order to localStorage.
|
// NEW: Save header order to localStorage.
|
||||||
@@ -953,8 +1091,7 @@ export function initDragAndDrop() {
|
|||||||
|
|
||||||
showHeaderDropZone();
|
showHeaderDropZone();
|
||||||
const topZone = getTopZone();
|
const topZone = getTopZone();
|
||||||
if (topZone)
|
if (topZone) {
|
||||||
{
|
|
||||||
topZone.style.display = '';
|
topZone.style.display = '';
|
||||||
ensureTopZonePlaceholder();
|
ensureTopZonePlaceholder();
|
||||||
}
|
}
|
||||||
@@ -1123,6 +1260,7 @@ export function initDragAndDrop() {
|
|||||||
hideHeaderDropZone();
|
hideHeaderDropZone();
|
||||||
|
|
||||||
cleanupTopZoneAfterDrop();
|
cleanupTopZoneAfterDrop();
|
||||||
|
snapshotZoneLocations();
|
||||||
const tz = getTopZone();
|
const tz = getTopZone();
|
||||||
if (tz) tz.style.minHeight = '';
|
if (tz) tz.style.minHeight = '';
|
||||||
}
|
}
|
||||||
|
|||||||
2
public/js/version.js
Normal file
2
public/js/version.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// generated by CI
|
||||||
|
window.APP_VERSION = 'v1.6.6';
|
||||||
Reference in New Issue
Block a user