From 493edfcf3a2e66e9630f9087ad1dcab05181fad7 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Sun, 18 Oct 2026 10:48:52 +0200 Subject: [PATCH] feat: show YouTube videos as two-line entries --- README.md | 2 +- src/main.rs | 58 +++++++++++++++++++++++++++-------------------- tests/unit/app.rs | 17 ++++++++++---- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 3284c16..c883634 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Startup shows a centered **YouTube / Miruro** selector. Use `j` / `k` or arrows After selection, the browser starts in **normal mode**. Press `i` to type a search; Enter submits a changed query and returns to normal mode. Esc returns without submitting. -Both providers use the same layout: a cyan view heading, bordered search and results, a bordered status line, and a dim one-line key hint bar. The selected row uses black text on cyan with a `> ` marker; saved titles are green, watch-later titles magenta, and `✓` marks watched entries. Metadata and help open in centered cyan-bordered popups; bulk-removal confirmations use yellow borders. Miruro keeps its catalog navigation and two-line title/episode rows rather than YouTube's video table. +Both providers use the same layout: a cyan view heading, bordered search and results, a bordered status line, and a dim one-line key hint bar. Entries use two lines: the title above dimmed metadata. YouTube shows the channel and publication date beneath each video title, without separate channel/date columns; tagged channels remain cyan. The selected row uses black text on cyan with a `> ` marker; saved titles are green, watch-later titles magenta, and `✓` marks watched entries. Metadata and help open in centered cyan-bordered popups; bulk-removal confirmations use yellow borders. Miruro keeps its catalog navigation and title/episode metadata. Selecting a YouTube video launches: diff --git a/src/main.rs b/src/main.rs index 23b429f..ca9ea14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,7 +101,7 @@ //! Switching views cancels in-flight extraction; returning retains the unaccepted request and per-channel offsets. //! Errors keep loaded rows and stop automatic retries; r reloads config and restarts the current view. //! Deep pages may slow down because yt-dlp offsets re-traverse earlier continuation pages. -//! Video tables show Published as YYYY-MM-DD; ~ marks estimated UTC dates, Unknown missing metadata. +//! Video entries show the title above channel · YYYY-MM-DD; ~ marks estimated UTC dates, Unknown missing metadata. //! Prefer exact upload dates and cache date labels during extraction, not on every redraw. //! Terebi receives `--new-instance --url` and a private `--progress-file`; each selection gets its own player. //! `--player` overrides its CLI path; otherwise `terebi` is resolved through PATH. @@ -2986,39 +2986,47 @@ impl App { .categories .values() .any(|channels| video_in_channels(video, channels, &self.config)); - let channel = Cell::from(video.channel.as_deref().unwrap_or("Unknown")); + let title = Span::raw(video.title.as_str()); + let channel = Span::raw(video.channel.as_deref().unwrap_or("Unknown")); Row::new([ Cell::from(if self.video_state.is_watched(&video.id) { "✓" } else { "" }), - if self.video_state.contains(CollectionKind::Saved, &video.id) { - Cell::from(video.title.as_str()).fg(Color::Green) - } else if self - .video_state - .contains(CollectionKind::WatchLater, &video.id) - { - Cell::from(video.title.as_str()).fg(Color::Magenta) - } else { - Cell::from(video.title.as_str()) - }, - if tagged { - channel.fg(Color::Cyan) - } else { - channel - }, - Cell::from(video.publication_date.as_deref().unwrap_or("Unknown")), + Cell::from(Text::from(vec![ + Line::from( + if self.video_state.contains(CollectionKind::Saved, &video.id) { + title.fg(Color::Green) + } else if self + .video_state + .contains(CollectionKind::WatchLater, &video.id) + { + title.fg(Color::Magenta) + } else { + title + }, + ), + Line::from(vec![ + if tagged { + channel.fg(Color::Cyan) + } else { + channel + }, + " · ".into(), + video + .publication_date + .as_deref() + .unwrap_or("Unknown") + .into(), + ]) + .dim(), + ])), ]) + .height(2) }), - [ - Constraint::Length(1), - Constraint::Fill(3), - Constraint::Fill(1), - Constraint::Length(11), - ], + [Constraint::Length(1), Constraint::Fill(1)], ) - .header(Row::new(["", "Video", "Channel", "Published"]).bold()) .block(Block::bordered().title(loading_title( if view.source == ViewSource::Search { " Results " diff --git a/tests/unit/app.rs b/tests/unit/app.rs index 50ee597..987755f 100644 --- a/tests/unit/app.rs +++ b/tests/unit/app.rs @@ -3010,9 +3010,9 @@ fn video_channel_colors_follow_category_membership_and_preserve_selection_contra .draw(|frame| app.draw(frame, Duration::ZERO)) .unwrap(); let buffer = terminal.backend().buffer(); - // One tab row, three search rows, then the table border and header. + // One tab row, three search rows, then the results border and two-line entries. for (index, (label, _, _, tagged)) in cases.iter().enumerate() { - let y = 6 + index as u16; + let y = 6 + 2 * index as u16; let row: String = (0..120).map(|x| buffer[(x, y)].symbol()).collect(); let x = row.find(label).expect("channel label is visible") as u16; assert_eq!( @@ -3024,8 +3024,17 @@ fn video_channel_colors_follow_category_membership_and_preserve_selection_contra }, "{label} in row {index}, cleared={cleared}" ); - assert_eq!(buffer[(3, y)].fg, Color::Reset, "title stays uncolored"); - assert_eq!(buffer[(108, y)].fg, Color::Reset, "date stays uncolored"); + let title: String = (0..120).map(|x| buffer[(x, y - 1)].symbol()).collect(); + assert!(title.contains("Video title")); + let date_x = row.find("2026-10-11").expect("date follows channel") as u16; + assert!(row.contains(&format!("{label} · 2026-10-11"))); + assert_eq!(buffer[(3, y - 1)].fg, Color::Reset, "title stays uncolored"); + assert_eq!(buffer[(date_x, y)].fg, Color::Reset, "date stays uncolored"); + assert!( + buffer[(x, y)] + .modifier + .contains(ratatui::style::Modifier::DIM) + ); } if !cleared { app.views[0].selection.select(Some(0));