Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebabb561d6 | ||
|
|
30761b6dad | ||
|
|
9ef40da5aa |
46
CHANGELOG.md
46
CHANGELOG.md
@@ -1,5 +1,51 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Changes 10/23/2025 (v1.6.2)
|
||||||
|
|
||||||
|
feat(i18n,auth): add Simplified Chinese (zh-CN) and expose in User Panel
|
||||||
|
|
||||||
|
- Add zh-CN locale to i18n.js with full key set.
|
||||||
|
- Introduce chinese_simplified label key across locales.
|
||||||
|
- Added some missing labels
|
||||||
|
- Update language selector mapping to include zh-CN (English/Spanish/French/German/简体中文).
|
||||||
|
- Wire zh-CN into Auth/User Panel (authModals) language dropdown.
|
||||||
|
- Fallback-safe rendering for language names when a key is missing.
|
||||||
|
|
||||||
|
ui: fix “Change Password” button sizing in User Panel
|
||||||
|
|
||||||
|
- Keep consistent padding and font size for cleaner layout
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes 10/23/2025 (v1.6.1)
|
||||||
|
|
||||||
|
feat(ui): unified zone toggle + polished interactions for sidebar/top cards
|
||||||
|
|
||||||
|
- Add floating toggle button styling (hover lift, press, focus ring, ripple)
|
||||||
|
for #zonesToggleFloating and #sidebarToggleFloating (CSS).
|
||||||
|
- Ensure icons are visible and centered; enforce consistent sizing/color.
|
||||||
|
- Introduce unified “zones collapsed” state persisted via `localStorage.zonesCollapsed`.
|
||||||
|
- Update dragAndDrop.js to:
|
||||||
|
- manage a single floating toggle for both Sidebar and Top Zone
|
||||||
|
- keep toggle visible when cards are in Top Zone; hide only when both cards are in Header
|
||||||
|
- rotate icon 90° when both cards are in Top Zone and panels are open
|
||||||
|
- respect collapsed state during DnD flows and on load
|
||||||
|
- preserve original DnD behaviors and saved orders (sidebar/header)
|
||||||
|
- Minor layout/visibility fixes during drag (clear temp heights; honor collapsed).
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- No breaking API changes; existing `sidebarOrder` / `headerOrder` continue to work.
|
||||||
|
- New key: `zonesCollapsed` (string '0'/'1') controls visibility of Sidebar + Top Zone.
|
||||||
|
|
||||||
|
UX:
|
||||||
|
|
||||||
|
- Floating toggle feels more “material”: subtle hover elevation, press feedback,
|
||||||
|
focus ring, and click ripple to restore the prior interactive feel.
|
||||||
|
- Icons remain legible on white (explicit color set), centered in the circular button.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Changes 10/22/2025 (v1.6.0)
|
## Changes 10/22/2025 (v1.6.0)
|
||||||
|
|
||||||
feat(acl): granular per-folder permissions + stricter gates; WebDAV & UI aligned
|
feat(acl): granular per-folder permissions + stricter gates; WebDAV & UI aligned
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ With drag-and-drop uploads, in-browser editing, secure user logins (SSO & TOTP 2
|
|||||||
|
|
||||||
- 🎨 **Responsive UI (Dark/Light Mode):** Modern, mobile-friendly design with persistent preferences (theme, layout, last folder, etc.).
|
- 🎨 **Responsive UI (Dark/Light Mode):** Modern, mobile-friendly design with persistent preferences (theme, layout, last folder, etc.).
|
||||||
|
|
||||||
- 🌐 **Internationalization:** English, Spanish, French, and German available. Community translations welcome.
|
- 🌐 **Internationalization:** English, Spanish, French, German & Simplified Chinese available. Community translations welcome.
|
||||||
|
|
||||||
- ⚙️ **Lightweight & Self-Contained:** Runs on PHP 8.3+, no external DB required. Single-folder or Docker deployment with minimal footprint, optimized for Unraid and self-hosting.
|
- ⚙️ **Lightweight & Self-Contained:** Runs on PHP 8.3+, no external DB required. Single-folder or Docker deployment with minimal footprint, optimized for Unraid and self-hosting.
|
||||||
|
|
||||||
|
|||||||
@@ -2036,10 +2036,9 @@ body.dark-mode .admin-panel-content label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#openChangePasswordModalBtn {
|
#openChangePasswordModalBtn {
|
||||||
width: auto;
|
width: max-content;
|
||||||
padding: 5px 10px;
|
padding: 6px 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-right: 300px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#changePasswordModal {
|
#changePasswordModal {
|
||||||
@@ -2309,3 +2308,68 @@ body.dark-mode .user-dropdown .user-menu .item:hover {
|
|||||||
|
|
||||||
:root { --perm-caret: #444; } /* light */
|
:root { --perm-caret: #444; } /* light */
|
||||||
body.dark-mode { --perm-caret: #ccc; } /* dark */
|
body.dark-mode { --perm-caret: #ccc; } /* dark */
|
||||||
|
|
||||||
|
#zonesToggleFloating,
|
||||||
|
#sidebarToggleFloating {
|
||||||
|
transition:
|
||||||
|
transform 160ms cubic-bezier(.2,.0,.2,1),
|
||||||
|
box-shadow 160ms cubic-bezier(.2,.0,.2,1),
|
||||||
|
border-color 160ms cubic-bezier(.2,.0,.2,1),
|
||||||
|
background-color 160ms cubic-bezier(.2,.0,.2,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#zonesToggleFloating .material-icons,
|
||||||
|
#zonesToggleFloating .material-icons-outlined,
|
||||||
|
#sidebarToggleFloating .material-icons,
|
||||||
|
#sidebarToggleFloating .material-icons-outlined {
|
||||||
|
color: #333 !important;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#zonesToggleFloating:hover,
|
||||||
|
#sidebarToggleFloating:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0,0,0,.14);
|
||||||
|
border-color: #cfcfcf;
|
||||||
|
}
|
||||||
|
|
||||||
|
#zonesToggleFloating:active,
|
||||||
|
#sidebarToggleFloating:active {
|
||||||
|
transform: translateY(0) scale(.96);
|
||||||
|
box-shadow: 0 3px 8px rgba(0,0,0,.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#zonesToggleFloating:focus-visible,
|
||||||
|
#sidebarToggleFloating:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow:
|
||||||
|
0 6px 16px rgba(0,0,0,.14),
|
||||||
|
0 0 0 3px rgba(25,118,210,.25); /* soft brandy ring */
|
||||||
|
}
|
||||||
|
|
||||||
|
#zonesToggleFloating::after,
|
||||||
|
#sidebarToggleFloating::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: radial-gradient(circle, rgba(0,0,0,.12) 0%, rgba(0,0,0,0) 60%);
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
transition: transform 300ms ease, opacity 450ms ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#zonesToggleFloating:active::after,
|
||||||
|
#sidebarToggleFloating:active::after {
|
||||||
|
transform: scale(1.4);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#zonesToggleFloating.is-collapsed,
|
||||||
|
#sidebarToggleFloating.is-collapsed {
|
||||||
|
background: #fafafa;
|
||||||
|
border-color: #e2e2e2;
|
||||||
|
}
|
||||||
@@ -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.0";
|
const version = "v1.6.2";
|
||||||
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>`;
|
||||||
|
|
||||||
|
|
||||||
@@ -1141,7 +1141,7 @@ async function loadUserFlagsList() {
|
|||||||
<th>${t("read_only")}</th>
|
<th>${t("read_only")}</th>
|
||||||
<th>${t("disable_upload")}</th>
|
<th>${t("disable_upload")}</th>
|
||||||
<th>${t("can_share")}</th>
|
<th>${t("can_share")}</th>
|
||||||
<th>bypassOwnership</th>
|
<th>${t("bypass_ownership")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>${rows || `<tr><td colspan="6">${t("no_users_found")}</td></tr>`}</tbody>
|
<tbody>${rows || `<tr><td colspan="6">${t("no_users_found")}</td></tr>`}</tbody>
|
||||||
|
|||||||
@@ -328,10 +328,19 @@ export async function openUserPanel() {
|
|||||||
const langSel = document.createElement('select');
|
const langSel = document.createElement('select');
|
||||||
langSel.id = 'languageSelector';
|
langSel.id = 'languageSelector';
|
||||||
langSel.className = 'form-select';
|
langSel.className = 'form-select';
|
||||||
['en', 'es', 'fr', 'de'].forEach(code => {
|
const languages = [
|
||||||
|
{ code: 'en', labelKey: 'english', fallback: 'English' },
|
||||||
|
{ code: 'es', labelKey: 'spanish', fallback: 'Español' },
|
||||||
|
{ code: 'fr', labelKey: 'french', fallback: 'Français' },
|
||||||
|
{ code: 'de', labelKey: 'german', fallback: 'Deutsch' },
|
||||||
|
{ code: 'zh-CN', labelKey: 'chinese_simplified', fallback: '简体中文' },
|
||||||
|
];
|
||||||
|
|
||||||
|
languages.forEach(({ code, labelKey, fallback }) => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
opt.value = code;
|
opt.value = code;
|
||||||
opt.textContent = t(code === 'en' ? 'english' : code === 'es' ? 'spanish' : code === 'fr' ? 'french' : 'german');
|
// use i18n if available, otherwise fallback
|
||||||
|
opt.textContent = (typeof t === 'function' ? t(labelKey) : '') || fallback;
|
||||||
langSel.appendChild(opt);
|
langSel.appendChild(opt);
|
||||||
});
|
});
|
||||||
langSel.value = localStorage.getItem('language') || 'en';
|
langSel.value = localStorage.getItem('language') || 'en';
|
||||||
|
|||||||
@@ -1,29 +1,234 @@
|
|||||||
// dragAndDrop.js
|
// dragAndDrop.js
|
||||||
// This file handles drag-and-drop functionality for cards in the sidebar, header and top drop zones.
|
// Enhances the dashboard with drag-and-drop functionality for cards:
|
||||||
// It also manages the visibility of the sidebar and header drop zones based on the current state of the application.
|
// - injects a tiny floating toggle btn
|
||||||
// It includes functions to save and load the order of cards in the sidebar and header from localStorage.
|
// - remembers collapsed state in localStorage
|
||||||
// It also includes functions to handle the drag-and-drop events, including mouse movements and drop zones.
|
// - keeps the original card DnD + order logic intact
|
||||||
// It uses CSS classes to manage the appearance of the sidebar and header drop zones during drag-and-drop operations.
|
|
||||||
|
|
||||||
// ---- responsive defaults ----
|
// ---- responsive defaults ----
|
||||||
const MEDIUM_MIN = 1205; // matches your small-screen cutoff
|
const MEDIUM_MIN = 1205; // matches your small-screen cutoff
|
||||||
const MEDIUM_MAX = 1600; // tweak as you like
|
const MEDIUM_MAX = 1600; // tweak as you like
|
||||||
|
|
||||||
|
const TOGGLE_TOP_PX = 10;
|
||||||
|
const TOGGLE_LEFT_PX = 100;
|
||||||
|
|
||||||
|
const TOGGLE_ICON_OPEN = 'view_sidebar';
|
||||||
|
const TOGGLE_ICON_CLOSED = 'menu';
|
||||||
|
|
||||||
|
function updateSidebarToggleUI() {
|
||||||
|
const btn = document.getElementById('sidebarToggleFloating');
|
||||||
|
const sidebar = getSidebar();
|
||||||
|
if (!btn || !sidebar) return;
|
||||||
|
|
||||||
|
if (!hasSidebarCards()) { btn.remove(); return; }
|
||||||
|
|
||||||
|
const collapsed = isSidebarCollapsed();
|
||||||
|
btn.innerHTML = `<i class="material-icons" aria-hidden="true">${
|
||||||
|
collapsed ? TOGGLE_ICON_CLOSED : TOGGLE_ICON_OPEN
|
||||||
|
}</i>`;
|
||||||
|
btn.title = collapsed ? 'Show sidebar' : 'Hide sidebar';
|
||||||
|
btn.style.display = 'block';
|
||||||
|
btn.classList.toggle('toggle-ping', collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function hasSidebarCards() {
|
||||||
|
const sb = getSidebar();
|
||||||
|
return !!sb && sb.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasTopZoneCards() {
|
||||||
|
const tz = getTopZone();
|
||||||
|
return !!tz && tz.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both cards are in the top zone (upload + folder)
|
||||||
|
function allCardsInTopZone() {
|
||||||
|
const tz = getTopZone();
|
||||||
|
if (!tz) return false;
|
||||||
|
const hasUpload = !!tz.querySelector('#uploadCard');
|
||||||
|
const hasFolder = !!tz.querySelector('#folderManagementCard');
|
||||||
|
return hasUpload && hasFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isZonesCollapsed() {
|
||||||
|
return localStorage.getItem('zonesCollapsed') === '1';
|
||||||
|
}
|
||||||
|
function setZonesCollapsed(collapsed) {
|
||||||
|
localStorage.setItem('zonesCollapsed', collapsed ? '1' : '0');
|
||||||
|
applyZonesCollapsed();
|
||||||
|
updateZonesToggleUI();
|
||||||
|
}
|
||||||
|
function applyZonesCollapsed() {
|
||||||
|
const collapsed = isZonesCollapsed();
|
||||||
|
const sidebar = getSidebar();
|
||||||
|
const topZone = getTopZone();
|
||||||
|
|
||||||
|
if (sidebar) sidebar.style.display = collapsed ? 'none' : (hasSidebarCards() ? 'block' : 'none');
|
||||||
|
if (topZone) topZone.style.display = collapsed ? 'none' : (hasTopZoneCards() ? '' : '');
|
||||||
|
}
|
||||||
|
|
||||||
function isMediumScreen() {
|
function isMediumScreen() {
|
||||||
const w = window.innerWidth;
|
const w = window.innerWidth;
|
||||||
return w >= MEDIUM_MIN && w < MEDIUM_MAX;
|
return w >= MEDIUM_MIN && w < MEDIUM_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----- Sidebar collapse state helpers -----
|
||||||
|
function getSidebar() {
|
||||||
|
return document.getElementById('sidebarDropArea');
|
||||||
|
}
|
||||||
|
function getTopZone() {
|
||||||
|
return document.getElementById('uploadFolderRow');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSidebarCollapsed() {
|
||||||
|
return localStorage.getItem('sidebarCollapsed') === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSidebarCollapsed(collapsed) {
|
||||||
|
localStorage.setItem('sidebarCollapsed', collapsed ? '1' : '0');
|
||||||
|
applySidebarCollapsed();
|
||||||
|
updateSidebarToggleUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySidebarCollapsed() {
|
||||||
|
const sidebar = getSidebar();
|
||||||
|
if (!sidebar) return;
|
||||||
|
|
||||||
|
// We avoid hard-coding layout assumptions: simply hide/show the sidebar area.
|
||||||
|
// If you want a sliding effect, add CSS later; JS will just toggle display.
|
||||||
|
const collapsed = isSidebarCollapsed();
|
||||||
|
sidebar.style.display = collapsed ? 'none' : 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureZonesToggle() {
|
||||||
|
// show only if at least one zone *can* show a card
|
||||||
|
const shouldShow = hasSidebarCards() || hasTopZoneCards();
|
||||||
|
|
||||||
|
let btn = document.getElementById('sidebarToggleFloating');
|
||||||
|
if (!shouldShow) {
|
||||||
|
if (btn) btn.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!btn) {
|
||||||
|
btn = document.createElement('button');
|
||||||
|
btn.id = 'sidebarToggleFloating';
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.setAttribute('aria-label', 'Toggle panels');
|
||||||
|
Object.assign(btn.style, {
|
||||||
|
position: 'fixed',
|
||||||
|
left: `${TOGGLE_LEFT_PX}px`,
|
||||||
|
top: `${TOGGLE_TOP_PX}px`,
|
||||||
|
zIndex: '1000',
|
||||||
|
width: '38px',
|
||||||
|
height: '38px',
|
||||||
|
borderRadius: '19px',
|
||||||
|
border: '1px solid #ccc',
|
||||||
|
background: '#fff',
|
||||||
|
cursor: 'pointer',
|
||||||
|
boxShadow: '0 2px 6px rgba(0,0,0,.15)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '0',
|
||||||
|
lineHeight: '0',
|
||||||
|
});
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
setZonesCollapsed(!isZonesCollapsed());
|
||||||
|
});
|
||||||
|
document.body.appendChild(btn);
|
||||||
|
}
|
||||||
|
updateZonesToggleUI();
|
||||||
|
}
|
||||||
|
function updateZonesToggleUI() {
|
||||||
|
const btn = document.getElementById('sidebarToggleFloating');
|
||||||
|
if (!btn) return;
|
||||||
|
|
||||||
|
// if neither zone has cards, remove the toggle
|
||||||
|
if (!hasSidebarCards() && !hasTopZoneCards()) {
|
||||||
|
btn.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collapsed = isZonesCollapsed();
|
||||||
|
const iconName = collapsed ? TOGGLE_ICON_CLOSED : TOGGLE_ICON_OPEN;
|
||||||
|
btn.innerHTML = `<i class="material-icons toggle-icon" aria-hidden="true">${iconName}</i>`;
|
||||||
|
btn.title = collapsed ? 'Show panels' : 'Hide panels';
|
||||||
|
btn.style.display = 'block';
|
||||||
|
|
||||||
|
// Rotate the icon 90° when BOTH cards are in the top zone and panels are open
|
||||||
|
const iconEl = btn.querySelector('.toggle-icon');
|
||||||
|
if (iconEl) {
|
||||||
|
iconEl.style.transition = 'transform 0.2s ease';
|
||||||
|
iconEl.style.display = 'inline-flex';
|
||||||
|
iconEl.style.alignItems = 'center';
|
||||||
|
if (!collapsed && allCardsInTopZone()) {
|
||||||
|
iconEl.style.transform = 'rotate(90deg)';
|
||||||
|
} else {
|
||||||
|
iconEl.style.transform = 'rotate(0deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a small floating toggle button (no HTML edits needed)
|
||||||
|
function ensureSidebarToggle() {
|
||||||
|
const sidebar = getSidebar();
|
||||||
|
if (!sidebar) return;
|
||||||
|
|
||||||
|
// Only show if there are cards
|
||||||
|
if (!hasSidebarCards()) {
|
||||||
|
const existing = document.getElementById('sidebarToggleFloating');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let btn = document.getElementById('sidebarToggleFloating');
|
||||||
|
if (!btn) {
|
||||||
|
btn = document.createElement('button');
|
||||||
|
btn.id = 'sidebarToggleFloating';
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.setAttribute('aria-label', 'Toggle sidebar');
|
||||||
|
|
||||||
|
Object.assign(btn.style, {
|
||||||
|
position: 'fixed',
|
||||||
|
left: `${TOGGLE_LEFT_PX}px`,
|
||||||
|
top: `${TOGGLE_TOP_PX}px`,
|
||||||
|
zIndex: '10010',
|
||||||
|
width: '38px',
|
||||||
|
height: '38px',
|
||||||
|
borderRadius: '19px',
|
||||||
|
border: '1px solid #ccc',
|
||||||
|
background: '#fff',
|
||||||
|
cursor: 'pointer',
|
||||||
|
boxShadow: '0 2px 6px rgba(0,0,0,.15)',
|
||||||
|
display: 'block',
|
||||||
|
});
|
||||||
|
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
setSidebarCollapsed(!isSidebarCollapsed());
|
||||||
|
// icon/title/animation update after state change
|
||||||
|
updateSidebarToggleUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set correct icon/title right away
|
||||||
|
//updateSidebarToggleUI();
|
||||||
|
//applySidebarCollapsed();
|
||||||
|
updateZonesToggleUI();
|
||||||
|
applyZonesCollapsed();
|
||||||
|
}
|
||||||
|
|
||||||
// Moves cards into the sidebar based on the saved order in localStorage.
|
// Moves cards into the sidebar based on the saved order in localStorage.
|
||||||
export function loadSidebarOrder() {
|
export function loadSidebarOrder() {
|
||||||
const sidebar = document.getElementById('sidebarDropArea');
|
const sidebar = getSidebar();
|
||||||
if (!sidebar) return;
|
if (!sidebar) return;
|
||||||
|
|
||||||
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 the suffix if you ever change logic
|
const defaultAppliedKey = 'layoutDefaultApplied_v1'; // bump if logic changes
|
||||||
|
|
||||||
// If we have a saved order (sidebar or header), just honor it as before
|
// If we have a saved order (sidebar), honor it as before
|
||||||
if (orderStr) {
|
if (orderStr) {
|
||||||
const order = JSON.parse(orderStr || '[]');
|
const order = JSON.parse(orderStr || '[]');
|
||||||
if (Array.isArray(order) && order.length > 0) {
|
if (Array.isArray(order) && order.length > 0) {
|
||||||
@@ -37,6 +242,11 @@ export function loadSidebarOrder() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
updateSidebarVisibility();
|
updateSidebarVisibility();
|
||||||
|
//applySidebarCollapsed(); // NEW: honor collapsed state
|
||||||
|
//ensureSidebarToggle(); // NEW: inject toggle
|
||||||
|
applyZonesCollapsed();
|
||||||
|
ensureZonesToggle();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,6 +255,10 @@ export function loadSidebarOrder() {
|
|||||||
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();
|
||||||
|
ensureZonesToggle();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,9 +286,12 @@ export function loadSidebarOrder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateSidebarVisibility();
|
updateSidebarVisibility();
|
||||||
|
//applySidebarCollapsed();
|
||||||
|
//ensureSidebarToggle();
|
||||||
|
applyZonesCollapsed();
|
||||||
|
ensureZonesToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function loadHeaderOrder() {
|
export function loadHeaderOrder() {
|
||||||
const headerDropArea = document.getElementById('headerDropArea');
|
const headerDropArea = document.getElementById('headerDropArea');
|
||||||
if (!headerDropArea) return;
|
if (!headerDropArea) return;
|
||||||
@@ -104,20 +321,28 @@ export function loadSidebarOrder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Internal helper: update sidebar visibility based on its content.
|
// Internal helper: update sidebar visibility based on its content.
|
||||||
|
// NOTE: do NOT auto-hide if user manually collapsed; that is separate.
|
||||||
function updateSidebarVisibility() {
|
function updateSidebarVisibility() {
|
||||||
const sidebar = document.getElementById('sidebarDropArea');
|
const sidebar = getSidebar();
|
||||||
if (sidebar) {
|
if (!sidebar) return;
|
||||||
const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
|
|
||||||
if (cards.length > 0) {
|
const anyCards = hasSidebarCards();
|
||||||
|
|
||||||
|
// clear any leftover drag height
|
||||||
|
sidebar.style.height = '';
|
||||||
|
|
||||||
|
if (anyCards) {
|
||||||
sidebar.classList.add('active');
|
sidebar.classList.add('active');
|
||||||
sidebar.style.display = 'block';
|
// respect the unified zones-collapsed switch
|
||||||
|
sidebar.style.display = isZonesCollapsed() ? 'none' : 'block';
|
||||||
} else {
|
} else {
|
||||||
sidebar.classList.remove('active');
|
sidebar.classList.remove('active');
|
||||||
sidebar.style.display = 'none';
|
sidebar.style.display = 'none';
|
||||||
}
|
}
|
||||||
// Save the current order in localStorage.
|
|
||||||
|
// Save order and update toggle visibility
|
||||||
saveSidebarOrder();
|
saveSidebarOrder();
|
||||||
}
|
ensureZonesToggle(); // will hide/remove the button if no cards
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW: Save header order to localStorage.
|
// NEW: Save header order to localStorage.
|
||||||
@@ -136,9 +361,10 @@ export function loadSidebarOrder() {
|
|||||||
const leftCol = document.getElementById('leftCol');
|
const leftCol = document.getElementById('leftCol');
|
||||||
const rightCol = document.getElementById('rightCol');
|
const rightCol = document.getElementById('rightCol');
|
||||||
|
|
||||||
const leftIsEmpty = !leftCol.querySelector('#uploadCard');
|
const leftIsEmpty = !leftCol?.querySelector('#uploadCard');
|
||||||
const rightIsEmpty = !rightCol.querySelector('#folderManagementCard');
|
const rightIsEmpty = !rightCol?.querySelector('#folderManagementCard');
|
||||||
|
|
||||||
|
if (leftCol && rightCol) {
|
||||||
if (leftIsEmpty && !rightIsEmpty) {
|
if (leftIsEmpty && !rightIsEmpty) {
|
||||||
leftCol.style.display = 'none';
|
leftCol.style.display = 'none';
|
||||||
rightCol.style.margin = '0 auto';
|
rightCol.style.margin = '0 auto';
|
||||||
@@ -152,6 +378,7 @@ export function loadSidebarOrder() {
|
|||||||
rightCol.style.margin = '';
|
rightCol.style.margin = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// When a card is being dragged, if the top drop zone is empty, set its min-height.
|
// When a card is being dragged, if the top drop zone is empty, set its min-height.
|
||||||
function addTopZoneHighlight() {
|
function addTopZoneHighlight() {
|
||||||
@@ -193,7 +420,7 @@ export function loadSidebarOrder() {
|
|||||||
|
|
||||||
// Internal helper: insert card into sidebar at a proper position based on event.clientY.
|
// Internal helper: insert card into sidebar at a proper position based on event.clientY.
|
||||||
function insertCardInSidebar(card, event) {
|
function insertCardInSidebar(card, event) {
|
||||||
const sidebar = document.getElementById('sidebarDropArea');
|
const sidebar = getSidebar();
|
||||||
if (!sidebar) return;
|
if (!sidebar) return;
|
||||||
const existingCards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
|
const existingCards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
|
||||||
let inserted = false;
|
let inserted = false;
|
||||||
@@ -212,11 +439,13 @@ export function loadSidebarOrder() {
|
|||||||
// Ensure card fills the sidebar.
|
// Ensure card fills the sidebar.
|
||||||
card.style.width = '100%';
|
card.style.width = '100%';
|
||||||
animateVerticalSlide(card);
|
animateVerticalSlide(card);
|
||||||
|
// if user dropped into sidebar, auto-un-collapse if currently collapsed
|
||||||
|
if (isZonesCollapsed()) setZonesCollapsed(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal helper: save the current sidebar card order to localStorage.
|
// Internal helper: save the current sidebar card order to localStorage.
|
||||||
function saveSidebarOrder() {
|
function saveSidebarOrder() {
|
||||||
const sidebar = document.getElementById('sidebarDropArea');
|
const sidebar = getSidebar();
|
||||||
if (sidebar) {
|
if (sidebar) {
|
||||||
const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
|
const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
|
||||||
const order = Array.from(cards).map(card => card.id);
|
const order = Array.from(cards).map(card => card.id);
|
||||||
@@ -227,7 +456,7 @@ export function loadSidebarOrder() {
|
|||||||
// Helper: move cards from sidebar back to the top drop area when on small screens.
|
// Helper: move cards from sidebar back to the top drop area when on small screens.
|
||||||
function moveSidebarCardsToTop() {
|
function moveSidebarCardsToTop() {
|
||||||
if (window.innerWidth < 1205) {
|
if (window.innerWidth < 1205) {
|
||||||
const sidebar = document.getElementById('sidebarDropArea');
|
const sidebar = getSidebar();
|
||||||
if (!sidebar) return;
|
if (!sidebar) return;
|
||||||
const cards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
|
const cards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
|
||||||
cards.forEach(card => {
|
cards.forEach(card => {
|
||||||
@@ -270,9 +499,8 @@ export function loadSidebarOrder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NEW HELPER FUNCTIONS FOR HEADER DROP ZONE ---
|
// --- Header drop zone helpers ---
|
||||||
|
|
||||||
// Show header drop zone and add a "drag-active" class so that the pseudo-element appears.
|
|
||||||
function showHeaderDropZone() {
|
function showHeaderDropZone() {
|
||||||
const headerDropArea = document.getElementById('headerDropArea');
|
const headerDropArea = document.getElementById('headerDropArea');
|
||||||
if (headerDropArea) {
|
if (headerDropArea) {
|
||||||
@@ -281,8 +509,6 @@ export function loadSidebarOrder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide header drop zone by removing the "drag-active" class.
|
|
||||||
// If a header icon is present (i.e. a card was dropped), the drop zone remains visible without the dashed border.
|
|
||||||
function hideHeaderDropZone() {
|
function hideHeaderDropZone() {
|
||||||
const headerDropArea = document.getElementById('headerDropArea');
|
const headerDropArea = document.getElementById('headerDropArea');
|
||||||
if (headerDropArea) {
|
if (headerDropArea) {
|
||||||
@@ -293,12 +519,12 @@ export function loadSidebarOrder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === NEW FUNCTION: Insert card into header drop zone as a material icon ===
|
// Insert card into header drop zone as a material icon
|
||||||
function insertCardInHeader(card, event) {
|
function insertCardInHeader(card, event) {
|
||||||
const headerDropArea = document.getElementById('headerDropArea');
|
const headerDropArea = document.getElementById('headerDropArea');
|
||||||
if (!headerDropArea) return;
|
if (!headerDropArea) return;
|
||||||
|
|
||||||
// For folder management and upload cards, preserve the original by moving it to a hidden container.
|
// Preserve the original by moving it to a hidden container.
|
||||||
if (card.id === 'folderManagementCard' || card.id === 'uploadCard') {
|
if (card.id === 'folderManagementCard' || card.id === 'uploadCard') {
|
||||||
let hiddenContainer = document.getElementById('hiddenCardsContainer');
|
let hiddenContainer = document.getElementById('hiddenCardsContainer');
|
||||||
if (!hiddenContainer) {
|
if (!hiddenContainer) {
|
||||||
@@ -307,27 +533,20 @@ export function loadSidebarOrder() {
|
|||||||
hiddenContainer.style.display = 'none';
|
hiddenContainer.style.display = 'none';
|
||||||
document.body.appendChild(hiddenContainer);
|
document.body.appendChild(hiddenContainer);
|
||||||
}
|
}
|
||||||
// Move the original card to the hidden container if it's not already there.
|
if (card.parentNode?.id !== 'hiddenCardsContainer') {
|
||||||
if (card.parentNode.id !== 'hiddenCardsContainer') {
|
|
||||||
hiddenContainer.appendChild(card);
|
hiddenContainer.appendChild(card);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (card.parentNode) {
|
||||||
// For other cards, simply remove from current container.
|
|
||||||
if (card.parentNode) {
|
|
||||||
card.parentNode.removeChild(card);
|
card.parentNode.removeChild(card);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Create the header icon button.
|
|
||||||
const iconButton = document.createElement('button');
|
const iconButton = document.createElement('button');
|
||||||
iconButton.className = 'header-card-icon';
|
iconButton.className = 'header-card-icon';
|
||||||
// Remove default button styling.
|
|
||||||
iconButton.style.border = 'none';
|
iconButton.style.border = 'none';
|
||||||
iconButton.style.background = 'none';
|
iconButton.style.background = 'none';
|
||||||
iconButton.style.outline = 'none';
|
iconButton.style.outline = 'none';
|
||||||
iconButton.style.cursor = 'pointer';
|
iconButton.style.cursor = 'pointer';
|
||||||
|
|
||||||
// Choose an icon based on the card type with 24px size.
|
|
||||||
if (card.id === 'uploadCard') {
|
if (card.id === 'uploadCard') {
|
||||||
iconButton.innerHTML = '<i class="material-icons" style="font-size:24px;">cloud_upload</i>';
|
iconButton.innerHTML = '<i class="material-icons" style="font-size:24px;">cloud_upload</i>';
|
||||||
} else if (card.id === 'folderManagementCard') {
|
} else if (card.id === 'folderManagementCard') {
|
||||||
@@ -336,39 +555,35 @@ export function loadSidebarOrder() {
|
|||||||
iconButton.innerHTML = '<i class="material-icons" style="font-size:24px;">insert_drive_file</i>';
|
iconButton.innerHTML = '<i class="material-icons" style="font-size:24px;">insert_drive_file</i>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save a reference to the card in the icon button.
|
|
||||||
iconButton.cardElement = card;
|
iconButton.cardElement = card;
|
||||||
// Associate this icon with the card for future removal.
|
|
||||||
card.headerIconButton = iconButton;
|
card.headerIconButton = iconButton;
|
||||||
|
|
||||||
let modal = null;
|
let modal = null;
|
||||||
let isLocked = false;
|
let isLocked = false;
|
||||||
let hoverActive = false;
|
let hoverActive = false;
|
||||||
|
|
||||||
// showModal: When triggered, ensure the card is attached to the modal.
|
|
||||||
function showModal() {
|
function showModal() {
|
||||||
if (!modal) {
|
if (!modal) {
|
||||||
modal = document.createElement('div');
|
modal = document.createElement('div');
|
||||||
modal.className = 'header-card-modal';
|
modal.className = 'header-card-modal';
|
||||||
modal.style.position = 'fixed';
|
Object.assign(modal.style, {
|
||||||
modal.style.top = '55px';
|
position: 'fixed',
|
||||||
modal.style.right = '80px';
|
top: '55px',
|
||||||
modal.style.zIndex = '11000';
|
right: '80px',
|
||||||
// Render the modal but initially keep it hidden.
|
zIndex: '11000',
|
||||||
modal.style.display = 'block';
|
display: 'block',
|
||||||
modal.style.visibility = 'hidden';
|
visibility: 'hidden',
|
||||||
modal.style.opacity = '0';
|
opacity: '0',
|
||||||
modal.style.background = 'none';
|
background: 'none',
|
||||||
modal.style.border = 'none';
|
border: 'none',
|
||||||
modal.style.padding = '0';
|
padding: '0',
|
||||||
modal.style.boxShadow = 'none';
|
boxShadow: 'none',
|
||||||
|
});
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
// Attach modal hover events.
|
|
||||||
modal.addEventListener('mouseover', handleMouseOver);
|
modal.addEventListener('mouseover', handleMouseOver);
|
||||||
modal.addEventListener('mouseout', handleMouseOut);
|
modal.addEventListener('mouseout', handleMouseOut);
|
||||||
iconButton.modalInstance = modal;
|
iconButton.modalInstance = modal;
|
||||||
}
|
}
|
||||||
// If the card isn't already in the modal, remove it from the hidden container and attach it.
|
|
||||||
if (!modal.contains(card)) {
|
if (!modal.contains(card)) {
|
||||||
const hiddenContainer = document.getElementById('hiddenCardsContainer');
|
const hiddenContainer = document.getElementById('hiddenCardsContainer');
|
||||||
if (hiddenContainer && hiddenContainer.contains(card)) {
|
if (hiddenContainer && hiddenContainer.contains(card)) {
|
||||||
@@ -376,17 +591,14 @@ export function loadSidebarOrder() {
|
|||||||
}
|
}
|
||||||
modal.appendChild(card);
|
modal.appendChild(card);
|
||||||
}
|
}
|
||||||
// Reveal the modal.
|
|
||||||
modal.style.visibility = 'visible';
|
modal.style.visibility = 'visible';
|
||||||
modal.style.opacity = '1';
|
modal.style.opacity = '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
// hideModal: Hide the modal and return the card to the hidden container.
|
|
||||||
function hideModal() {
|
function hideModal() {
|
||||||
if (modal && !isLocked && !hoverActive) {
|
if (modal && !isLocked && !hoverActive) {
|
||||||
modal.style.visibility = 'hidden';
|
modal.style.visibility = 'hidden';
|
||||||
modal.style.opacity = '0';
|
modal.style.opacity = '0';
|
||||||
// Return the card to the hidden container.
|
|
||||||
const hiddenContainer = document.getElementById('hiddenCardsContainer');
|
const hiddenContainer = document.getElementById('hiddenCardsContainer');
|
||||||
if (hiddenContainer && modal.contains(card)) {
|
if (hiddenContainer && modal.contains(card)) {
|
||||||
hiddenContainer.appendChild(card);
|
hiddenContainer.appendChild(card);
|
||||||
@@ -408,33 +620,32 @@ export function loadSidebarOrder() {
|
|||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach hover events to the icon.
|
|
||||||
iconButton.addEventListener('mouseover', handleMouseOver);
|
iconButton.addEventListener('mouseover', handleMouseOver);
|
||||||
iconButton.addEventListener('mouseout', handleMouseOut);
|
iconButton.addEventListener('mouseout', handleMouseOut);
|
||||||
|
|
||||||
// Toggle the locked state on click so the modal stays open.
|
|
||||||
iconButton.addEventListener('click', (e) => {
|
iconButton.addEventListener('click', (e) => {
|
||||||
isLocked = !isLocked;
|
isLocked = !isLocked;
|
||||||
if (isLocked) {
|
if (isLocked) showModal();
|
||||||
showModal();
|
else hideModal();
|
||||||
} else {
|
|
||||||
hideModal();
|
|
||||||
}
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Append the header icon button into the header drop zone.
|
|
||||||
headerDropArea.appendChild(iconButton);
|
headerDropArea.appendChild(iconButton);
|
||||||
// Save the updated header order.
|
|
||||||
saveHeaderOrder();
|
saveHeaderOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Main Drag and Drop Initialization ===
|
// === Main Drag and Drop Initialization ===
|
||||||
export function initDragAndDrop() {
|
export function initDragAndDrop() {
|
||||||
function run() {
|
function run() {
|
||||||
|
// make sure toggle exists even if user hasn't dragged yet
|
||||||
|
// ensureSidebarToggle();
|
||||||
|
//applySidebarCollapsed();
|
||||||
|
applyZonesCollapsed();
|
||||||
|
ensureZonesToggle();
|
||||||
|
|
||||||
const draggableCards = document.querySelectorAll('#uploadCard, #folderManagementCard');
|
const draggableCards = document.querySelectorAll('#uploadCard, #folderManagementCard');
|
||||||
draggableCards.forEach(card => {
|
draggableCards.forEach(card => {
|
||||||
if (!card.dataset.originalContainerId) {
|
if (!card.dataset.originalContainerId && card.parentNode) {
|
||||||
card.dataset.originalContainerId = card.parentNode.id;
|
card.dataset.originalContainerId = card.parentNode.id;
|
||||||
}
|
}
|
||||||
const header = card.querySelector('.card-header');
|
const header = card.querySelector('.card-header');
|
||||||
@@ -451,37 +662,32 @@ export function loadSidebarOrder() {
|
|||||||
header.addEventListener('mousedown', function (e) {
|
header.addEventListener('mousedown', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const card = this.closest('.card');
|
const card = this.closest('.card');
|
||||||
// Capture the card's initial bounding rectangle.
|
|
||||||
const initialRect = card.getBoundingClientRect();
|
const initialRect = card.getBoundingClientRect();
|
||||||
const originX = ((e.clientX - initialRect.left) / initialRect.width) * 100;
|
const originX = ((e.clientX - initialRect.left) / initialRect.width) * 100;
|
||||||
const originY = ((e.clientY - initialRect.top) / initialRect.height) * 100;
|
const originY = ((e.clientY - initialRect.top) / initialRect.height) * 100;
|
||||||
card.style.transformOrigin = `${originX}% ${originY}%`;
|
card.style.transformOrigin = `${originX}% ${originY}%`;
|
||||||
|
|
||||||
// Store the initial rect so we use it later.
|
|
||||||
dragTimer = setTimeout(() => {
|
dragTimer = setTimeout(() => {
|
||||||
isDragging = true;
|
isDragging = true;
|
||||||
card.classList.add('dragging');
|
card.classList.add('dragging');
|
||||||
card.style.pointerEvents = 'none';
|
card.style.pointerEvents = 'none';
|
||||||
addTopZoneHighlight();
|
addTopZoneHighlight();
|
||||||
|
|
||||||
const sidebar = document.getElementById('sidebarDropArea');
|
const sidebar = getSidebar();
|
||||||
if (sidebar) {
|
if (sidebar) {
|
||||||
sidebar.classList.add('active');
|
sidebar.classList.add('active');
|
||||||
sidebar.style.display = 'block';
|
sidebar.style.display = isSidebarCollapsed() ? 'none' : 'block';
|
||||||
sidebar.classList.add('highlight');
|
sidebar.classList.add('highlight');
|
||||||
sidebar.style.height = '800px';
|
sidebar.style.height = '800px';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show header drop zone while dragging.
|
|
||||||
showHeaderDropZone();
|
showHeaderDropZone();
|
||||||
|
|
||||||
// Use the stored initialRect.
|
|
||||||
initialLeft = initialRect.left + window.pageXOffset;
|
initialLeft = initialRect.left + window.pageXOffset;
|
||||||
initialTop = initialRect.top + window.pageYOffset;
|
initialTop = initialRect.top + window.pageYOffset;
|
||||||
offsetX = e.pageX - initialLeft;
|
offsetX = e.pageX - initialLeft;
|
||||||
offsetY = e.pageY - initialTop;
|
offsetY = e.pageY - initialTop;
|
||||||
|
|
||||||
// Remove any associated header icon if present.
|
|
||||||
if (card.headerIconButton) {
|
if (card.headerIconButton) {
|
||||||
if (card.headerIconButton.parentNode) {
|
if (card.headerIconButton.parentNode) {
|
||||||
card.headerIconButton.parentNode.removeChild(card.headerIconButton);
|
card.headerIconButton.parentNode.removeChild(card.headerIconButton);
|
||||||
@@ -493,7 +699,6 @@ export function loadSidebarOrder() {
|
|||||||
saveHeaderOrder();
|
saveHeaderOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append card to body and fix its dimensions.
|
|
||||||
document.body.appendChild(card);
|
document.body.appendChild(card);
|
||||||
card.style.position = 'absolute';
|
card.style.position = 'absolute';
|
||||||
card.style.left = initialLeft + 'px';
|
card.style.left = initialLeft + 'px';
|
||||||
@@ -505,6 +710,7 @@ export function loadSidebarOrder() {
|
|||||||
card.style.zIndex = '10000';
|
card.style.zIndex = '10000';
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
header.addEventListener('mouseup', function () {
|
header.addEventListener('mouseup', function () {
|
||||||
clearTimeout(dragTimer);
|
clearTimeout(dragTimer);
|
||||||
});
|
});
|
||||||
@@ -524,13 +730,12 @@ export function loadSidebarOrder() {
|
|||||||
card.classList.remove('dragging');
|
card.classList.remove('dragging');
|
||||||
removeTopZoneHighlight();
|
removeTopZoneHighlight();
|
||||||
|
|
||||||
const sidebar = document.getElementById('sidebarDropArea');
|
const sidebar = getSidebar();
|
||||||
if (sidebar) {
|
if (sidebar) {
|
||||||
sidebar.classList.remove('highlight');
|
sidebar.classList.remove('highlight');
|
||||||
sidebar.style.height = '';
|
sidebar.style.height = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any existing header icon if present.
|
|
||||||
if (card.headerIconButton) {
|
if (card.headerIconButton) {
|
||||||
if (card.headerIconButton.parentNode) {
|
if (card.headerIconButton.parentNode) {
|
||||||
card.headerIconButton.parentNode.removeChild(card.headerIconButton);
|
card.headerIconButton.parentNode.removeChild(card.headerIconButton);
|
||||||
@@ -547,7 +752,7 @@ export function loadSidebarOrder() {
|
|||||||
let droppedInHeader = false;
|
let droppedInHeader = false;
|
||||||
|
|
||||||
// Check if dropped in sidebar drop zone.
|
// Check if dropped in sidebar drop zone.
|
||||||
const sidebarElem = document.getElementById('sidebarDropArea');
|
const sidebarElem = getSidebar();
|
||||||
if (sidebarElem) {
|
if (sidebarElem) {
|
||||||
const rect = sidebarElem.getBoundingClientRect();
|
const rect = sidebarElem.getBoundingClientRect();
|
||||||
const dropZoneBottom = rect.top + 800; // Virtual drop zone height.
|
const dropZoneBottom = rect.top + 800; // Virtual drop zone height.
|
||||||
@@ -561,6 +766,7 @@ export function loadSidebarOrder() {
|
|||||||
droppedInSidebar = true;
|
droppedInSidebar = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the top drop zone.
|
// Check the top drop zone.
|
||||||
const topRow = document.getElementById('uploadFolderRow');
|
const topRow = document.getElementById('uploadFolderRow');
|
||||||
if (!droppedInSidebar && topRow) {
|
if (!droppedInSidebar && topRow) {
|
||||||
@@ -582,7 +788,6 @@ export function loadSidebarOrder() {
|
|||||||
updateTopZoneLayout();
|
updateTopZoneLayout();
|
||||||
container.appendChild(card);
|
container.appendChild(card);
|
||||||
droppedInTop = true;
|
droppedInTop = true;
|
||||||
// Set a fixed width during animation.
|
|
||||||
card.style.width = "363px";
|
card.style.width = "363px";
|
||||||
animateVerticalSlide(card);
|
animateVerticalSlide(card);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -591,6 +796,7 @@ export function loadSidebarOrder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the header drop zone.
|
// Check the header drop zone.
|
||||||
const headerDropArea = document.getElementById('headerDropArea');
|
const headerDropArea = document.getElementById('headerDropArea');
|
||||||
if (!droppedInSidebar && !droppedInTop && headerDropArea) {
|
if (!droppedInSidebar && !droppedInTop && headerDropArea) {
|
||||||
@@ -605,6 +811,7 @@ export function loadSidebarOrder() {
|
|||||||
droppedInHeader = true;
|
droppedInHeader = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If card was not dropped in any zone, return it to its original container.
|
// If card was not dropped in any zone, return it to its original container.
|
||||||
if (!droppedInSidebar && !droppedInTop && !droppedInHeader) {
|
if (!droppedInSidebar && !droppedInTop && !droppedInHeader) {
|
||||||
const orig = document.getElementById(card.dataset.originalContainerId);
|
const orig = document.getElementById(card.dataset.originalContainerId);
|
||||||
@@ -635,8 +842,6 @@ export function loadSidebarOrder() {
|
|||||||
|
|
||||||
updateTopZoneLayout();
|
updateTopZoneLayout();
|
||||||
updateSidebarVisibility();
|
updateSidebarVisibility();
|
||||||
|
|
||||||
// Hide header drop zone if no icon is present.
|
|
||||||
hideHeaderDropZone();
|
hideHeaderDropZone();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ const translations = {
|
|||||||
"spanish": "Spanish",
|
"spanish": "Spanish",
|
||||||
"french": "French",
|
"french": "French",
|
||||||
"german": "German",
|
"german": "German",
|
||||||
|
"chinese_simplified": "Chinese (Simplified)",
|
||||||
"use_totp_code_instead": "Use TOTP Code instead",
|
"use_totp_code_instead": "Use TOTP Code instead",
|
||||||
"submit_recovery_code": "Submit Recovery Code",
|
"submit_recovery_code": "Submit Recovery Code",
|
||||||
"please_enter_recovery_code": "Please enter your recovery code.",
|
"please_enter_recovery_code": "Please enter your recovery code.",
|
||||||
@@ -275,7 +276,13 @@ const translations = {
|
|||||||
"newfile_placeholder": "New file name",
|
"newfile_placeholder": "New file name",
|
||||||
"file_created_successfully": "File created successfully!",
|
"file_created_successfully": "File created successfully!",
|
||||||
"error_creating_file": "Error creating file",
|
"error_creating_file": "Error creating file",
|
||||||
"file_created": "File created successfully!"
|
"file_created": "File created successfully!",
|
||||||
|
"no_access_to_resource": "You do not have access to this resource.",
|
||||||
|
"can_share": "Can Share",
|
||||||
|
"bypass_ownership": "Bypass Ownership",
|
||||||
|
"error_loading_user_grants": "Error loading user grants",
|
||||||
|
"click_to_edit": "Click to edit",
|
||||||
|
"folder_access": "Folder Access"
|
||||||
},
|
},
|
||||||
es: {
|
es: {
|
||||||
"please_log_in_to_continue": "Por favor, inicie sesión para continuar.",
|
"please_log_in_to_continue": "Por favor, inicie sesión para continuar.",
|
||||||
@@ -458,6 +465,7 @@ const translations = {
|
|||||||
"spanish": "Español",
|
"spanish": "Español",
|
||||||
"french": "Francés",
|
"french": "Francés",
|
||||||
"german": "Alemán",
|
"german": "Alemán",
|
||||||
|
"chinese_simplified": "Chino (simplificado)",
|
||||||
"use_totp_code_instead": "Usar código TOTP en su lugar",
|
"use_totp_code_instead": "Usar código TOTP en su lugar",
|
||||||
"submit_recovery_code": "Enviar código de recuperación",
|
"submit_recovery_code": "Enviar código de recuperación",
|
||||||
"please_enter_recovery_code": "Por favor, ingrese su código de recuperación.",
|
"please_enter_recovery_code": "Por favor, ingrese su código de recuperación.",
|
||||||
@@ -686,6 +694,7 @@ const translations = {
|
|||||||
"spanish": "Espagnol",
|
"spanish": "Espagnol",
|
||||||
"french": "Français",
|
"french": "Français",
|
||||||
"german": "Allemand",
|
"german": "Allemand",
|
||||||
|
"chinese_simplified": "Chinois (simplifié)",
|
||||||
"use_totp_code_instead": "Utiliser le code TOTP à la place",
|
"use_totp_code_instead": "Utiliser le code TOTP à la place",
|
||||||
"submit_recovery_code": "Soumettre le code de récupération",
|
"submit_recovery_code": "Soumettre le code de récupération",
|
||||||
"please_enter_recovery_code": "Veuillez entrer votre code de récupération.",
|
"please_enter_recovery_code": "Veuillez entrer votre code de récupération.",
|
||||||
@@ -923,6 +932,7 @@ const translations = {
|
|||||||
"spanish": "Spanisch",
|
"spanish": "Spanisch",
|
||||||
"french": "Französisch",
|
"french": "Französisch",
|
||||||
"german": "Deutsch",
|
"german": "Deutsch",
|
||||||
|
"chinese_simplified": "Chinesisch (vereinfacht)",
|
||||||
"use_totp_code_instead": "Stattdessen TOTP-Code verwenden",
|
"use_totp_code_instead": "Stattdessen TOTP-Code verwenden",
|
||||||
"submit_recovery_code": "Wiederherstellungscode absenden",
|
"submit_recovery_code": "Wiederherstellungscode absenden",
|
||||||
"please_enter_recovery_code": "Bitte geben Sie Ihren Wiederherstellungscode ein.",
|
"please_enter_recovery_code": "Bitte geben Sie Ihren Wiederherstellungscode ein.",
|
||||||
@@ -972,7 +982,275 @@ const translations = {
|
|||||||
"show": "Zeige",
|
"show": "Zeige",
|
||||||
"items_per_page": "elemente pro seite",
|
"items_per_page": "elemente pro seite",
|
||||||
"columns": "Spalten"
|
"columns": "Spalten"
|
||||||
|
},
|
||||||
|
"zh-CN": {
|
||||||
|
"please_log_in_to_continue": "请登录以继续。",
|
||||||
|
"no_files_selected": "未选择文件。",
|
||||||
|
"confirm_delete_files": "确定要删除所选的 {count} 个文件吗?",
|
||||||
|
"element_not_found": "未找到 ID 为 \"{id}\" 的元素。",
|
||||||
|
"search_placeholder": "搜索文件、标签和上传者…",
|
||||||
|
"search_placeholder_advanced": "高级搜索:文件、标签、上传者和内容…",
|
||||||
|
"basic_search_tooltip": "基础搜索:按文件名、标签和上传者搜索。",
|
||||||
|
"advanced_search_tooltip": "高级搜索:包括文件内容、文件名、标签和上传者。",
|
||||||
|
"file_name": "文件名",
|
||||||
|
"date_modified": "修改日期",
|
||||||
|
"upload_date": "上传日期",
|
||||||
|
"file_size": "文件大小",
|
||||||
|
"uploader": "上传者",
|
||||||
|
"enter_totp_code": "输入 TOTP 验证码",
|
||||||
|
"use_recovery_code_instead": "改用恢复代码",
|
||||||
|
"enter_recovery_code": "输入恢复代码",
|
||||||
|
"editing": "正在编辑",
|
||||||
|
"decrease_font": "A-",
|
||||||
|
"increase_font": "A+",
|
||||||
|
"save": "保存",
|
||||||
|
"close": "关闭",
|
||||||
|
"no_files_found": "未找到文件。",
|
||||||
|
"switch_to_table_view": "切换到表格视图",
|
||||||
|
"switch_to_gallery_view": "切换到图库视图",
|
||||||
|
"share_file": "分享文件",
|
||||||
|
"set_expiration": "设置到期时间:",
|
||||||
|
"password_optional": "密码(可选):",
|
||||||
|
"generate_share_link": "生成分享链接",
|
||||||
|
"shareable_link": "可分享链接:",
|
||||||
|
"copy_link": "复制链接",
|
||||||
|
"tag_file": "标记文件",
|
||||||
|
"tag_name": "标签名称:",
|
||||||
|
"tag_color": "标签颜色:",
|
||||||
|
"save_tag": "保存标签",
|
||||||
|
"light_mode": "浅色模式",
|
||||||
|
"dark_mode": "深色模式",
|
||||||
|
"upload_instruction": "将文件/文件夹拖到此处,或点击“选择文件”",
|
||||||
|
"no_files_selected_default": "未选择文件",
|
||||||
|
"choose_files": "选择文件",
|
||||||
|
"delete_selected": "删除所选",
|
||||||
|
"copy_selected": "复制所选",
|
||||||
|
"move_selected": "移动所选",
|
||||||
|
"tag_selected": "标记所选",
|
||||||
|
"download_zip": "下载 ZIP",
|
||||||
|
"extract_zip": "解压 ZIP",
|
||||||
|
"preview": "预览",
|
||||||
|
"edit": "编辑",
|
||||||
|
"rename": "重命名",
|
||||||
|
"trash_empty": "回收站为空。",
|
||||||
|
"no_trash_selected": "未选择要还原的回收站项目。",
|
||||||
|
|
||||||
|
"title": "FileRise",
|
||||||
|
"header_title": "FileRise",
|
||||||
|
"header_title_text": "标题文本",
|
||||||
|
"logout": "退出登录",
|
||||||
|
"change_password": "更改密码",
|
||||||
|
"restore_text": "还原或",
|
||||||
|
"delete_text": "删除回收站项目",
|
||||||
|
"restore_selected": "还原所选",
|
||||||
|
"restore_all": "全部还原",
|
||||||
|
"delete_selected_trash": "删除所选",
|
||||||
|
"delete_all": "全部删除",
|
||||||
|
"upload_header": "上传文件/文件夹",
|
||||||
|
|
||||||
|
"folder_navigation": "文件夹导航与管理",
|
||||||
|
"create_folder": "创建文件夹",
|
||||||
|
"create_folder_title": "创建文件夹",
|
||||||
|
"enter_folder_name": "输入文件夹名称",
|
||||||
|
"cancel": "取消",
|
||||||
|
"create": "创建",
|
||||||
|
"rename_folder": "重命名文件夹",
|
||||||
|
"rename_folder_title": "重命名文件夹",
|
||||||
|
"rename_folder_placeholder": "输入新的文件夹名称",
|
||||||
|
"delete_folder": "删除文件夹",
|
||||||
|
"delete_folder_title": "删除文件夹",
|
||||||
|
"delete_folder_message": "确定要删除此文件夹吗?",
|
||||||
|
"folder_help": "文件夹帮助",
|
||||||
|
"folder_help_item_1": "点击文件夹以查看其中的文件。",
|
||||||
|
"folder_help_item_2": "使用 [-] 折叠,使用 [+] 展开文件夹。",
|
||||||
|
"folder_help_item_3": "选择一个文件夹并点击“创建文件夹”以添加子文件夹。",
|
||||||
|
"folder_help_item_4": "要重命名或删除文件夹,请选择后点击相应按钮。",
|
||||||
|
|
||||||
|
"actions": "操作",
|
||||||
|
"file_list_title": "文件列表(根目录)",
|
||||||
|
"files_in": "文件位于",
|
||||||
|
"delete_files": "删除文件",
|
||||||
|
"delete_selected_files_title": "删除所选文件",
|
||||||
|
"delete_files_message": "确定要删除所选文件吗?",
|
||||||
|
"copy_files": "复制文件",
|
||||||
|
"copy_files_title": "复制所选文件",
|
||||||
|
"copy_files_message": "选择目标文件夹以复制所选文件:",
|
||||||
|
"move_files": "移动文件",
|
||||||
|
"move_files_title": "移动所选文件",
|
||||||
|
"move_files_message": "选择目标文件夹以移动所选文件:",
|
||||||
|
"move": "移动",
|
||||||
|
"extract_zip_button": "解压 ZIP",
|
||||||
|
"download_zip_title": "将所选文件打包为 ZIP 下载",
|
||||||
|
"download_zip_prompt": "输入 ZIP 文件名:",
|
||||||
|
"zip_placeholder": "files.zip",
|
||||||
|
"share": "分享",
|
||||||
|
"total_files": "文件总数",
|
||||||
|
"total_size": "总大小",
|
||||||
|
"prev": "上一页",
|
||||||
|
"next": "下一页",
|
||||||
|
"page": "第",
|
||||||
|
"of": "页,共",
|
||||||
|
|
||||||
|
"login": "登录",
|
||||||
|
"remember_me": "记住我",
|
||||||
|
"login_oidc": "使用 OIDC 登录",
|
||||||
|
"basic_http_login": "使用基本 HTTP 登录",
|
||||||
|
|
||||||
|
"change_password_title": "更改密码",
|
||||||
|
"old_password": "旧密码",
|
||||||
|
"new_password": "新密码",
|
||||||
|
"confirm_new_password": "确认新密码",
|
||||||
|
|
||||||
|
"create_new_user_title": "创建新用户",
|
||||||
|
"username": "用户名:",
|
||||||
|
"password": "密码:",
|
||||||
|
"enter_password": "密码",
|
||||||
|
"preparing_download": "正在准备下载…",
|
||||||
|
"download_file": "下载文件",
|
||||||
|
"confirm_or_change_filename": "确认或修改下载文件名:",
|
||||||
|
"filename": "文件名",
|
||||||
|
"download": "下载",
|
||||||
|
"grant_admin": "授予管理员权限",
|
||||||
|
"save_user": "保存用户",
|
||||||
|
|
||||||
|
"remove_user_title": "删除用户",
|
||||||
|
"select_user_remove": "选择要删除的用户:",
|
||||||
|
"delete_user": "删除用户",
|
||||||
|
|
||||||
|
"rename_file_title": "重命名文件",
|
||||||
|
"rename_file_placeholder": "输入新的文件名",
|
||||||
|
|
||||||
|
"share_folder": "分享文件夹",
|
||||||
|
"allow_uploads": "允许上传",
|
||||||
|
"share_link_generated": "已生成分享链接",
|
||||||
|
"error_generating_share_link": "生成分享链接时出错",
|
||||||
|
"custom": "自定义",
|
||||||
|
"duration": "持续时间",
|
||||||
|
"seconds": "秒",
|
||||||
|
"minutes": "分钟",
|
||||||
|
"hours": "小时",
|
||||||
|
"days": "天",
|
||||||
|
"custom_duration_warning": "⚠️ 使用较长的到期时间可能存在安全风险,请谨慎使用。",
|
||||||
|
|
||||||
|
"folder_share": "分享文件夹",
|
||||||
|
|
||||||
|
"yes": "是",
|
||||||
|
"no": "否",
|
||||||
|
"unsaved_changes_confirm": "您有未保存的更改,确定要关闭而不保存吗?",
|
||||||
|
"delete": "删除",
|
||||||
|
"upload": "上传",
|
||||||
|
"copy": "复制",
|
||||||
|
"extract": "解压",
|
||||||
|
"user": "用户:",
|
||||||
|
"unknown_error": "未知错误",
|
||||||
|
"link_copied": "链接已复制到剪贴板",
|
||||||
|
"weeks": "周",
|
||||||
|
"months": "月",
|
||||||
|
|
||||||
|
"dark_mode_toggle": "深色模式",
|
||||||
|
"light_mode_toggle": "浅色模式",
|
||||||
|
"switch_to_light_mode": "切换到浅色模式",
|
||||||
|
"switch_to_dark_mode": "切换到深色模式",
|
||||||
|
|
||||||
|
"header_settings": "标题设置",
|
||||||
|
"shared_max_upload_size_bytes_title": "共享最大上传大小",
|
||||||
|
"shared_max_upload_size_bytes": "共享最大上传大小(字节)",
|
||||||
|
"max_bytes_shared_uploads_note": "请输入共享文件夹上传的最大允许字节数",
|
||||||
|
"manage_shared_links": "管理分享链接",
|
||||||
|
"folder_shares": "文件夹分享",
|
||||||
|
"file_shares": "文件分享",
|
||||||
|
"loading": "正在加载…",
|
||||||
|
"error_loading_share_links": "加载分享链接时出错",
|
||||||
|
"share_deleted_successfully": "分享已成功删除",
|
||||||
|
"error_deleting_share": "删除分享时出错",
|
||||||
|
"password_protected": "受密码保护",
|
||||||
|
"no_shared_links_available": "暂无可用的分享链接",
|
||||||
|
|
||||||
|
"admin_panel": "管理员面板",
|
||||||
|
"user_panel": "用户面板",
|
||||||
|
"user_settings": "用户设置",
|
||||||
|
"save_profile_picture": "保存头像",
|
||||||
|
"please_select_picture": "请选择图片",
|
||||||
|
"profile_picture_updated": "头像已更新",
|
||||||
|
"error_updating_picture": "更新头像时出错",
|
||||||
|
"trash_restore_delete": "回收站恢复/删除",
|
||||||
|
"totp_settings": "TOTP 设置",
|
||||||
|
"enable_totp": "启用 TOTP",
|
||||||
|
"language": "语言",
|
||||||
|
"select_language": "选择语言",
|
||||||
|
"english": "英语",
|
||||||
|
"spanish": "西班牙语",
|
||||||
|
"french": "法语",
|
||||||
|
"german": "德语",
|
||||||
|
"chinese_simplified": "简体中文",
|
||||||
|
"use_totp_code_instead": "改用 TOTP 验证码",
|
||||||
|
"submit_recovery_code": "提交恢复代码",
|
||||||
|
"please_enter_recovery_code": "请输入您的恢复代码。",
|
||||||
|
"recovery_code_verification_failed": "恢复代码验证失败",
|
||||||
|
"error_verifying_recovery_code": "验证恢复代码时出错",
|
||||||
|
"totp_verification_failed": "TOTP 验证失败",
|
||||||
|
"error_verifying_totp_code": "验证 TOTP 代码时出错",
|
||||||
|
"totp_setup": "TOTP 设置",
|
||||||
|
"scan_qr_code": "请使用验证器应用扫描此二维码。",
|
||||||
|
"enter_totp_confirmation": "输入应用生成的 6 位验证码以确认设置:",
|
||||||
|
"confirm": "确认",
|
||||||
|
"please_enter_valid_code": "请输入有效的 6 位验证码。",
|
||||||
|
"totp_enabled_successfully": "TOTP 启用成功。",
|
||||||
|
"error_generating_recovery_code": "生成恢复代码时出错",
|
||||||
|
"error_loading_qr_code": "加载二维码时出错。",
|
||||||
|
"error_disabling_totp_setting": "禁用 TOTP 设置时出错",
|
||||||
|
"user_management": "用户管理",
|
||||||
|
"add_user": "添加用户",
|
||||||
|
"remove_user": "删除用户",
|
||||||
|
"user_permissions": "用户权限",
|
||||||
|
"oidc_configuration": "OIDC 配置",
|
||||||
|
"oidc_provider_url": "OIDC 提供者 URL",
|
||||||
|
"oidc_client_id": "OIDC 客户端 ID",
|
||||||
|
"oidc_client_secret": "OIDC 客户端密钥",
|
||||||
|
"oidc_redirect_uri": "OIDC 重定向 URI",
|
||||||
|
"global_totp_settings": "全局 TOTP 设置",
|
||||||
|
"global_otpauth_url": "全局 OTPAuth URL",
|
||||||
|
"login_options": "登录选项",
|
||||||
|
"disable_login_form": "禁用登录表单",
|
||||||
|
"disable_basic_http_auth": "禁用基本 HTTP 认证",
|
||||||
|
"disable_oidc_login": "禁用 OIDC 登录",
|
||||||
|
"save_settings": "保存设置",
|
||||||
|
"at_least_one_login_method": "至少保留一种登录方式。",
|
||||||
|
"settings_updated_successfully": "设置已成功更新。",
|
||||||
|
"error_updating_settings": "更新设置时出错",
|
||||||
|
"user_permissions_updated_successfully": "用户权限已成功更新。",
|
||||||
|
"error_updating_permissions": "更新权限时出错",
|
||||||
|
"no_users_found": "未找到用户。",
|
||||||
|
"user_folder_only": "仅限用户文件夹",
|
||||||
|
"read_only": "只读",
|
||||||
|
"disable_upload": "禁用上传",
|
||||||
|
"error_loading_users": "加载用户时出错",
|
||||||
|
"save_permissions": "保存权限",
|
||||||
|
"your_recovery_code": "您的恢复代码",
|
||||||
|
"please_save_recovery_code": "请妥善保存此代码。此代码仅显示一次且只能使用一次。",
|
||||||
|
"ok": "确定",
|
||||||
|
"show": "显示",
|
||||||
|
"items_per_page": "每页项目数",
|
||||||
|
"columns": "列",
|
||||||
|
"row_height": "行高",
|
||||||
|
"api_docs": "API 文档",
|
||||||
|
"show_folders_above_files": "在文件上方显示文件夹",
|
||||||
|
"display": "显示",
|
||||||
|
"create_file": "创建文件",
|
||||||
|
"create_new_file": "创建新文件",
|
||||||
|
"enter_file_name": "输入文件名",
|
||||||
|
"newfile_placeholder": "新文件名",
|
||||||
|
"file_created_successfully": "文件创建成功!",
|
||||||
|
"error_creating_file": "创建文件时出错",
|
||||||
|
"file_created": "文件创建成功!",
|
||||||
|
"no_access_to_resource": "您无权访问此资源。",
|
||||||
|
"can_share": "可分享",
|
||||||
|
"bypass_ownership": "绕过所有权限制",
|
||||||
|
"error_loading_user_grants": "加载用户授权时出错",
|
||||||
|
"click_to_edit": "点击编辑",
|
||||||
|
"folder_access": "文件夹访问"
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let currentLocale = 'en';
|
let currentLocale = 'en';
|
||||||
|
|||||||
Reference in New Issue
Block a user