fix: button focus ring, playlist reorder mode, edit modal mobile, backdrop-filter

- button:focus{outline:none} — removes the blue tap-highlight box in WebView
- Playlist reorder: replaced always-on drag handles with explicit 'Neu anordnen'
  mode. Handles only appear after tapping the button; a sticky Speichern/Abbrechen
  bar appears above the list. Touch drag is now fully supported. Context-menu
  (long-press) is blocked while reorder mode is active to prevent conflicts.
- styles.css: .pl-drag-handle always visible when pl-draggable (no hover needed)
- Edit modal footer: wrap buttons on small screens so Save doesn't overflow
- Edit modal overlay: remove backdrop-filter:blur — causes WebView compositor
  glitch where 3/4 of the modal disappears after returning from background

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-18 22:40:11 +02:00
1 parent 68bec67880
commit 9d8a5a22bb
4 files changed
+112 -36

No files matched your search

+4 -1
View File
@@ -16,6 +16,7 @@ import {
onGlobalSearch, s_goArtist, s_goAlbum, playerBarGoArtist, playerBarGoAlbum,
searchSimilarArtist, searchAndPreview, loadItunesAlbum, expandArtistBio,
loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist,
plReorderEnter, plReorderSave, plReorderCancel,
} from './views.js';
import { loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll } from './spotify.js';
import {
@@ -282,7 +283,9 @@ Object.assign(window, {
openSettings, closeSettings, saveSettings, confirmDisconnect, checkBackend,
openSidebar, closeSidebar, switchNavMode, showCtxMenu,
// Views
loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist, loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist,
plReorderEnter, plReorderSave, plReorderCancel,
loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
executeSpotifyDownload,
openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist, createPlaylistPrompt,
showSongInfo, closeInfoModal,
+89 -32
View File
@@ -557,11 +557,10 @@ export async function loadPlaylistDetail(pl) {
playQueue(shuffled, 0);
};
sl.innerHTML = '';
sl.classList.add('pl-draggable');
const plSearch = document.getElementById('playlist-search');
if (plSearch) plSearch.value = '';
songs.forEach((s,i) => sl.appendChild(makeSongItem(s, i, songs, { noArtistLink: true, noAlbumLink: true })));
_initPlaylistDragDrop(sl, songs, S._currentPlaylist);
_setupReorderMode(sl, songs, S._currentPlaylist);
} catch(e) { sl.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
}
@@ -574,9 +573,28 @@ export function filterPlaylist(val) {
});
}
function _initPlaylistDragDrop(container, songs, pl) {
let dragSrc = null;
let saveTimer = null;
// ─── PLAYLIST REORDER MODE ───────────────────────────────────────────────
let _reorderCtx = null;
function _setupReorderMode(container, songs, pl) {
// Exit any previous reorder mode silently when loading a new playlist.
if (_reorderCtx) _exitReorderMode(false);
_reorderCtx = { container, songs, pl };
const btn = document.getElementById('btn-reorder-playlist');
if (btn) btn.onclick = plReorderEnter;
const bar = document.getElementById('pl-reorder-bar');
if (bar) bar.style.display = 'none';
}
export function plReorderEnter() {
if (!_reorderCtx) return;
const { container, songs } = _reorderCtx;
container.classList.add('pl-draggable');
// Block context-menu (long-press) on all items while reordering.
container.addEventListener('contextmenu', _blockCtxMenu, true);
let dragSrc = null;
let touchDragSrc = null;
Array.from(container.children).forEach((item, i) => {
const handle = document.createElement('div');
@@ -585,54 +603,93 @@ function _initPlaylistDragDrop(container, songs, pl) {
item.insertBefore(handle, item.firstChild);
item._songRef = songs[i];
// Track whether drag started from the handle (e.target in dragstart is always the item, not the handle)
// ── Desktop drag ──
let fromHandle = false;
handle.addEventListener('mousedown', () => { fromHandle = true; item.draggable = true; });
item.addEventListener('dragstart', e => {
if (!fromHandle) { e.preventDefault(); item.draggable = false; return; }
fromHandle = false;
dragSrc = item;
fromHandle = false; dragSrc = item;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', ''); // required by Firefox
e.dataTransfer.setData('text/plain', '');
setTimeout(() => item.classList.add('dragging'), 0);
});
item.addEventListener('dragend', () => {
fromHandle = false;
item.draggable = false;
dragSrc = null;
fromHandle = false; item.draggable = false; dragSrc = null;
item.classList.remove('dragging');
container.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
});
item.addEventListener('dragover', e => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
e.preventDefault(); e.dataTransfer.dropEffect = 'move';
if (item === dragSrc) return;
container.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
item.classList.add('drag-over');
});
item.addEventListener('dragleave', e => {
if (!item.contains(e.relatedTarget)) item.classList.remove('drag-over');
});
item.addEventListener('dragleave', e => { if (!item.contains(e.relatedTarget)) item.classList.remove('drag-over'); });
item.addEventListener('drop', e => {
e.preventDefault();
e.stopPropagation();
e.preventDefault(); e.stopPropagation();
if (!dragSrc || item === dragSrc) return;
item.classList.remove('drag-over');
const allItems = Array.from(container.children);
const fromIdx = allItems.indexOf(dragSrc);
const toIdx = allItems.indexOf(item);
container.insertBefore(dragSrc, fromIdx < toIdx ? item.nextSibling : item);
const newOrder = Array.from(container.children).map(el => el._songRef);
songs.splice(0, songs.length, ...newOrder);
Array.from(container.children).forEach((el, idx) => {
const n = el.querySelector('.song-num');
if (n) n.textContent = idx + 1;
});
clearTimeout(saveTimer);
saveTimer = setTimeout(() => _savePlaylistOrder(pl, songs), 800);
_doReorder(container, songs, dragSrc, item);
});
// ── Touch drag ──
handle.addEventListener('touchstart', e => {
e.preventDefault(); e.stopPropagation();
touchDragSrc = item; item.classList.add('dragging');
}, { passive: false });
handle.addEventListener('touchmove', e => {
if (!touchDragSrc) return;
e.preventDefault();
const t = e.touches[0];
container.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
const over = _itemAtPoint(container, t.clientX, t.clientY, touchDragSrc);
if (over) over.classList.add('drag-over');
}, { passive: false });
handle.addEventListener('touchend', e => {
if (!touchDragSrc) return;
const t = e.changedTouches[0];
touchDragSrc.classList.remove('dragging');
container.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
const over = _itemAtPoint(container, t.clientX, t.clientY, touchDragSrc);
if (over) _doReorder(container, songs, touchDragSrc, over);
touchDragSrc = null;
}, { passive: false });
});
const bar = document.getElementById('pl-reorder-bar');
if (bar) bar.style.display = 'flex';
}
function _blockCtxMenu(e) { e.preventDefault(); e.stopPropagation(); }
function _itemAtPoint(container, x, y, exclude) {
return (document.elementsFromPoint(x, y) || [])
.find(el => el.classList?.contains('song-item') && el !== exclude && container.contains(el)) || null;
}
function _doReorder(container, songs, src, target) {
const all = Array.from(container.children);
const from = all.indexOf(src), to = all.indexOf(target);
container.insertBefore(src, from < to ? target.nextSibling : target);
const newOrder = Array.from(container.children).map(el => el._songRef);
songs.splice(0, songs.length, ...newOrder);
Array.from(container.children).forEach((el, i) => {
const n = el.querySelector('.song-num'); if (n) n.textContent = i + 1;
});
}
export function plReorderSave() { _exitReorderMode(true); }
export function plReorderCancel() { _exitReorderMode(false); }
function _exitReorderMode(save) {
if (!_reorderCtx) return;
const { container, songs, pl } = _reorderCtx;
container.classList.remove('pl-draggable');
container.removeEventListener('contextmenu', _blockCtxMenu, true);
container.querySelectorAll('.pl-drag-handle').forEach(h => h.remove());
const bar = document.getElementById('pl-reorder-bar');
if (bar) bar.style.display = 'none';
if (save) _savePlaylistOrder(pl, songs);
}
async function _savePlaylistOrder(pl, songs) {