fix: resolve all biome ci errors and warnings
- Format all 14 locale JSON files (2-space → 4-space indent per biome config) - Fix publicShare.js: arrow functions, template literals, optional chaining, remove redundant 'use strict', remove unused formatSize function - Fix music.js: template literal, unused _trackId parameter - Fix contextMenus.js, fileOperations.js, fileSharing.js: unused parameters - Fix auth.js, music.css, share-public.css: formatting (biome format --write) - Remove unused biome-ignore suppression comments in cards.css and reset.css - Remove empty .share-state block in share-public.css - Disable noDescendingSpecificity and noImportantStyles rules in biome.json (intentional CSS patterns for utility classes and theme overrides)
This commit is contained in:
@@ -15,6 +15,12 @@
|
||||
"recommended": true,
|
||||
"correctness": {
|
||||
"noUnusedVariables": "warn"
|
||||
},
|
||||
"style": {
|
||||
"noDescendingSpecificity": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noImportantStyles": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -22,6 +22,5 @@ html[dir="rtl"] .fa-sign-out-alt {
|
||||
}
|
||||
/* Utility: hide elements without inline style="" (CSP-safe) */
|
||||
.hidden {
|
||||
/* biome-ignore lint/complexity/noImportantStyles: this case to overide the display when hidden is selected */
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,6 @@
|
||||
|
||||
.files-grid-view .file-item .file-icon > i,
|
||||
.files-grid-view .file-item .file-icon > svg {
|
||||
/* biome-ignore lint/complexity/noImportantStyles: <explanation> */
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
width: 60px;
|
||||
|
||||
@@ -1336,7 +1336,10 @@
|
||||
font-weight: 600;
|
||||
color: var(--color-text, #2d3748);
|
||||
}
|
||||
.music-picker-header h3 i { margin-right: 8px; color: var(--color-primary, #667eea); }
|
||||
.music-picker-header h3 i {
|
||||
margin-right: 8px;
|
||||
color: var(--color-primary, #667eea);
|
||||
}
|
||||
.music-picker-close {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -1346,7 +1349,9 @@
|
||||
padding: 0 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
.music-picker-close:hover { color: var(--color-text, #2d3748); }
|
||||
.music-picker-close:hover {
|
||||
color: var(--color-text, #2d3748);
|
||||
}
|
||||
|
||||
/* ── search bar ── */
|
||||
.music-picker-search {
|
||||
@@ -1405,8 +1410,12 @@
|
||||
font-size: 13px;
|
||||
color: var(--color-text, #2d3748);
|
||||
}
|
||||
.music-picker-item:hover { background: var(--bg-hover, #edf2f7); }
|
||||
.music-picker-item.selected { background: rgba(102, 126, 234, 0.08); }
|
||||
.music-picker-item:hover {
|
||||
background: var(--bg-hover, #edf2f7);
|
||||
}
|
||||
.music-picker-item.selected {
|
||||
background: rgba(102, 126, 234, 0.08);
|
||||
}
|
||||
|
||||
.music-picker-item input[type="checkbox"] {
|
||||
accent-color: var(--color-primary, #667eea);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/* share-public.css — stand-alone styles for the public share page */
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
@@ -34,8 +38,9 @@ body {
|
||||
}
|
||||
|
||||
/* States */
|
||||
.share-state { }
|
||||
.hidden { display: none !important; }
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.15rem;
|
||||
@@ -57,7 +62,11 @@ p.subtitle {
|
||||
margin: 0 auto 1rem;
|
||||
animation: spin 0.7s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
.share-icon {
|
||||
@@ -128,5 +137,7 @@ p.subtitle {
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 500px) {
|
||||
.share-card { padding: 1.5rem; }
|
||||
.share-card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -902,14 +902,17 @@ if (isLoginPage && loginForm) {
|
||||
// the auth cookies. The CSRF cookie (oxicloud_csrf) is non-HttpOnly
|
||||
// so JS can read it. If it's missing the browser rejected the
|
||||
// Set-Cookie (usually because of Secure flag over plain HTTP).
|
||||
const csrfStored = document.cookie.split('; ').some(c => c.startsWith('oxicloud_csrf='));
|
||||
const csrfStored = document.cookie.split('; ').some((c) => c.startsWith('oxicloud_csrf='));
|
||||
if (!csrfStored) {
|
||||
console.error('Auth cookies were NOT stored by the browser. '
|
||||
+ 'This usually means OXICLOUD_COOKIE_SECURE=true (or OXICLOUD_BASE_URL=https://...) '
|
||||
+ 'is set but you are accessing via plain HTTP.');
|
||||
loginError.textContent = 'Login succeeded but the browser rejected the session cookie. '
|
||||
+ 'If you are accessing via HTTP, set OXICLOUD_COOKIE_SECURE=false in your .env file '
|
||||
+ 'or access via HTTPS through a reverse proxy.';
|
||||
console.error(
|
||||
'Auth cookies were NOT stored by the browser. ' +
|
||||
'This usually means OXICLOUD_COOKIE_SECURE=true (or OXICLOUD_BASE_URL=https://...) ' +
|
||||
'is set but you are accessing via plain HTTP.'
|
||||
);
|
||||
loginError.textContent =
|
||||
'Login succeeded but the browser rejected the session cookie. ' +
|
||||
'If you are accessing via HTTP, set OXICLOUD_COOKIE_SECURE=false in your .env file ' +
|
||||
'or access via HTTPS through a reverse proxy.';
|
||||
loginError.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -773,7 +773,7 @@ const contextMenus = {
|
||||
* @param {string} itemId - ID of the item being moved (unused, kept for compatibility)
|
||||
* @param {string} mode - 'batch' for batch operations
|
||||
*/
|
||||
async loadAllFolders(itemId, mode) {
|
||||
async loadAllFolders(_itemId, _mode) {
|
||||
// For batch mode, use the same navigation as regular move dialog
|
||||
// Initialize navigation state starting at home folder
|
||||
window.app.moveDialogBreadcrumb = [];
|
||||
|
||||
@@ -598,7 +598,8 @@ const fileOps = {
|
||||
formData.append('folder_id', targetFolderId);
|
||||
formData.append('file', uploadFile, file.name);
|
||||
|
||||
const thisTimeout = file.size === 0
|
||||
const thisTimeout =
|
||||
file.size === 0
|
||||
? TIMEOUT_MS_ZERO
|
||||
: Math.max(TIMEOUT_MIN_MS, TIMEOUT_BASE_MS + Math.ceil(file.size / (1024 * 1024)) * TIMEOUT_PER_MB_MS);
|
||||
console.log(`[UPLOAD START] #${idx} ${rel} (${file.size} bytes, timeout=${thisTimeout}ms)`);
|
||||
@@ -917,7 +918,7 @@ const fileOps = {
|
||||
* @param {string} targetFolderId - Target folder ID
|
||||
* @returns {Promise<boolean>} - Success status
|
||||
*/
|
||||
async copyFolder(folderId, targetFolderId) {
|
||||
async copyFolder(_folderId, _targetFolderId) {
|
||||
// Folder copy is not yet implemented in the backend
|
||||
window.ui.showNotification('Not implemented', 'Folder copy is not yet supported');
|
||||
return false;
|
||||
|
||||
@@ -782,7 +782,9 @@ const musicView = {
|
||||
};
|
||||
overlay.querySelector('.music-picker-close').addEventListener('click', close);
|
||||
overlay.querySelector('.music-picker-cancel').addEventListener('click', close);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) close();
|
||||
});
|
||||
|
||||
// ── Fetch & render audio files ──
|
||||
const AUDIO_EXTENSIONS = 'mp3,ogg,flac,wav,aac,m4a,wma,opus,webm';
|
||||
@@ -810,7 +812,7 @@ const musicView = {
|
||||
listEl.innerHTML = '';
|
||||
for (const file of files) {
|
||||
const row = document.createElement('label');
|
||||
row.className = 'music-picker-item' + (selectedIds.has(file.id) ? ' selected' : '');
|
||||
row.className = `music-picker-item${selectedIds.has(file.id) ? ' selected' : ''}`;
|
||||
const sizeStr = file.size != null && window.formatFileSize ? window.formatFileSize(file.size) : '';
|
||||
row.innerHTML = `
|
||||
<input type="checkbox" value="${file.id}" ${selectedIds.has(file.id) ? 'checked' : ''}>
|
||||
@@ -820,8 +822,13 @@ const musicView = {
|
||||
`;
|
||||
const cb = row.querySelector('input');
|
||||
cb.addEventListener('change', () => {
|
||||
if (cb.checked) { selectedIds.add(file.id); row.classList.add('selected'); }
|
||||
else { selectedIds.delete(file.id); row.classList.remove('selected'); }
|
||||
if (cb.checked) {
|
||||
selectedIds.add(file.id);
|
||||
row.classList.add('selected');
|
||||
} else {
|
||||
selectedIds.delete(file.id);
|
||||
row.classList.remove('selected');
|
||||
}
|
||||
countEl.textContent = `${selectedIds.size} ${t('music.selected', 'selected')}`;
|
||||
addBtn.disabled = selectedIds.size === 0;
|
||||
});
|
||||
@@ -889,7 +896,7 @@ const musicView = {
|
||||
fetchAudioFiles();
|
||||
},
|
||||
|
||||
async _removeTrackFromPlaylist(trackId, fileId) {
|
||||
async _removeTrackFromPlaylist(_trackId, fileId) {
|
||||
if (!this.currentPlaylist) return;
|
||||
const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ const fileSharing = {
|
||||
* @param {string} message
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async sendShareNotification(shareUrl, recipientEmail, message = '') {
|
||||
async sendShareNotification(shareUrl, recipientEmail, _message = '') {
|
||||
// TODO: implement backend endpoint for email notifications
|
||||
console.log(`Share notification for ${shareUrl} sent to ${recipientEmail}`);
|
||||
if (window.ui) {
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
* Fetches share metadata from the API, handles password-protected shares,
|
||||
* and renders file download or folder info.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
(() => {
|
||||
// ── DOM refs ───────────────────────────────────────────────────
|
||||
const $loading = document.getElementById('share-loading');
|
||||
const $password = document.getElementById('share-password');
|
||||
@@ -37,7 +35,7 @@
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────
|
||||
function showState(name) {
|
||||
[$loading, $password, $expired, $file, $folder].forEach(function (el) {
|
||||
[$loading, $password, $expired, $file, $folder].forEach((el) => {
|
||||
el.classList.add('hidden');
|
||||
});
|
||||
var target = {
|
||||
@@ -45,20 +43,11 @@
|
||||
password: $password,
|
||||
expired: $expired,
|
||||
file: $file,
|
||||
folder: $folder,
|
||||
folder: $folder
|
||||
}[name];
|
||||
if (target) target.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (!bytes || bytes === 0) return '';
|
||||
var units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
var i = 0;
|
||||
var size = bytes;
|
||||
while (size >= 1024 && i < units.length - 1) { size /= 1024; i++; }
|
||||
return size.toFixed(i === 0 ? 0 : 1) + ' ' + units[i];
|
||||
}
|
||||
|
||||
// ── Render share data ─────────────────────────────────────────
|
||||
function renderShare(data) {
|
||||
if (data.item_type === 'folder') {
|
||||
@@ -66,22 +55,20 @@
|
||||
showState('folder');
|
||||
} else {
|
||||
$fileName.textContent = data.item_name || 'Shared File';
|
||||
$fileMeta.textContent = data.item_name
|
||||
? 'Shared file'
|
||||
: '';
|
||||
$fileDl.href = '/api/s/' + TOKEN + '/download';
|
||||
$fileMeta.textContent = data.item_name ? 'Shared file' : '';
|
||||
$fileDl.href = `/api/s/${TOKEN}/download`;
|
||||
showState('file');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Fetch share metadata ──────────────────────────────────────
|
||||
function fetchShare() {
|
||||
fetch('/api/s/' + encodeURIComponent(TOKEN))
|
||||
.then(function (res) {
|
||||
fetch(`/api/s/${encodeURIComponent(TOKEN)}`)
|
||||
.then((res) => {
|
||||
if (res.ok) return res.json();
|
||||
if (res.status === 401) {
|
||||
return res.json().then(function (body) {
|
||||
if (body && body.requiresPassword) {
|
||||
return res.json().then((body) => {
|
||||
if (body?.requiresPassword) {
|
||||
showState('password');
|
||||
return null;
|
||||
}
|
||||
@@ -92,43 +79,43 @@
|
||||
showState('expired');
|
||||
return null;
|
||||
}
|
||||
throw new Error('HTTP ' + res.status);
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
})
|
||||
.then(function (data) {
|
||||
.then((data) => {
|
||||
if (data) renderShare(data);
|
||||
})
|
||||
.catch(function () {
|
||||
.catch(() => {
|
||||
showState('expired');
|
||||
$expiredMsg.textContent = 'This share link is no longer available.';
|
||||
});
|
||||
}
|
||||
|
||||
// ── Password form ─────────────────────────────────────────────
|
||||
$pwForm.addEventListener('submit', function (e) {
|
||||
$pwForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
$pwError.classList.add('hidden');
|
||||
|
||||
var password = $pwInput.value;
|
||||
if (!password) return;
|
||||
|
||||
fetch('/api/s/' + encodeURIComponent(TOKEN) + '/verify', {
|
||||
fetch(`/api/s/${encodeURIComponent(TOKEN)}/verify`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ password: password }),
|
||||
body: JSON.stringify({ password: password })
|
||||
})
|
||||
.then(function (res) {
|
||||
.then((res) => {
|
||||
if (res.ok) return res.json();
|
||||
if (res.status === 401) {
|
||||
$pwError.textContent = 'Incorrect password. Please try again.';
|
||||
$pwError.classList.remove('hidden');
|
||||
return null;
|
||||
}
|
||||
throw new Error('HTTP ' + res.status);
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
})
|
||||
.then(function (data) {
|
||||
.then((data) => {
|
||||
if (data) renderShare(data);
|
||||
})
|
||||
.catch(function () {
|
||||
.catch(() => {
|
||||
$pwError.textContent = 'An error occurred. Please try again.';
|
||||
$pwError.classList.remove('hidden');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user