diff --git a/static/css/components/dialogs.css b/static/css/components/dialogs.css
index dc7c7244..cd41e490 100644
--- a/static/css/components/dialogs.css
+++ b/static/css/components/dialogs.css
@@ -499,9 +499,9 @@
/* Playlist track count in selector */
.playlist-track-count {
- margin-left: auto;
- font-size: 12px;
- color: #718096;
+ margin-left: auto;
+ font-size: 12px;
+ color: #718096;
}
/* Custom confirm dialog */
diff --git a/static/css/views/music.css b/static/css/views/music.css
index 7e5c18d2..98ccd415 100644
--- a/static/css/views/music.css
+++ b/static/css/views/music.css
@@ -106,7 +106,9 @@
display: flex;
align-items: center;
justify-content: center;
- transition: transform 0.15s, box-shadow 0.15s;
+ transition:
+ transform 0.15s,
+ box-shadow 0.15s;
box-shadow: 0 2px 8px var(--color-accent-shadow, rgba(255, 94, 58, 0.25));
}
@@ -426,7 +428,9 @@
}
@keyframes spin {
- to { transform: rotate(360deg); }
+ to {
+ transform: rotate(360deg);
+ }
}
/* Dark theme */
@@ -757,7 +761,7 @@
}
.player-queue::after {
- content: '';
+ content: "";
position: absolute;
bottom: -8px;
right: 60px;
@@ -916,7 +920,7 @@
}
.player-btn.repeat-one i::after {
- content: '1';
+ content: "1";
font-size: 8px;
position: absolute;
top: 0;
@@ -1076,7 +1080,9 @@
padding: 4px 6px;
border-radius: 4px;
opacity: 0;
- transition: opacity 0.15s, color 0.15s;
+ transition:
+ opacity 0.15s,
+ color 0.15s;
font-size: 0.85rem;
}
.music-track:hover .music-track-remove-btn {
@@ -1105,7 +1111,7 @@
display: flex;
align-items: center;
justify-content: center;
- background: rgba(0,0,0,0.4);
+ background: rgba(0, 0, 0, 0.4);
color: #fff;
font-size: 1.1rem;
opacity: 0;
@@ -1140,7 +1146,7 @@
.music-shares-overlay {
position: fixed;
inset: 0;
- background: rgba(0,0,0,0.45);
+ background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
@@ -1154,7 +1160,7 @@
max-height: 80vh;
display: flex;
flex-direction: column;
- box-shadow: 0 8px 32px rgba(0,0,0,0.18);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
}
.music-shares-header {
display: flex;
diff --git a/static/js/app/main.js b/static/js/app/main.js
index 9ee50111..d7ac93cb 100644
--- a/static/js/app/main.js
+++ b/static/js/app/main.js
@@ -621,7 +621,7 @@ function setupEventListeners() {
switchToMusicSection();
break;
- case 'nav.trash':
+ case 'nav.trash':
switchToTrashSection();
break;
diff --git a/static/js/app/navigation.js b/static/js/app/navigation.js
index fcb0a98d..b6224177 100644
--- a/static/js/app/navigation.js
+++ b/static/js/app/navigation.js
@@ -334,7 +334,7 @@ function switchToMusicSection() {
// Hide breadcrumb
const breadcrumb = document.querySelector('.breadcrumb');
- breadcrumb?.classList.add("hidden");
+ breadcrumb?.classList.add('hidden');
// Hide file containers
toggleFileContainer(false);
diff --git a/static/js/features/files/contextMenus.js b/static/js/features/files/contextMenus.js
index 1e4bf4b6..468e140c 100644
--- a/static/js/features/files/contextMenus.js
+++ b/static/js/features/files/contextMenus.js
@@ -55,9 +55,9 @@ const contextMenus = {
const option = document.getElementById('add-to-playlist-option');
if (!option) return;
- const targetFile = window.app && window.app.contextMenuTargetFile;
+ const targetFile = window.app?.contextMenuTargetFile;
if (targetFile) {
- const isAudio = targetFile.mime_type && targetFile.mime_type.startsWith('audio/');
+ const isAudio = targetFile.mime_type?.startsWith('audio/');
option.classList.toggle('hidden', !isAudio);
} else {
option.classList.add('hidden');
@@ -1082,7 +1082,7 @@ const contextMenus = {
const dialog = document.getElementById('playlist-dialog');
const container = document.getElementById('playlist-select-container');
const filesInfo = document.getElementById('playlist-dialog-files-info');
-
+
if (!dialog || !container) {
console.error('Playlist dialog elements not found');
return;
@@ -1099,7 +1099,7 @@ const contextMenus = {
// Reset selection
this._selectedPlaylistId = null;
container.innerHTML = '
';
-
+
// Reset add button state
const addBtn = document.getElementById('playlist-add-btn');
if (addBtn) addBtn.disabled = true;
@@ -1112,7 +1112,7 @@ const contextMenus = {
try {
const resp = await fetch('/api/playlists', { credentials: 'include' });
if (!resp.ok) throw new Error('Failed to load playlists');
-
+
const playlists = await resp.json();
this._renderPlaylistSelect(container, playlists);
} catch (err) {
@@ -1122,16 +1122,16 @@ const contextMenus = {
},
_renderPlaylistSelect(container, playlists) {
- const t = (key, fallback) => window.i18n ? window.i18n.t(key, fallback) : fallback;
-
+ const t = (key, fallback) => (window.i18n ? window.i18n.t(key, fallback) : fallback);
+
container.innerHTML = '';
-
+
if (playlists.length === 0) {
container.innerHTML = `${t('music.no_playlists', 'No playlists yet. Create one first!')}
`;
return;
}
- playlists.forEach(playlist => {
+ playlists.forEach((playlist) => {
const item = document.createElement('div');
item.className = 'folder-select-item';
item.dataset.id = playlist.id;
@@ -1140,15 +1140,17 @@ const contextMenus = {
${this._escapeHtml(playlist.name)}
${playlist.track_count || 0} ${t('music.tracks', 'tracks')}
`;
-
+
item.addEventListener('click', () => {
- container.querySelectorAll('.folder-select-item').forEach(el => el.classList.remove('selected'));
+ container.querySelectorAll('.folder-select-item').forEach((el) => {
+ el.classList.remove('selected');
+ });
item.classList.add('selected');
this._selectedPlaylistId = playlist.id;
const addBtn = document.getElementById('playlist-add-btn');
if (addBtn) addBtn.disabled = false;
});
-
+
container.appendChild(item);
});
},
@@ -1156,7 +1158,7 @@ const contextMenus = {
async addSelectedFilesToPlaylist() {
const playlistId = this._selectedPlaylistId;
const files = window.app.playlistDialogFiles || [];
-
+
if (!playlistId || files.length === 0) return;
const addBtn = document.getElementById('playlist-add-btn');
@@ -1170,7 +1172,7 @@ const contextMenus = {
'Content-Type': 'application/json',
...getCsrfHeaders()
},
- body: JSON.stringify({ file_ids: files.map(f => f.id) })
+ body: JSON.stringify({ file_ids: files.map((f) => f.id) })
});
if (!resp.ok) {
@@ -1178,7 +1180,7 @@ const contextMenus = {
throw new Error(err.message || 'Failed to add tracks');
}
- const result = await resp.json();
+ await resp.json();
window.ui.showNotification(
window.i18n ? window.i18n.t('music.added', 'Added!') : 'Added!',
`${files.length} ${files.length === 1 ? 'track' : 'tracks'} ${window.i18n ? window.i18n.t('music.added_to_playlist', 'added to playlist') : 'added to playlist'}`
@@ -1187,7 +1189,7 @@ const contextMenus = {
this.closePlaylistDialog();
// Refresh music view if open
- if (window.musicView && window.musicView.playlists) {
+ if (window.musicView?.playlists) {
window.musicView._loadPlaylists();
}
} catch (err) {
diff --git a/static/js/features/library/music.js b/static/js/features/library/music.js
index eb3687d5..87e0f440 100644
--- a/static/js/features/library/music.js
+++ b/static/js/features/library/music.js
@@ -55,15 +55,15 @@ const musicView = {
if (this.loading) return;
this.loading = true;
this._showLoading(true);
-
+
try {
const resp = await fetch('/api/playlists', {
credentials: 'include',
headers: this._headers()
});
-
+
if (!resp.ok) throw new Error('Failed to load playlists');
-
+
this.playlists = await resp.json();
this._renderPlaylists();
} catch (err) {
@@ -77,7 +77,7 @@ const musicView = {
_renderPlaylists() {
if (!this._container) return;
-
+
const t = (key, fallback = '') => {
return typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
};
@@ -191,7 +191,9 @@ const musicView = {
return;
}
- listEl.innerHTML = this.playlists.map(p => `
+ listEl.innerHTML = this.playlists
+ .map(
+ (p) => `
@@ -201,9 +203,11 @@ const musicView = {
${p.track_count || 0} ${t('music.tracks', 'tracks')}
- `).join('');
+ `
+ )
+ .join('');
- listEl.querySelectorAll('.music-playlist-item').forEach(item => {
+ listEl.querySelectorAll('.music-playlist-item').forEach((item) => {
item.addEventListener('click', () => {
const id = item.dataset.id;
this._selectPlaylist(id);
@@ -264,11 +268,11 @@ const musicView = {
},
async _selectPlaylist(playlistId) {
- const playlist = this.playlists.find(p => p.id === playlistId);
+ const playlist = this.playlists.find((p) => p.id === playlistId);
if (!playlist) return;
this.currentPlaylist = playlist;
-
+
const detailEl = document.getElementById('music-playlist-detail');
const welcomeEl = this._container.querySelector('.music-welcome');
const nameEl = document.getElementById('music-playlist-name');
@@ -301,12 +305,12 @@ const musicView = {
}
const togglePublicBtn = document.getElementById('music-toggle-public-btn');
if (togglePublicBtn) {
- const t2 = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
+ const t2 = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
togglePublicBtn.title = playlist.is_public ? t2('music.make_private', 'Make private') : t2('music.make_public', 'Make public');
togglePublicBtn.classList.toggle('active', playlist.is_public);
}
- document.querySelectorAll('.music-playlist-item').forEach(item => {
+ document.querySelectorAll('.music-playlist-item').forEach((item) => {
item.classList.toggle('active', item.dataset.id === playlistId);
});
@@ -363,7 +367,9 @@ const musicView = {
- ${this.currentTracks.map((track, idx) => `
+ ${this.currentTracks
+ .map(
+ (track, idx) => `
@@ -381,28 +387,28 @@ const musicView = {
- `).join('')}
+ `
+ )
+ .join('')}
`;
-
- const self = this;
- trackListEl.querySelectorAll('.music-track').forEach(row => {
+ trackListEl.querySelectorAll('.music-track').forEach((row) => {
row.addEventListener('click', () => {
- const idx = parseInt(row.dataset.idx);
+ const idx = parseInt(row.dataset.idx, 10);
// Toggle selection
- trackListEl.querySelectorAll('.music-track').forEach(r => {
+ trackListEl.querySelectorAll('.music-track').forEach((r) => {
if (r !== row) r.classList.remove('selected');
});
row.classList.toggle('selected');
- self.selected.clear();
+ this.selected.clear();
if (row.classList.contains('selected')) {
- self.selected.add(idx);
+ this.selected.add(idx);
}
});
row.addEventListener('dblclick', (e) => {
e.preventDefault();
- const idx = parseInt(row.dataset.idx);
- self._playTrack(idx);
+ const idx = parseInt(row.dataset.idx, 10);
+ this._playTrack(idx);
});
// Drag & drop
@@ -413,7 +419,9 @@ const musicView = {
});
row.addEventListener('dragend', () => {
row.classList.remove('dragging');
- trackListEl.querySelectorAll('.music-track').forEach(r => r.classList.remove('drag-over'));
+ trackListEl.querySelectorAll('.music-track').forEach((r) => {
+ r.classList.remove('drag-over');
+ });
});
row.addEventListener('dragover', (e) => {
e.preventDefault();
@@ -429,10 +437,10 @@ const musicView = {
row.addEventListener('drop', (e) => {
e.preventDefault();
row.classList.remove('drag-over');
- const fromIdx = parseInt(e.dataTransfer.getData('text/plain'));
- const toIdx = parseInt(row.dataset.idx);
+ const fromIdx = parseInt(e.dataTransfer.getData('text/plain'), 10);
+ const toIdx = parseInt(row.dataset.idx, 10);
if (fromIdx !== toIdx) {
- self._reorderTrack(fromIdx, toIdx);
+ this._reorderTrack(fromIdx, toIdx);
}
});
@@ -441,7 +449,7 @@ const musicView = {
if (removeBtn) {
removeBtn.addEventListener('click', (e) => {
e.stopPropagation();
- self._removeTrackFromPlaylist(row.dataset.id, row.dataset.fileId);
+ this._removeTrackFromPlaylist(row.dataset.id, row.dataset.fileId);
});
}
});
@@ -491,7 +499,7 @@ const musicView = {
icon: 'fa-music',
confirmText: t('music.create', 'Create')
});
- if (!name || !name.trim()) return;
+ if (!name?.trim()) return;
this._createPlaylist(name.trim());
},
@@ -517,12 +525,22 @@ const musicView = {
this._renderPlaylists();
this._selectPlaylist(playlist.id);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.create_playlist', 'Create Playlist'), text: name });
+ window.notifications.addNotification({
+ icon: 'fa-check-circle',
+ iconClass: 'upload',
+ title: t('music.create_playlist', 'Create Playlist'),
+ text: name
+ });
}
} catch (err) {
console.error('Create playlist error:', err);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
} finally {
if (createBtn) createBtn.disabled = false;
@@ -536,8 +554,11 @@ const musicView = {
if (!this.currentPlaylist) return;
- const confirmed = await new Promise(resolve => {
- if (!window.Modal) { resolve(confirm(t('music.confirm_delete', 'Delete this playlist?'))); return; }
+ const confirmed = await new Promise((resolve) => {
+ if (!window.Modal) {
+ resolve(confirm(t('music.confirm_delete', 'Delete this playlist?')));
+ return;
+ }
window.Modal.prompt({
title: t('music.delete', 'Delete'),
label: t('music.confirm_delete', 'Delete this playlist?'),
@@ -545,7 +566,7 @@ const musicView = {
value: this.currentPlaylist.name,
icon: 'fa-trash',
confirmText: t('music.delete', 'Delete')
- }).then(val => resolve(val !== null));
+ }).then((val) => resolve(val !== null));
});
if (!confirmed) return;
@@ -561,7 +582,7 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to delete playlist');
const deletedName = this.currentPlaylist.name;
- this.playlists = this.playlists.filter(p => p.id !== this.currentPlaylist.id);
+ this.playlists = this.playlists.filter((p) => p.id !== this.currentPlaylist.id);
this.currentPlaylist = null;
this.currentTracks = [];
this._renderPlaylists();
@@ -571,7 +592,12 @@ const musicView = {
} catch (err) {
console.error('Delete playlist error:', err);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
} finally {
if (deleteBtn) deleteBtn.disabled = false;
@@ -627,7 +653,7 @@ const musicView = {
icon: 'fa-pen',
confirmText: t('actions.confirm', 'Save')
});
- if (!newName || !newName.trim() || newName.trim() === this.currentPlaylist.name) return;
+ if (!newName?.trim() || newName.trim() === this.currentPlaylist.name) return;
try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}`, {
@@ -640,7 +666,7 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to update playlist');
this.currentPlaylist.name = newName.trim();
- const idx = this.playlists.findIndex(p => p.id === this.currentPlaylist.id);
+ const idx = this.playlists.findIndex((p) => p.id === this.currentPlaylist.id);
if (idx !== -1) this.playlists[idx].name = newName.trim();
const nameEl = document.getElementById('music-playlist-name');
@@ -649,7 +675,12 @@ const musicView = {
} catch (err) {
console.error('Edit playlist error:', err);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
}
},
@@ -668,7 +699,7 @@ const musicView = {
icon: 'fa-share-alt',
confirmText: t('music.share', 'Share')
});
- if (!userId || !userId.trim()) return;
+ if (!userId?.trim()) return;
try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/share`, {
@@ -681,12 +712,22 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to share playlist');
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.share', 'Share'), text: t('music.added', 'Added!') });
+ window.notifications.addNotification({
+ icon: 'fa-check-circle',
+ iconClass: 'upload',
+ title: t('music.share', 'Share'),
+ text: t('music.added', 'Added!')
+ });
}
} catch (err) {
console.error('Share playlist error:', err);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
}
},
@@ -747,12 +788,17 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to add tracks');
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.add_tracks', 'Add Tracks'), text: `${fileIds.length} ${t('music.added_to_playlist', 'added to playlist')}` });
+ window.notifications.addNotification({
+ icon: 'fa-check-circle',
+ iconClass: 'upload',
+ title: t('music.add_tracks', 'Add Tracks'),
+ text: `${fileIds.length} ${t('music.added_to_playlist', 'added to playlist')}`
+ });
}
await this._loadPlaylistTracks(this.currentPlaylist.id);
// Update track count
- const playlist = this.playlists.find(p => p.id === this.currentPlaylist.id);
+ const playlist = this.playlists.find((p) => p.id === this.currentPlaylist.id);
if (playlist) {
playlist.track_count = (playlist.track_count || 0) + fileIds.length;
this.currentPlaylist.track_count = playlist.track_count;
@@ -763,7 +809,12 @@ const musicView = {
} catch (err) {
console.error('Add tracks error:', err);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: t('music.add_error', 'Could not add tracks to playlist') });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: t('music.add_error', 'Could not add tracks to playlist')
+ });
}
}
});
@@ -773,7 +824,7 @@ const musicView = {
async _removeTrackFromPlaylist(trackId, fileId) {
if (!this.currentPlaylist) return;
- const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
+ const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/tracks/${encodeURIComponent(fileId)}`, {
@@ -784,10 +835,15 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to remove track');
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.remove', 'Remove'), text: t('music.track_removed', 'Track removed') });
+ window.notifications.addNotification({
+ icon: 'fa-check-circle',
+ iconClass: 'upload',
+ title: t('music.remove', 'Remove'),
+ text: t('music.track_removed', 'Track removed')
+ });
}
await this._loadPlaylistTracks(this.currentPlaylist.id);
- const playlist = this.playlists.find(p => p.id === this.currentPlaylist.id);
+ const playlist = this.playlists.find((p) => p.id === this.currentPlaylist.id);
if (playlist) {
playlist.track_count = Math.max(0, (playlist.track_count || 1) - 1);
this.currentPlaylist.track_count = playlist.track_count;
@@ -798,14 +854,19 @@ const musicView = {
} catch (err) {
console.error('Remove track error:', err);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
}
},
async _reorderTrack(fromIdx, toIdx) {
if (!this.currentPlaylist) return;
- const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
+ const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const tracks = [...this.currentTracks];
const [moved] = tracks.splice(fromIdx, 1);
@@ -813,7 +874,7 @@ const musicView = {
this.currentTracks = tracks;
this._renderTracks();
- const itemIds = tracks.map(tr => tr.id);
+ const itemIds = tracks.map((tr) => tr.id);
try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/reorder`, {
method: 'PUT',
@@ -825,7 +886,12 @@ const musicView = {
} catch (err) {
console.error('Reorder error:', err);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
await this._loadPlaylistTracks(this.currentPlaylist.id);
}
@@ -833,7 +899,7 @@ const musicView = {
async _showManageSharesDialog() {
if (!this.currentPlaylist) return;
- const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
+ const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const existing = document.getElementById('music-shares-dialog');
if (existing) existing.remove();
@@ -864,7 +930,9 @@ const musicView = {
document.body.appendChild(dialog);
dialog.querySelector('.music-shares-close-btn').addEventListener('click', () => dialog.remove());
- dialog.addEventListener('click', (e) => { if (e.target === dialog) dialog.remove(); });
+ dialog.addEventListener('click', (e) => {
+ if (e.target === dialog) dialog.remove();
+ });
dialog.querySelector('#music-share-add-btn').addEventListener('click', async () => {
const userInput = dialog.querySelector('#music-share-user-input');
@@ -884,11 +952,21 @@ const musicView = {
writeInput.checked = false;
this._loadSharesList(dialog);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.share', 'Share'), text: t('music.added', 'Added!') });
+ window.notifications.addNotification({
+ icon: 'fa-check-circle',
+ iconClass: 'upload',
+ title: t('music.share', 'Share'),
+ text: t('music.added', 'Added!')
+ });
}
} catch (err) {
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
}
});
@@ -898,7 +976,7 @@ const musicView = {
async _loadSharesList(dialog) {
if (!this.currentPlaylist) return;
- const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
+ const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const body = dialog.querySelector('.music-shares-body');
if (!body) return;
@@ -917,15 +995,19 @@ const musicView = {
return;
}
- body.innerHTML = shares.map(s => `
+ body.innerHTML = shares
+ .map(
+ (s) => `
${this._escapeHtml(s.user_id)}
${s.can_write ? t('music.can_write', 'Can edit') : t('music.read_only', 'Read only')}
- `).join('');
+ `
+ )
+ .join('');
- body.querySelectorAll('.music-share-remove-btn').forEach(btn => {
+ body.querySelectorAll('.music-share-remove-btn').forEach((btn) => {
btn.addEventListener('click', async () => {
const item = btn.closest('.music-share-item');
const userId = item.dataset.userId;
@@ -939,7 +1021,7 @@ const musicView = {
async _removeShare(userId, dialog) {
if (!this.currentPlaylist) return;
- const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
+ const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/share/${encodeURIComponent(userId)}`, {
@@ -951,14 +1033,19 @@ const musicView = {
this._loadSharesList(dialog);
} catch (err) {
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
}
},
async _togglePublic() {
if (!this.currentPlaylist) return;
- const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
+ const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const newValue = !this.currentPlaylist.is_public;
try {
@@ -971,7 +1058,7 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to update playlist');
this.currentPlaylist.is_public = newValue;
- const idx = this.playlists.findIndex(p => p.id === this.currentPlaylist.id);
+ const idx = this.playlists.findIndex((p) => p.id === this.currentPlaylist.id);
if (idx !== -1) this.playlists[idx].is_public = newValue;
const badge = document.getElementById('music-public-badge');
@@ -985,19 +1072,29 @@ const musicView = {
if (window.notifications) {
const status = newValue ? t('music.public', 'Public') : t('music.private', 'Private');
- window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.toggle_public', 'Visibility'), text: status });
+ window.notifications.addNotification({
+ icon: 'fa-check-circle',
+ iconClass: 'upload',
+ title: t('music.toggle_public', 'Visibility'),
+ text: status
+ });
}
} catch (err) {
console.error('Toggle public error:', err);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
}
},
async _showCoverPicker() {
if (!this.currentPlaylist) return;
- const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
+ const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const input = document.createElement('input');
input.type = 'file';
@@ -1035,7 +1132,7 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to set cover');
this.currentPlaylist.cover_file_id = uploaded.id;
- const plIdx = this.playlists.findIndex(p => p.id === this.currentPlaylist.id);
+ const plIdx = this.playlists.findIndex((p) => p.id === this.currentPlaylist.id);
if (plIdx !== -1) this.playlists[plIdx].cover_file_id = uploaded.id;
const coverEl = document.getElementById('music-playlist-cover');
@@ -1044,12 +1141,22 @@ const musicView = {
}
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.set_cover', 'Set cover'), text: t('music.cover_updated', 'Cover updated') });
+ window.notifications.addNotification({
+ icon: 'fa-check-circle',
+ iconClass: 'upload',
+ title: t('music.set_cover', 'Set cover'),
+ text: t('music.cover_updated', 'Cover updated')
+ });
}
} catch (err) {
console.error('Cover upload error:', err);
if (window.notifications) {
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: err.message
+ });
}
}
});
@@ -1077,10 +1184,10 @@ const musicPlayer = {
init() {
if (this._initialized) return;
-
+
this.audio = new Audio();
this.audio.volume = this.volume;
-
+
this.audio.addEventListener('ended', () => this._onEnded());
this.audio.addEventListener('timeupdate', () => this._onTimeUpdate());
this.audio.addEventListener('loadedmetadata', () => this._onLoadedMetadata());
@@ -1254,7 +1361,7 @@ const musicPlayer = {
playTrack(index) {
if (index < 0 || index >= this.queue.length) return;
-
+
this.currentIndex = index;
this.currentTrack = this.queue[index];
this._loadAndPlay();
@@ -1265,7 +1372,7 @@ const musicPlayer = {
const fileId = this.currentTrack.file_id;
this.audio.src = `/api/files/${fileId}`;
- this.audio.play().catch(err => {
+ this.audio.play().catch((err) => {
console.error('Playback error:', err);
});
this.isPlaying = true;
@@ -1301,7 +1408,7 @@ const musicPlayer = {
next() {
if (this.queue.length === 0) return;
-
+
let nextIndex;
if (this.shuffle) {
nextIndex = Math.floor(Math.random() * this.queue.length);
@@ -1341,7 +1448,7 @@ const musicPlayer = {
const modes = ['none', 'all', 'one'];
const currentIdx = modes.indexOf(this.repeat);
this.repeat = modes[(currentIdx + 1) % modes.length];
-
+
const btn = document.getElementById('player-repeat-btn');
if (btn) {
btn.classList.remove('active', 'repeat-one');
@@ -1358,7 +1465,7 @@ const musicPlayer = {
this.audio.volume = this.volume;
this.isMuted = this.volume === 0;
this._updateVolumeIcon();
-
+
const input = document.getElementById('player-volume-input');
if (input) {
input.value = this.volume * 100;
@@ -1431,15 +1538,15 @@ const musicPlayer = {
if (totalTimeEl) {
totalTimeEl.textContent = this._formatTime(this.audio.duration);
}
-
+
if (this.currentTrack && this.audio.duration) {
this.currentTrack.duration_secs = Math.round(this.audio.duration);
-
+
const trackDurationEl = document.querySelector(`.music-track[data-idx="${this.currentIndex}"] .music-track-duration`);
if (trackDurationEl) {
trackDurationEl.textContent = this._formatDuration(this.audio.duration);
}
-
+
const queueDurationEl = document.querySelector(`.queue-item[data-idx="${this.currentIndex}"] .queue-item-duration`);
if (queueDurationEl) {
queueDurationEl.textContent = this._formatDuration(this.audio.duration);
@@ -1470,7 +1577,12 @@ const musicPlayer = {
};
if (window.notifications) {
const trackName = this.currentTrack?.title || this.currentTrack?.file_name || t('music.unknown_title', 'Unknown');
- window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: `${t('music.playback_error', 'Playback failed')}: ${trackName}` });
+ window.notifications.addNotification({
+ icon: 'fa-exclamation-circle',
+ iconClass: 'error',
+ title: t('music.error', 'Error'),
+ text: `${t('music.playback_error', 'Playback failed')}: ${trackName}`
+ });
}
},
@@ -1496,8 +1608,8 @@ const musicPlayer = {
const t = (key, fallback = '') => {
return typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
};
- trackName.textContent = this.currentTrack
- ? (this.currentTrack.title || this.currentTrack.file_name || t('music.unknown_title', 'Unknown'))
+ trackName.textContent = this.currentTrack
+ ? this.currentTrack.title || this.currentTrack.file_name || t('music.unknown_title', 'Unknown')
: t('music.not_playing', 'Not playing');
}
@@ -1506,13 +1618,13 @@ const musicPlayer = {
}
if (musicView.currentTracks.length > 0) {
- document.querySelectorAll('.music-track').forEach(row => {
- const idx = parseInt(row.dataset.idx);
+ document.querySelectorAll('.music-track').forEach((row) => {
+ const idx = parseInt(row.dataset.idx, 10);
row.classList.toggle('playing', idx === this.currentIndex && this.isPlaying);
-
+
const numText = row.querySelector('.track-num-text');
const playIcon = row.querySelector('.track-play-icon');
-
+
if (idx === this.currentIndex) {
if (numText) numText.classList.add('hidden');
if (playIcon) playIcon.classList.remove('hidden');
@@ -1562,7 +1674,9 @@ const musicPlayer = {
return;
}
- queueList.innerHTML = this.queue.map((track, idx) => `
+ queueList.innerHTML = this.queue
+ .map(
+ (track, idx) => `
${idx + 1}
@@ -1574,20 +1688,22 @@ const musicPlayer = {
- `).join('');
+ `
+ )
+ .join('');
- queueList.querySelectorAll('.player-queue-item').forEach(item => {
+ queueList.querySelectorAll('.player-queue-item').forEach((item) => {
item.addEventListener('click', (e) => {
if (e.target.closest('.queue-item-remove')) return;
- const idx = parseInt(item.dataset.idx);
+ const idx = parseInt(item.dataset.idx, 10);
this.playTrack(idx);
});
});
- queueList.querySelectorAll('.queue-item-remove').forEach(btn => {
+ queueList.querySelectorAll('.queue-item-remove').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
- const idx = parseInt(btn.dataset.idx);
+ const idx = parseInt(btn.dataset.idx, 10);
this._removeFromQueue(idx);
});
});
@@ -1632,7 +1748,7 @@ const musicPlayer = {
},
_formatTime(secs) {
- if (!secs || isNaN(secs)) return '0:00';
+ if (!secs || Number.isNaN(secs)) return '0:00';
const mins = Math.floor(secs / 60);
const s = Math.floor(secs % 60);
return `${mins}:${s.toString().padStart(2, '0')}`;