feat(playlists): make YouTube-mirrored playlists editable + syncable

Mirrors (source='youtube') were read-only; per the original two-way design they can
now be edited locally (add/remove/reorder/rename) and pushed back. The read-sync
skips a dirty mirror so it won't clobber unpushed edits; a successful push clears
dirty and lets the mirror refresh. _mark_dirty now covers any YouTube-linked list
(exported local or mirror), and rename marks dirty too. push/delete no longer reject
mirrors. Push reconciles the title as well (cheap playlists.list compare, then
playlists.update only if changed) so a local rename doesn't revert on the next pull.
Frontend: editable = not-built-in (was also excluding mirrors); rows/reorder enabled
for all owned lists; AddToPlaylist popover lists mirrors too (with a YT marker); the
origin chip now reads 'edits sync back'. Trilingual.
This commit is contained in:
npeter83 2026-06-15 21:40:13 +02:00
parent 49d226126c
commit 9acea11010
8 changed files with 58 additions and 26 deletions

View file

@ -161,9 +161,10 @@ export default function Playlists({ canWrite }: { canWrite: boolean }) {
const plName = (p: { kind: string; name: string }) =>
p.kind === "watch_later" ? t("playlists.watchLater") : p.name;
const builtin = detail?.kind === "watch_later";
// YouTube-mirrored playlists are read-only here until the write-back phase.
// YouTube-mirrored playlists can be edited locally and synced back (the read-sync skips
// a dirty mirror so it won't clobber unpushed edits); the chip just marks their origin.
const mirrored = detail?.source === "youtube";
const editable = !builtin && !mirrored;
const editable = !builtin;
const linked = editable && !!detail?.yt_playlist_id;
const [syncing, setSyncing] = useState(false);
const [pushing, setPushing] = useState(false);
@ -253,7 +254,7 @@ export default function Playlists({ canWrite }: { canWrite: boolean }) {
async function onDragEnd(e: DragEndEvent) {
const { active: a, over } = e;
if (!over || a.id === over.id || selectedId == null || mirrored) return;
if (!over || a.id === over.id || selectedId == null) return;
const oldIndex = items.findIndex((v) => v.id === a.id);
const newIndex = items.findIndex((v) => v.id === over.id);
if (oldIndex < 0 || newIndex < 0) return;
@ -513,7 +514,6 @@ export default function Playlists({ canWrite }: { canWrite: boolean }) {
key={v.id}
video={v}
index={i}
readOnly={mirrored}
onPlay={() => setPlayingIndex(i)}
onRemove={() => removeItem(v.id)}
/>