-
{plName(pl)}
+
+ {plName(pl)}
+ {pl.source === "youtube" && (
+
+ )}
+
{t("playlists.itemCount", { count: pl.item_count })}
@@ -301,7 +348,7 @@ export default function Playlists() {
) : (
{plName(detail)}
- {!builtin && (
+ {editable && (
- {!builtin && (
+ {editable && (
{t("playlists.delete")}
)}
+ {mirrored && (
+
+ {t("playlists.ytReadonly")}
+
+ )}
@@ -361,6 +413,7 @@ export default function Playlists() {
key={v.id}
video={v}
index={i}
+ readOnly={mirrored}
onPlay={() => setPlayingIndex(i)}
onRemove={() => removeItem(v.id)}
/>
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index e846ba9..0bb9f7d 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -202,7 +202,7 @@ export default function Sidebar({
async function shareView() {
try {
await navigator.clipboard.writeText(shareUrl(filters));
- notify({ message: t("sidebar.shareCopied") });
+ notify({ level: "success", message: t("sidebar.shareCopied") });
} catch {
notify({ level: "warning", message: t("sidebar.shareFailed") });
}
diff --git a/frontend/src/i18n/locales/de/playlists.json b/frontend/src/i18n/locales/de/playlists.json
index 6d4851f..4b8728d 100644
--- a/frontend/src/i18n/locales/de/playlists.json
+++ b/frontend/src/i18n/locales/de/playlists.json
@@ -1,6 +1,10 @@
{
"title": "Wiedergabelisten",
"watchLater": "Später ansehen",
+ "syncYoutube": "Von YouTube synchronisieren",
+ "syncedToast": "{{count}} Wiedergabelisten von YouTube synchronisiert",
+ "syncFailed": "Synchronisierung von YouTube fehlgeschlagen",
+ "ytReadonly": "Von YouTube synchronisiert — vorerst schreibgeschützt",
"noneYet": "Noch keine Wiedergabelisten",
"newPlaylist": "Neue Liste…",
"itemCount_one": "{{count}} Video",
diff --git a/frontend/src/i18n/locales/en/playlists.json b/frontend/src/i18n/locales/en/playlists.json
index 9bef503..85ed5d9 100644
--- a/frontend/src/i18n/locales/en/playlists.json
+++ b/frontend/src/i18n/locales/en/playlists.json
@@ -1,6 +1,10 @@
{
"title": "Playlists",
"watchLater": "Watch later",
+ "syncYoutube": "Sync from YouTube",
+ "syncedToast": "Synced {{count}} playlists from YouTube",
+ "syncFailed": "Couldn't sync from YouTube",
+ "ytReadonly": "Synced from YouTube — read-only for now",
"noneYet": "No playlists yet",
"newPlaylist": "New playlist…",
"itemCount_one": "{{count}} video",
diff --git a/frontend/src/i18n/locales/hu/playlists.json b/frontend/src/i18n/locales/hu/playlists.json
index dcfcedd..0f30811 100644
--- a/frontend/src/i18n/locales/hu/playlists.json
+++ b/frontend/src/i18n/locales/hu/playlists.json
@@ -1,6 +1,10 @@
{
"title": "Lejátszási listák",
"watchLater": "Megnézem később",
+ "syncYoutube": "Szinkron YouTube-ról",
+ "syncedToast": "{{count}} lista szinkronizálva a YouTube-ról",
+ "syncFailed": "Nem sikerült a YouTube-szinkron",
+ "ytReadonly": "YouTube-ról szinkronizálva — egyelőre csak olvasható",
"noneYet": "Még nincs lejátszási lista",
"newPlaylist": "Új lista…",
"itemCount_one": "{{count}} videó",
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index f41535c..3472d9b 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -344,6 +344,8 @@ export const api = {
}),
watchLaterRemove: (videoId: string) =>
req(`/api/playlists/watch-later/${videoId}`, { method: "DELETE" }),
+ syncYoutubePlaylists: (): Promise<{ synced: number; reason?: string }> =>
+ req("/api/playlists/sync-youtube", { method: "POST" }),
// --- quota usage ---
myUsage: (): Promise