diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-04-13 04:08:27 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-04-13 04:08:27 +0800 |
| commit | 9be1a0af6491d2f9bde8061f76f8f471153229aa (patch) | |
| tree | 400445afc8691b850fa679dd524be443b1d621e0 | |
| parent | d939c8afdb13dfe981b0368c788fb1a95277f165 (diff) | |
| download | rwebbrowser-9be1a0af6491d2f9bde8061f76f8f471153229aa.tar.gz rwebbrowser-9be1a0af6491d2f9bde8061f76f8f471153229aa.tar.xz | |
feat: implement browsing history tracking and navigation via ricochet://history
| -rw-r--r-- | src/core/browser.cc | 72 |
1 files changed, 71 insertions, 1 deletions
diff --git a/src/core/browser.cc b/src/core/browser.cc index 779bdb2..fb9af83 100644 --- a/src/core/browser.cc +++ b/src/core/browser.cc @@ -149,6 +149,60 @@ std::string generate_bookmarks_html() return html; } +void append_to_history( const std::string& url, // NOLINT(bugprone-easily-swappable-parameters) + const std::string& title ) +{ + if ( url.starts_with( "ricochet://" ) ) { + return; + } + std::ofstream file( get_config_path( "history.txt" ), std::ios::app ); + if ( !file.is_open() ) { + return; + } + + std::string safe_title = title; + if ( safe_title.empty() ) { + safe_title = url; + } + for ( char& c : safe_title ) { + if ( c == '\n' || c == '\r' || c == '\t' ) { + c = ' '; + } + } + file << url << '\t' << safe_title << '\n'; +} + +std::string generate_history_html() +{ + std::ifstream file( get_config_path( "history.txt" ) ); + std::string html = "<html><head><title>History</title></head><body><h1>Browsing History</h1><ul>"; + if ( !file.is_open() ) { + html += "<li>No history found. Start browsing!</li>"; + } else { + std::vector<std::string> lines; + std::string line; + while ( std::getline( file, line ) ) { + lines.push_back( std::move( line ) ); + } + + std::size_t count = 0; + for ( auto it = lines.rbegin(); it != lines.rend() && count < 100; ++it, ++count ) { + const std::size_t tab_pos = it->find( '\t' ); + if ( tab_pos != std::string::npos ) { + const std::string url = it->substr( 0, tab_pos ); + const std::string title = it->substr( tab_pos + 1 ); + html += "<li><a href=\""; + html += url; + html += "\">[Link] "; + html += title; + html += "</a></li>"; + } + } + } + html += "</ul></body></html>"; + return html; +} + PageData process_binary_download( const HttpRequest& req, const net::HttpResponse& resp ) { std::string fname = "download.dat"; @@ -201,6 +255,8 @@ PageData load_page( const HttpRequest& req ) std::string html_body; if ( req.url == "ricochet://bookmarks" ) { html_body = generate_bookmarks_html(); + } else if ( req.url == "ricochet://history" ) { + html_body = generate_history_html(); } else { const net::HttpClient client; auto response_result = client.fetch( req.url, req.method, req.body ); @@ -243,7 +299,8 @@ PageData load_page( const HttpRequest& req ) std::string build_footer( std::string_view url, std::size_t width ) { - const std::string_view keys = " | [j/k]Scroll [n/N]Find [/]Nav [f]Hint [i]In [r]Ref [H/L]Hist [b/B]Mark [q]Quit "; + const std::string_view keys + = " | [j/k]Scroll [n/N]Find [/]Nav [f]Hint [i]In [r]Ref [H/L]Back [h]Hist [b/B]Mark [q]Quit "; const std::string_view prefix = " URL: "; std::string footer; @@ -827,6 +884,13 @@ InputAction process_key( char c, return InputAction::Navigate; } + if ( c == 'h' ) { + history.resize( history_idx + 1 ); + history.push_back( HttpRequest { .url = "ricochet://history", .method = "GET", .body = "" } ); + history_idx++; + return InputAction::Navigate; + } + if ( c == 'n' ) { const std::string query = get_terminal_input( terminal, "Find in Page:" ); execute_search( page_data, query, scroll_y, false ); @@ -917,6 +981,12 @@ int Browser::run( std::string_view initial_url ) auto page_data = load_page( current_req ); + static std::string last_recorded_url; + if ( current_url != last_recorded_url && !current_url.starts_with( "ricochet://" ) ) { + append_to_history( current_url, page_data.title ); + last_recorded_url = current_url; + } + auto [term_width, term_height] = terminal.get_size(); const std::size_t wrap_width = ( term_width > 1 ) ? ( term_width - 1 ) : 80; page_data.lines = wrap_lines( page_data.lines, wrap_width ); |
