asdf98 commited on
Commit
1d7de65
·
verified ·
1 Parent(s): 3522631

Add browser history search and bookmark storage commands

Browse files
Files changed (1) hide show
  1. src-tauri/src/history.rs +65 -13
src-tauri/src/history.rs CHANGED
@@ -3,23 +3,26 @@ use tauri::AppHandle;
3
  use uuid::Uuid;
4
 
5
  #[derive(Debug, Clone, Serialize, Deserialize)]
6
- pub struct HistoryEntry {
7
- pub id: String,
8
- pub tab_id: String,
9
- pub url: String,
10
- pub title: String,
11
- pub visited_at: i64,
12
- }
13
 
14
  const HISTORY_FILE: &str = "browser_history.json";
 
 
 
 
 
 
 
15
 
16
  pub fn record_visit(app: &AppHandle, tab_id: String, url: String, title: String) -> Result<(), String> {
17
- if url.is_empty() || url == "about:blank" || url.starts_with("lumaref-action://") { return Ok(()); }
18
  let mut entries: Vec<HistoryEntry> = crate::persistence::load_json(app, HISTORY_FILE).unwrap_or_default();
19
  let now = chrono::Utc::now().timestamp();
20
- if let Some(last) = entries.first() {
21
- if last.url == url && now - last.visited_at < 3 { return Ok(()); }
22
- }
23
  entries.insert(0, HistoryEntry { id: Uuid::new_v4().to_string(), tab_id, url, title, visited_at: now });
24
  entries.truncate(5000);
25
  crate::persistence::save_json(app, HISTORY_FILE, &entries)
@@ -33,6 +36,55 @@ pub fn history_list(app: AppHandle, limit: Option<usize>) -> Result<Vec<HistoryE
33
  }
34
 
35
  #[tauri::command]
36
- pub fn history_clear(app: AppHandle) -> Result<(), String> {
37
- crate::persistence::save_json(&app, HISTORY_FILE, &Vec::<HistoryEntry>::new())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
 
3
  use uuid::Uuid;
4
 
5
  #[derive(Debug, Clone, Serialize, Deserialize)]
6
+ pub struct HistoryEntry { pub id: String, pub tab_id: String, pub url: String, pub title: String, pub visited_at: i64 }
7
+
8
+ #[derive(Debug, Clone, Serialize, Deserialize)]
9
+ pub struct BookmarkEntry { pub id: String, pub title: String, pub url: String, pub created_at: i64, pub updated_at: i64 }
 
 
 
10
 
11
  const HISTORY_FILE: &str = "browser_history.json";
12
+ const BOOKMARKS_FILE: &str = "browser_bookmarks.json";
13
+ const MAX_TITLE_LEN: usize = 160;
14
+ const MAX_BOOKMARKS: usize = 1000;
15
+
16
+ fn clean_title(raw: &str, fallback: &str) -> String { let s: String = raw.chars().filter(|c| !c.is_control()).take(MAX_TITLE_LEN).collect(); let t = s.trim(); if t.is_empty() { fallback.to_string() } else { t.to_string() } }
17
+ fn valid_url(url: &str) -> bool { url.starts_with("http://") || url.starts_with("https://") }
18
+ fn host_title(url: &str) -> String { url.split("//").nth(1).and_then(|s| s.split('/').next()).unwrap_or(url).trim_start_matches("www.").to_string() }
19
 
20
  pub fn record_visit(app: &AppHandle, tab_id: String, url: String, title: String) -> Result<(), String> {
21
+ if url.is_empty() || url == "about:blank" || url.starts_with("lumaref-action://") || !valid_url(&url) { return Ok(()); }
22
  let mut entries: Vec<HistoryEntry> = crate::persistence::load_json(app, HISTORY_FILE).unwrap_or_default();
23
  let now = chrono::Utc::now().timestamp();
24
+ if let Some(last) = entries.first() { if last.url == url && now - last.visited_at < 3 { return Ok(()); } }
25
+ let title = clean_title(&title, &host_title(&url));
 
26
  entries.insert(0, HistoryEntry { id: Uuid::new_v4().to_string(), tab_id, url, title, visited_at: now });
27
  entries.truncate(5000);
28
  crate::persistence::save_json(app, HISTORY_FILE, &entries)
 
36
  }
37
 
38
  #[tauri::command]
39
+ pub fn history_search(app: AppHandle, query: String, limit: Option<usize>) -> Result<Vec<HistoryEntry>, String> {
40
+ let q = query.trim().to_lowercase();
41
+ if q.is_empty() { return history_list(app, limit); }
42
+ let max = limit.unwrap_or(8).min(50);
43
+ let entries: Vec<HistoryEntry> = crate::persistence::load_json(&app, HISTORY_FILE).unwrap_or_default();
44
+ let mut out = Vec::new();
45
+ for e in entries {
46
+ if e.url.to_lowercase().contains(&q) || e.title.to_lowercase().contains(&q) {
47
+ out.push(e);
48
+ if out.len() >= max { break; }
49
+ }
50
+ }
51
+ Ok(out)
52
+ }
53
+
54
+ #[tauri::command]
55
+ pub fn history_clear(app: AppHandle) -> Result<(), String> { crate::persistence::save_json(&app, HISTORY_FILE, &Vec::<HistoryEntry>::new()) }
56
+
57
+ #[tauri::command]
58
+ pub fn bookmarks_list(app: AppHandle) -> Result<Vec<BookmarkEntry>, String> {
59
+ let mut items: Vec<BookmarkEntry> = crate::persistence::load_json(&app, BOOKMARKS_FILE).unwrap_or_default();
60
+ items.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
61
+ Ok(items)
62
+ }
63
+
64
+ #[tauri::command]
65
+ pub fn bookmarks_add(app: AppHandle, url: String, title: Option<String>) -> Result<BookmarkEntry, String> {
66
+ let url = url.trim().to_string();
67
+ if !valid_url(&url) { return Err("Bookmark URL must be http(s)".into()); }
68
+ let now = chrono::Utc::now().timestamp();
69
+ let mut items: Vec<BookmarkEntry> = crate::persistence::load_json(&app, BOOKMARKS_FILE).unwrap_or_default();
70
+ let title = clean_title(title.as_deref().unwrap_or(""), &host_title(&url));
71
+ if let Some(existing) = items.iter_mut().find(|b| b.url == url) {
72
+ existing.title = title;
73
+ existing.updated_at = now;
74
+ let result = existing.clone();
75
+ crate::persistence::save_json(&app, BOOKMARKS_FILE, &items)?;
76
+ return Ok(result);
77
+ }
78
+ let item = BookmarkEntry { id: Uuid::new_v4().to_string(), title, url, created_at: now, updated_at: now };
79
+ items.insert(0, item.clone());
80
+ items.truncate(MAX_BOOKMARKS);
81
+ crate::persistence::save_json(&app, BOOKMARKS_FILE, &items)?;
82
+ Ok(item)
83
+ }
84
+
85
+ #[tauri::command]
86
+ pub fn bookmarks_remove(app: AppHandle, id: String) -> Result<(), String> {
87
+ let mut items: Vec<BookmarkEntry> = crate::persistence::load_json(&app, BOOKMARKS_FILE).unwrap_or_default();
88
+ items.retain(|b| b.id != id);
89
+ crate::persistence::save_json(&app, BOOKMARKS_FILE, &items)
90
  }