Add domain history clear confirmation
This commit is contained in:
@@ -1,13 +1,36 @@
|
|||||||
|
use ely_domain::{ProfileId, SpaceId};
|
||||||
use gpui::Context;
|
use gpui::Context;
|
||||||
|
|
||||||
use super::{ElyShell, ShellState};
|
use super::{ElyShell, ShellState};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(super) struct PendingHistoryDomainClear {
|
||||||
|
profile_id: ProfileId,
|
||||||
|
space_id: SpaceId,
|
||||||
|
host: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PendingHistoryDomainClear {
|
||||||
|
fn new(profile_id: ProfileId, space_id: SpaceId, host: String) -> Self {
|
||||||
|
Self { profile_id, space_id, host }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn host(&self) -> &str {
|
||||||
|
&self.host
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn matches_context(&self, profile_id: &ProfileId, space_id: &SpaceId) -> bool {
|
||||||
|
&self.profile_id == profile_id && &self.space_id == space_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ElyShell {
|
impl ElyShell {
|
||||||
pub(super) fn request_clear_history_confirmation(&mut self, cx: &mut Context<Self>) {
|
pub(super) fn request_clear_history_confirmation(&mut self, cx: &mut Context<Self>) {
|
||||||
if let ShellState::Ready(core) = &mut self.state
|
if let ShellState::Ready(core) = &mut self.state
|
||||||
&& let Ok(snapshot) = core.snapshot()
|
&& let Ok(snapshot) = core.snapshot()
|
||||||
{
|
{
|
||||||
self.history_clear_confirmation = Some(snapshot.active_profile_id);
|
self.history_clear_confirmation = Some(snapshot.active_profile_id);
|
||||||
|
self.pending_history_domain_clear = None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +47,44 @@ impl ElyShell {
|
|||||||
&& core.clear_active_profile_history().is_ok()
|
&& core.clear_active_profile_history().is_ok()
|
||||||
{
|
{
|
||||||
self.history_clear_confirmation = None;
|
self.history_clear_confirmation = None;
|
||||||
|
self.pending_history_domain_clear = None;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn request_clear_history_domain_confirmation(
|
||||||
|
&mut self,
|
||||||
|
host: String,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if let ShellState::Ready(core) = &mut self.state
|
||||||
|
&& let Ok(snapshot) = core.snapshot()
|
||||||
|
{
|
||||||
|
self.pending_history_domain_clear = Some(PendingHistoryDomainClear::new(
|
||||||
|
snapshot.active_profile_id,
|
||||||
|
snapshot.active_space_id,
|
||||||
|
host,
|
||||||
|
));
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn cancel_clear_history_domain_confirmation(&mut self, cx: &mut Context<Self>) {
|
||||||
|
self.pending_history_domain_clear = None;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn clear_active_space_history_for_pending_domain(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let Some(pending) = self.pending_history_domain_clear.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let ShellState::Ready(core) = &mut self.state
|
||||||
|
&& let Ok(snapshot) = core.snapshot()
|
||||||
|
&& pending.matches_context(&snapshot.active_profile_id, &snapshot.active_space_id)
|
||||||
|
&& core.clear_active_space_history_for_host(pending.host()).is_ok()
|
||||||
|
{
|
||||||
|
self.pending_history_domain_clear = None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod download_labels;
|
|||||||
mod download_settings;
|
mod download_settings;
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod general;
|
mod general;
|
||||||
|
mod history;
|
||||||
mod plugin_catalog;
|
mod plugin_catalog;
|
||||||
mod plugin_details;
|
mod plugin_details;
|
||||||
mod plugins;
|
mod plugins;
|
||||||
@@ -23,7 +24,7 @@ mod task_manager;
|
|||||||
|
|
||||||
use ely_browser_core::BrowserSnapshot;
|
use ely_browser_core::BrowserSnapshot;
|
||||||
use ely_design_system::{colors, spacing};
|
use ely_design_system::{colors, spacing};
|
||||||
use ely_domain::{ArchiveSource, ArchivedTab, BrowserTab, HistoryEntry};
|
use ely_domain::{ArchiveSource, ArchivedTab, BrowserTab};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString,
|
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString,
|
||||||
StatefulInteractiveElement, Styled, div, px, rgb,
|
StatefulInteractiveElement, Styled, div, px, rgb,
|
||||||
@@ -73,136 +74,6 @@ impl ElyShell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_history_page(
|
|
||||||
&mut self,
|
|
||||||
snapshot: &BrowserSnapshot,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> AnyElement {
|
|
||||||
render_canvas_surface(
|
|
||||||
div()
|
|
||||||
.size_full()
|
|
||||||
.p_8()
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_5()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_end()
|
|
||||||
.justify_between()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_size(px(26.0))
|
|
||||||
.text_color(rgb(colors::INK))
|
|
||||||
.child("History"),
|
|
||||||
)
|
|
||||||
.child(div().text_sm().text_color(rgb(colors::MUTED)).child(
|
|
||||||
format!(
|
|
||||||
"{} / {}",
|
|
||||||
snapshot.active_profile_name, snapshot.active_space_name
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_xs()
|
|
||||||
.text_color(rgb(colors::MUTED))
|
|
||||||
.child(format!("{} entries", snapshot.history_entries.len())),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(self.render_history_list(snapshot, cx)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_history_list(
|
|
||||||
&mut self,
|
|
||||||
snapshot: &BrowserSnapshot,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> AnyElement {
|
|
||||||
if snapshot.history_entries.is_empty() {
|
|
||||||
return div()
|
|
||||||
.flex_1()
|
|
||||||
.border_t_1()
|
|
||||||
.border_color(rgb(colors::HAIRLINE))
|
|
||||||
.pt_5()
|
|
||||||
.text_sm()
|
|
||||||
.text_color(rgb(colors::MUTED))
|
|
||||||
.child("History is empty for this Space and Profile.")
|
|
||||||
.into_any_element();
|
|
||||||
}
|
|
||||||
|
|
||||||
div()
|
|
||||||
.flex_1()
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.overflow_y_scrollbar()
|
|
||||||
.border_t_1()
|
|
||||||
.border_color(rgb(colors::HAIRLINE))
|
|
||||||
.children(
|
|
||||||
snapshot
|
|
||||||
.history_entries
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, entry)| self.render_history_row(index, entry, cx)),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_history_row(
|
|
||||||
&mut self,
|
|
||||||
index: usize,
|
|
||||||
entry: &HistoryEntry,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> AnyElement {
|
|
||||||
let url = entry.url().clone();
|
|
||||||
|
|
||||||
div()
|
|
||||||
.id(SharedString::from(format!("history-{index}")))
|
|
||||||
.py_3()
|
|
||||||
.border_b_1()
|
|
||||||
.border_color(rgb(colors::HAIRLINE))
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.gap_4()
|
|
||||||
.cursor_pointer()
|
|
||||||
.hover(|style| style.bg(rgb(colors::CANVAS_SOFT)))
|
|
||||||
.active(|style| style.opacity(0.82))
|
|
||||||
.on_click(cx.listener(move |shell, _, window, cx| {
|
|
||||||
shell.open_url(url.clone(), window, cx);
|
|
||||||
}))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.min_w_0()
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_sm()
|
|
||||||
.font_semibold()
|
|
||||||
.truncate()
|
|
||||||
.text_color(rgb(colors::INK))
|
|
||||||
.child(entry.title().to_string()),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_xs()
|
|
||||||
.truncate()
|
|
||||||
.text_color(rgb(colors::MUTED))
|
|
||||||
.child(entry.url().display_url()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(div().text_color(rgb(colors::MUTED_SOFT)).child(IconName::ExternalLink))
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_archive_page(
|
fn render_archive_page(
|
||||||
&mut self,
|
&mut self,
|
||||||
snapshot: &BrowserSnapshot,
|
snapshot: &BrowserSnapshot,
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
use ely_browser_core::BrowserSnapshot;
|
||||||
|
use ely_design_system::colors;
|
||||||
|
use ely_domain::HistoryEntry;
|
||||||
|
use gpui::prelude::FluentBuilder;
|
||||||
|
use gpui::{
|
||||||
|
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString, Styled, div,
|
||||||
|
px, rgb,
|
||||||
|
};
|
||||||
|
use gpui_component::{
|
||||||
|
Sizable, StyledExt,
|
||||||
|
button::{Button, ButtonVariants},
|
||||||
|
scroll::ScrollableElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::super::PendingHistoryDomainClear;
|
||||||
|
use super::{ElyShell, render_canvas_surface};
|
||||||
|
|
||||||
|
impl ElyShell {
|
||||||
|
pub(super) fn render_history_page(
|
||||||
|
&mut self,
|
||||||
|
snapshot: &BrowserSnapshot,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> AnyElement {
|
||||||
|
let pending_domain_clear = self.pending_history_domain_clear.clone().filter(|pending| {
|
||||||
|
pending.matches_context(&snapshot.active_profile_id, &snapshot.active_space_id)
|
||||||
|
});
|
||||||
|
|
||||||
|
render_canvas_surface(
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.p_8()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_5()
|
||||||
|
.child(render_history_header(snapshot))
|
||||||
|
.when_some(pending_domain_clear, |this, pending| {
|
||||||
|
this.child(render_domain_clear_confirmation(&pending, cx))
|
||||||
|
})
|
||||||
|
.child(render_history_list(snapshot, cx)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_history_header(snapshot: &BrowserSnapshot) -> AnyElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_end()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_2()
|
||||||
|
.child(div().text_size(px(26.0)).text_color(rgb(colors::INK)).child("History"))
|
||||||
|
.child(div().text_sm().text_color(rgb(colors::MUTED)).child(format!(
|
||||||
|
"{} / {}",
|
||||||
|
snapshot.active_profile_name, snapshot.active_space_name
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(rgb(colors::MUTED))
|
||||||
|
.child(format!("{} entries", snapshot.history_entries.len())),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_domain_clear_confirmation(
|
||||||
|
pending: &PendingHistoryDomainClear,
|
||||||
|
cx: &mut Context<ElyShell>,
|
||||||
|
) -> AnyElement {
|
||||||
|
div()
|
||||||
|
.rounded_md()
|
||||||
|
.border_1()
|
||||||
|
.border_color(rgb(colors::ERROR))
|
||||||
|
.bg(rgb(colors::CANVAS_SOFT))
|
||||||
|
.px_4()
|
||||||
|
.py_3()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.gap_4()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.min_w_0()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(rgb(colors::INK))
|
||||||
|
.child(format!("Confirm clearing {}", pending.host())),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(rgb(colors::MUTED))
|
||||||
|
.child("This removes matching history in the current Space."),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
Button::new("cancel-clear-history-domain")
|
||||||
|
.ghost()
|
||||||
|
.xsmall()
|
||||||
|
.label("Cancel")
|
||||||
|
.on_click(cx.listener(|shell, _, _, cx| {
|
||||||
|
shell.cancel_clear_history_domain_confirmation(cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("confirm-clear-history-domain")
|
||||||
|
.danger()
|
||||||
|
.xsmall()
|
||||||
|
.label("Clear")
|
||||||
|
.on_click(cx.listener(|shell, _, _, cx| {
|
||||||
|
shell.clear_active_space_history_for_pending_domain(cx);
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_history_list(snapshot: &BrowserSnapshot, cx: &mut Context<ElyShell>) -> AnyElement {
|
||||||
|
if snapshot.history_entries.is_empty() {
|
||||||
|
return div()
|
||||||
|
.flex_1()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(rgb(colors::HAIRLINE))
|
||||||
|
.pt_5()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(rgb(colors::MUTED))
|
||||||
|
.child("History is empty for this Space and Profile.")
|
||||||
|
.into_any_element();
|
||||||
|
}
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex_1()
|
||||||
|
.min_h_0()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.overflow_y_scrollbar()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(rgb(colors::HAIRLINE))
|
||||||
|
.children(
|
||||||
|
snapshot
|
||||||
|
.history_entries
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, entry)| render_history_row(index, entry, cx)),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_history_row(
|
||||||
|
index: usize,
|
||||||
|
entry: &HistoryEntry,
|
||||||
|
cx: &mut Context<ElyShell>,
|
||||||
|
) -> AnyElement {
|
||||||
|
let url = entry.url().clone();
|
||||||
|
let host = entry.url().host();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id(SharedString::from(format!("history-{index}")))
|
||||||
|
.py_3()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(rgb(colors::HAIRLINE))
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.gap_4()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.min_w_0()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.font_semibold()
|
||||||
|
.truncate()
|
||||||
|
.text_color(rgb(colors::INK))
|
||||||
|
.child(entry.title().to_string()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.truncate()
|
||||||
|
.text_color(rgb(colors::MUTED))
|
||||||
|
.child(entry.url().display_url()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
Button::new(("open-history-entry", index))
|
||||||
|
.ghost()
|
||||||
|
.xsmall()
|
||||||
|
.label("Open")
|
||||||
|
.tooltip("Open History Entry")
|
||||||
|
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.open_url(url.clone(), window, cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.when_some(host, |this, host| {
|
||||||
|
this.child(
|
||||||
|
Button::new(("clear-history-domain", index))
|
||||||
|
.danger()
|
||||||
|
.xsmall()
|
||||||
|
.label("Clear Domain")
|
||||||
|
.tooltip("Clear Domain History")
|
||||||
|
.on_click(cx.listener(move |shell, _, _, cx| {
|
||||||
|
shell.request_clear_history_domain_confirmation(host.clone(), cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ use gpui::{App, AppContext, Context, Entity, FocusHandle, Focusable, Subscriptio
|
|||||||
use gpui_component::input::{InputEvent, InputState, SelectAll};
|
use gpui_component::input::{InputEvent, InputState, SelectAll};
|
||||||
|
|
||||||
use downloads::PendingDownloadFileAction;
|
use downloads::PendingDownloadFileAction;
|
||||||
|
use history::PendingHistoryDomainClear;
|
||||||
use plugins::{PendingPluginInstall, PendingPluginUninstall};
|
use plugins::{PendingPluginInstall, PendingPluginUninstall};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -38,6 +39,7 @@ pub struct ElyShell {
|
|||||||
download_clear_confirmation: bool,
|
download_clear_confirmation: bool,
|
||||||
download_security_confirmation: Option<PendingDownloadFileAction>,
|
download_security_confirmation: Option<PendingDownloadFileAction>,
|
||||||
history_clear_confirmation: Option<ProfileId>,
|
history_clear_confirmation: Option<ProfileId>,
|
||||||
|
pending_history_domain_clear: Option<PendingHistoryDomainClear>,
|
||||||
site_permissions_clear_confirmation: Option<ProfileId>,
|
site_permissions_clear_confirmation: Option<ProfileId>,
|
||||||
plugin_install_error: Option<String>,
|
plugin_install_error: Option<String>,
|
||||||
pending_plugin_install: Option<PendingPluginInstall>,
|
pending_plugin_install: Option<PendingPluginInstall>,
|
||||||
@@ -101,6 +103,7 @@ impl ElyShell {
|
|||||||
download_clear_confirmation: false,
|
download_clear_confirmation: false,
|
||||||
download_security_confirmation: None,
|
download_security_confirmation: None,
|
||||||
history_clear_confirmation: None,
|
history_clear_confirmation: None,
|
||||||
|
pending_history_domain_clear: None,
|
||||||
site_permissions_clear_confirmation: None,
|
site_permissions_clear_confirmation: None,
|
||||||
plugin_install_error: None,
|
plugin_install_error: None,
|
||||||
pending_plugin_install: None,
|
pending_plugin_install: None,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use ely_domain::{BrowserTab, HistoryEntry, ProfileId, ProfileKind, UrlText};
|
use ely_domain::{BrowserTab, HistoryEntry, ProfileId, ProfileKind, SpaceId, UrlText};
|
||||||
|
|
||||||
use crate::navigation::records_history;
|
use crate::navigation::records_history;
|
||||||
|
|
||||||
@@ -12,6 +12,15 @@ impl BrowserCore {
|
|||||||
self.clear_profile_history(&profile_id)
|
self.clear_profile_history(&profile_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_active_space_history_for_host(
|
||||||
|
&mut self,
|
||||||
|
host: &str,
|
||||||
|
) -> Result<usize, crate::CoreError> {
|
||||||
|
let profile_id = self.active_profile_id.clone();
|
||||||
|
let space_id = self.active_space_id.clone();
|
||||||
|
self.clear_space_profile_history_for_host(&profile_id, &space_id, host)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn record_history_entry(&mut self, tab: &BrowserTab) {
|
pub(super) fn record_history_entry(&mut self, tab: &BrowserTab) {
|
||||||
if !self.history_recording_policy.records_history()
|
if !self.history_recording_policy.records_history()
|
||||||
|| !records_history(tab.url())
|
|| !records_history(tab.url())
|
||||||
@@ -72,6 +81,33 @@ impl BrowserCore {
|
|||||||
Ok(original_count - self.history_entries.len())
|
Ok(original_count - self.history_entries.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clear_space_profile_history_for_host(
|
||||||
|
&mut self,
|
||||||
|
profile_id: &ProfileId,
|
||||||
|
space_id: &SpaceId,
|
||||||
|
host: &str,
|
||||||
|
) -> Result<usize, crate::CoreError> {
|
||||||
|
if !self.profiles.iter().any(|profile| profile.id() == profile_id) {
|
||||||
|
return Err(crate::CoreError::ProfileNotFound { id: profile_id.clone() });
|
||||||
|
}
|
||||||
|
if !self.spaces.iter().any(|space| space.id() == space_id) {
|
||||||
|
return Err(crate::CoreError::SpaceNotFound { id: space_id.clone() });
|
||||||
|
}
|
||||||
|
|
||||||
|
let normalized_host = host.trim().to_ascii_lowercase();
|
||||||
|
if normalized_host.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let original_count = self.history_entries.len();
|
||||||
|
self.history_entries.retain(|entry| {
|
||||||
|
entry.profile_id() != profile_id
|
||||||
|
|| entry.space_id() != space_id
|
||||||
|
|| entry.url().host().as_deref() != Some(normalized_host.as_str())
|
||||||
|
});
|
||||||
|
Ok(original_count - self.history_entries.len())
|
||||||
|
}
|
||||||
|
|
||||||
fn profile_records_history(&self, profile_id: &ProfileId) -> bool {
|
fn profile_records_history(&self, profile_id: &ProfileId) -> bool {
|
||||||
match self.profiles.iter().find(|profile| profile.id() == profile_id) {
|
match self.profiles.iter().find(|profile| profile.id() == profile_id) {
|
||||||
Some(profile) => profile.kind() == &ProfileKind::Standard,
|
Some(profile) => profile.kind() == &ProfileKind::Standard,
|
||||||
|
|||||||
@@ -85,6 +85,58 @@ fn clear_active_profile_history_without_entries_is_empty_change() -> Result<(),
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clear_active_space_history_for_host_stays_in_space_and_profile() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let work_space_id = core.snapshot()?.active_space_id;
|
||||||
|
let default_profile_id = core.snapshot()?.active_profile_id;
|
||||||
|
|
||||||
|
core.open_tab(UrlText::parse("https://example.com/work-one")?);
|
||||||
|
core.open_tab(UrlText::parse("https://example.com/work-two")?);
|
||||||
|
core.open_tab(UrlText::parse("https://example.org/work")?);
|
||||||
|
|
||||||
|
let research_space_id = core.create_space("Research", "R", 0xf54e00)?;
|
||||||
|
core.open_tab(UrlText::parse("https://example.com/research")?);
|
||||||
|
|
||||||
|
core.select_space(&work_space_id)?;
|
||||||
|
let personal_profile_id = core.create_profile("Personal", 0x26251e, ProfileKind::Standard)?;
|
||||||
|
core.open_tab(UrlText::parse("https://example.com/personal")?);
|
||||||
|
core.select_profile(&default_profile_id)?;
|
||||||
|
|
||||||
|
let removed_count = core.clear_active_space_history_for_host("EXAMPLE.com")?;
|
||||||
|
let work_snapshot = core.snapshot()?;
|
||||||
|
assert_eq!(removed_count, 2);
|
||||||
|
assert_eq!(work_snapshot.history_entries.len(), 1);
|
||||||
|
assert_eq!(work_snapshot.history_entries[0].url().as_str(), "https://example.org/work");
|
||||||
|
assert_eq!(work_snapshot.active_profile_history_entry_count, 2);
|
||||||
|
|
||||||
|
core.select_space(&research_space_id)?;
|
||||||
|
let research_snapshot = core.snapshot()?;
|
||||||
|
assert_eq!(research_snapshot.history_entries.len(), 1);
|
||||||
|
assert_eq!(research_snapshot.history_entries[0].url().as_str(), "https://example.com/research");
|
||||||
|
|
||||||
|
core.select_space(&work_space_id)?;
|
||||||
|
core.select_profile(&personal_profile_id)?;
|
||||||
|
let personal_snapshot = core.snapshot()?;
|
||||||
|
assert_eq!(personal_snapshot.history_entries.len(), 1);
|
||||||
|
assert_eq!(personal_snapshot.history_entries[0].url().as_str(), "https://example.com/personal");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clear_active_space_history_for_absent_host_is_empty_change() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
core.open_tab(UrlText::parse("https://example.com/work")?);
|
||||||
|
|
||||||
|
let removed_count = core.clear_active_space_history_for_host("absent.example")?;
|
||||||
|
let snapshot = core.snapshot()?;
|
||||||
|
|
||||||
|
assert_eq!(removed_count, 0);
|
||||||
|
assert_eq!(snapshot.history_entries.len(), 1);
|
||||||
|
assert_eq!(snapshot.active_profile_history_entry_count, 1);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn history_scoped_search_opens_recent_matching_entry() -> Result<(), Box<dyn Error>> {
|
fn history_scoped_search_opens_recent_matching_entry() -> Result<(), Box<dyn Error>> {
|
||||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
|||||||
@@ -52,6 +52,13 @@ impl UrlText {
|
|||||||
.unwrap_or_else(|| self.value.clone())
|
.unwrap_or_else(|| self.value.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn host(&self) -> Option<String> {
|
||||||
|
Url::parse(&self.value)
|
||||||
|
.ok()
|
||||||
|
.and_then(|url| url.host_str().map(|host| host.to_ascii_lowercase()))
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn display_url(&self) -> String {
|
pub fn display_url(&self) -> String {
|
||||||
let Ok(url) = Url::parse(&self.value) else {
|
let Ok(url) = Url::parse(&self.value) else {
|
||||||
|
|||||||
Reference in New Issue
Block a user