Yoda 0.12.5

Browser for Gemini Protocol
//! Browser bookmarks dialog

use super::Profile;
use crate::app::browser::window::action::{Action as WindowAction, Position};
use adw::{
    ActionRow, PreferencesGroup, PreferencesPage,
    prelude::{
        ActionRowExt, AdwDialogExt, ExpanderRowExt, PreferencesDialogExt, PreferencesGroupExt,
        PreferencesPageExt,
    },
};
use gtk::{
    glib::{DateTime, GString, Uri, UriFlags},
    prelude::ButtonExt,
};
use indexmap::IndexMap;
use std::rc::Rc;

struct Record {
    time: DateTime,
    request: String,
    title: Option<String>,
}

pub trait Bookmarks {
    fn bookmarks(window_action: &Rc<WindowAction>, profile: &Rc<Profile>) -> Self;
}

impl Bookmarks for adw::PreferencesDialog {
    fn bookmarks(window_action: &Rc<WindowAction>, profile: &Rc<Profile>) -> Self {
        let mut index: IndexMap<GString, Vec<Record>> = IndexMap::new();
        for bookmark in profile.bookmark.recent(None) {
            match Uri::parse(&bookmark.request, UriFlags::NONE) {
                Ok(uri) => index
                    .entry(match uri.host() {
                        Some(host) => host,
                        None => uri.to_str(),
                    })
                    .or_default()
                    .push(Record {
                        request: bookmark.request,
                        time: bookmark.time,
                        title: bookmark.title,
                    }),
                Err(_) => continue, // @TODO
            }
        }

        let d = adw::PreferencesDialog::builder()
            .search_enabled(true)
            .title("Bookmarks")
            .build();

        d.add(&{
            let p = PreferencesPage::builder()
                .icon_name("document-open-recent-symbolic")
                //.title("All")
                .build();

            for (group, records) in index {
                p.add(&{
                    let g = PreferencesGroup::new();
                    g.add(&{
                        let e = adw::ExpanderRow::builder()
                            .enable_expansion(true)
                            .expanded(false)
                            .subtitle(
                                records
                                    .iter()
                                    .max_by_key(|r| r.time.to_unix())
                                    .unwrap()
                                    .time
                                    .format_iso8601()
                                    .unwrap(),
                            )
                            .title(escape(&group))
                            .build();

                        for record in records {
                            e.add_row(&{
                                let a = ActionRow::builder()
                                    .activatable(false)
                                    .title_selectable(true)
                                    .title(match record.title {
                                        Some(title) => title,
                                        None => record.time.format_iso8601().unwrap().to_string(),
                                    })
                                    .subtitle_selectable(true)
                                    .subtitle(escape(&record.request))
                                    .build();

                                a.add_suffix(&{
                                    let b = gtk::Button::builder()
                                        .css_classes(["accent", "circular", "flat"])
                                        .icon_name("mail-forward-symbolic")
                                        .tooltip_text("Open in the new tab")
                                        .valign(gtk::Align::Center)
                                        .build();
                                    b.connect_clicked({
                                        let a = window_action.clone();
                                        let d = d.clone();
                                        move |_| {
                                            a.append.activate_stateful_once(
                                                Position::After,
                                                Some(record.request.clone()),
                                                false,
                                                true,
                                                true,
                                                true,
                                            );
                                            d.close();
                                        }
                                    });
                                    b
                                });
                                a
                            })
                        }
                        e
                    });
                    g
                });
            }
            p
        });
        d
    }
}

/// Prevents GTK warnings (`use_markup` has no effect @TODO)
fn escape(value: &str) -> String {
    value.replace("&amp;", "&").replace("&", "&amp;")
}