diff --git a/Base/res/ladybird/about-pages/bookmarks.html b/Base/res/ladybird/about-pages/bookmarks.html new file mode 100644 index 00000000000..b620d3adb16 --- /dev/null +++ b/Base/res/ladybird/about-pages/bookmarks.html @@ -0,0 +1,468 @@ + + + + Bookmark Manager + + + + + +
+ + + + +

Bookmarks

+
+ +
+
+
+ + + + diff --git a/Libraries/LibURL/URL.h b/Libraries/LibURL/URL.h index f80e65851b4..62ba35ffbc4 100644 --- a/Libraries/LibURL/URL.h +++ b/Libraries/LibURL/URL.h @@ -219,13 +219,14 @@ inline URL about_srcdoc() { return URL::about("srcdoc"_string); } inline URL about_error() { return URL::about("error"_string); } inline URL about_newtab() { return URL::about("newtab"_string); } +inline URL about_bookmarks() { return URL::about("bookmarks"_string); } inline URL about_processes() { return URL::about("processes"_string); } inline URL about_settings() { return URL::about("settings"_string); } inline URL about_version() { return URL::about("version"_string); } inline bool is_webui_url(URL const& url) { - return first_is_one_of(url, about_processes(), about_settings()); + return first_is_one_of(url, about_bookmarks(), about_processes(), about_settings()); } } diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 0d5158801b2..b1b6aea51f0 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -934,6 +934,10 @@ void Application::initialize_actions() m_motion_menu->items().first().get>()->set_checked(true); m_bookmarks_menu = Menu::create("Bookmarks"sv); + m_bookmarks_menu->add_action(Action::create("Manage Bookmarks"sv, ActionID::ManageBookmarks, [this]() { + open_url_in_new_tab(URL::about_bookmarks(), Web::HTML::ActivateTab::Yes); + })); + m_bookmarks_menu->add_separator(); m_toggle_bookmark_action = Action::create("Toggle Bookmark"sv, ActionID::ToggleBookmark, [this]() { auto view = active_web_view(); diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index 67282293a69..c2188bd2fec 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -67,6 +67,8 @@ public: void bookmarks_changed(Badge); void show_bookmarks_bar_changed(Badge); + virtual void show_bookmark_context_menu(Gfx::IntPoint, Optional, [[maybe_unused]] Optional target_folder_id) { } + static CookieJar& cookie_jar() { return *the().m_cookie_jar; } static StorageJar& storage_jar() { return *the().m_storage_jar; } diff --git a/Libraries/LibWebView/CMakeLists.txt b/Libraries/LibWebView/CMakeLists.txt index 10c14c0e53a..c419e46b56e 100644 --- a/Libraries/LibWebView/CMakeLists.txt +++ b/Libraries/LibWebView/CMakeLists.txt @@ -29,6 +29,7 @@ set(SOURCES ViewImplementation.cpp WebContentClient.cpp WebUI.cpp + WebUI/BookmarksUI.cpp WebUI/ProcessesUI.cpp WebUI/SettingsUI.cpp ) diff --git a/Libraries/LibWebView/Forward.h b/Libraries/LibWebView/Forward.h index 42afdc83fb3..af64f7b7b16 100644 --- a/Libraries/LibWebView/Forward.h +++ b/Libraries/LibWebView/Forward.h @@ -27,6 +27,7 @@ class WebUI; struct Attribute; struct AutocompleteEngine; +struct BookmarkItem; struct BrowserOptions; struct ConsoleOutput; struct CookieStorageKey; diff --git a/Libraries/LibWebView/Menu.h b/Libraries/LibWebView/Menu.h index d19e2df71bf..376bb7fa065 100644 --- a/Libraries/LibWebView/Menu.h +++ b/Libraries/LibWebView/Menu.h @@ -37,6 +37,7 @@ enum class ActionID { TakeVisibleScreenshot, TakeFullScreenshot, + ManageBookmarks, ToggleBookmark, ToggleBookmarkViaToolbar, ToggleBookmarksBar, diff --git a/Libraries/LibWebView/WebUI.cpp b/Libraries/LibWebView/WebUI.cpp index d016f75787b..21125d25444 100644 --- a/Libraries/LibWebView/WebUI.cpp +++ b/Libraries/LibWebView/WebUI.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -29,7 +30,9 @@ ErrorOr> WebUI::create(WebContentClient& client, String host) { RefPtr web_ui; - if (host == "processes"sv) + if (host == "bookmarks"sv) + web_ui = TRY(create_web_ui(client, move(host))); + else if (host == "processes"sv) web_ui = TRY(create_web_ui(client, move(host))); else if (host == "settings"sv) web_ui = TRY(create_web_ui(client, move(host))); diff --git a/Libraries/LibWebView/WebUI/BookmarksUI.cpp b/Libraries/LibWebView/WebUI/BookmarksUI.cpp new file mode 100644 index 00000000000..b740f730cbf --- /dev/null +++ b/Libraries/LibWebView/WebUI/BookmarksUI.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace WebView { + +void BookmarksUI::register_interfaces() +{ + register_interface("loadBookmarks"sv, [this](auto const&) { + load_bookmarks(); + }); + register_interface("moveItem"sv, [this](auto const& data) { + move_item(data); + }); + register_interface("showContextMenu"sv, [this](auto const& data) { + show_context_menu(data); + }); +} + +void BookmarksUI::bookmarks_changed() +{ + load_bookmarks(); +} + +void BookmarksUI::load_bookmarks() +{ + async_send_message("loadBookmarks"sv, Application::bookmark_store().serialize_items()); +} + +void BookmarksUI::move_item(JsonValue const& data) +{ + if (!data.is_object()) + return; + auto const& object = data.as_object(); + + auto id = object.get_string("id"sv); + auto index = object.get_integer("index"sv); + if (!id.has_value() || !index.has_value()) + return; + + auto target_folder_id = object.get_string("targetFolderId"sv); + Application::bookmark_store().move_item(*id, target_folder_id, *index); +} + +void BookmarksUI::show_context_menu(JsonValue const& data) +{ + if (!data.is_object()) + return; + auto const& object = data.as_object(); + + auto client_x = object.get_integer("clientX"sv); + auto client_y = object.get_integer("clientY"sv); + if (!client_x.has_value() || !client_y.has_value()) + return; + + if (auto id = object.get_string("id"sv); id.has_value()) { + auto item = Application::bookmark_store().find_item_by_id(*id); + auto target_folder_id = object.get_string("targetFolderId"sv); + + Application::the().show_bookmark_context_menu({ *client_x, *client_y }, item, target_folder_id); + } else { + Application::the().show_bookmark_context_menu({ *client_x, *client_y }, {}, {}); + } +} + +} diff --git a/Libraries/LibWebView/WebUI/BookmarksUI.h b/Libraries/LibWebView/WebUI/BookmarksUI.h new file mode 100644 index 00000000000..6ddf13e1504 --- /dev/null +++ b/Libraries/LibWebView/WebUI/BookmarksUI.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2026, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace WebView { + +class BookmarksUI + : public WebUI + , public BookmarkStoreObserver { + WEB_UI(BookmarksUI); + +private: + virtual void register_interfaces() override; + virtual void bookmarks_changed() override; + + void load_bookmarks(); + void move_item(JsonValue const&); + void show_context_menu(JsonValue const&); +}; + +} diff --git a/UI/AppKit/Application/Application.h b/UI/AppKit/Application/Application.h index 50bb3d87919..a41966db635 100644 --- a/UI/AppKit/Application/Application.h +++ b/UI/AppKit/Application/Application.h @@ -33,6 +33,7 @@ private: virtual void rebuild_bookmarks_menu() const override; virtual void update_bookmarks_bar_display(bool) const override; + virtual void show_bookmark_context_menu(Gfx::IntPoint, Optional, Optional target_folder_id) override; virtual Optional bookmark_item_id_for_context_menu() const override; virtual NonnullRefPtr display_add_bookmark_dialog() const override; virtual NonnullRefPtr display_edit_bookmark_dialog(WebView::BookmarkItem::Bookmark const& current_bookmark) const override; diff --git a/UI/AppKit/Application/Application.mm b/UI/AppKit/Application/Application.mm index bb2d55a476d..30759182cc2 100644 --- a/UI/AppKit/Application/Application.mm +++ b/UI/AppKit/Application/Application.mm @@ -166,6 +166,18 @@ void Application::update_bookmarks_bar_display(bool show_bookmarks_bar) const [delegate updateBookmarksBarDisplay:show_bookmarks_bar]; } +void Application::show_bookmark_context_menu(Gfx::IntPoint content_position, Optional item, Optional target_folder_id) +{ + ApplicationDelegate* delegate = [NSApp delegate]; + + if (auto* tab = [delegate activeTab]) { + [[tab bookmarksBar] showContextMenu:content_position + view:[tab web_view] + bookmarkItem:item + targetFolderID:target_folder_id]; + } +} + Optional Application::bookmark_item_id_for_context_menu() const { ApplicationDelegate* delegate = [NSApp delegate]; diff --git a/UI/AppKit/Interface/BookmarksBar.h b/UI/AppKit/Interface/BookmarksBar.h index cb052494e57..9b6b0de52b8 100644 --- a/UI/AppKit/Interface/BookmarksBar.h +++ b/UI/AppKit/Interface/BookmarksBar.h @@ -6,6 +6,10 @@ #pragma once +#include +#include +#include + #import @class BookmarkFolderPopover; @@ -20,6 +24,10 @@ - (void)bookmarkFolderDidClose:(BookmarkFolderPopover*)folder; - (void)showContextMenu:(id)control event:(NSEvent*)event; +- (void)showContextMenu:(Gfx::IntPoint)content_position + view:(NSView*)view + bookmarkItem:(Optional)item + targetFolderID:(Optional)target_folder_id; @property (nonatomic, strong, readonly) NSString* selected_bookmark_menu_item_id; @property (nonatomic, strong, readonly) NSString* selected_bookmark_menu_target_folder_id; diff --git a/UI/AppKit/Interface/BookmarksBar.mm b/UI/AppKit/Interface/BookmarksBar.mm index 599cc1d9147..d235ecce8ff 100644 --- a/UI/AppKit/Interface/BookmarksBar.mm +++ b/UI/AppKit/Interface/BookmarksBar.mm @@ -5,10 +5,12 @@ */ #include +#include #include #import #import +#import #import #import @@ -287,6 +289,31 @@ static Optional find_bookmark_folder_by_id(WebView::Menu& menu, [NSMenu popUpContextMenu:self.bookmark_folder_context_menu withEvent:event forView:control]; } +- (void)showContextMenu:(Gfx::IntPoint)content_position + view:(NSView*)view + bookmarkItem:(Optional)item + targetFolderID:(Optional)target_folder_id +{ + auto* event = Ladybird::create_context_menu_mouse_event(view, content_position); + + if (item.has_value()) { + self.selected_bookmark_menu_item_id = Ladybird::string_to_ns_string(item->id); + self.selected_bookmark_menu_target_folder_id = target_folder_id.has_value() + ? Ladybird::string_to_ns_string(*target_folder_id) + : nil; + + if (item->is_bookmark()) + [NSMenu popUpContextMenu:self.bookmark_context_menu withEvent:event forView:view]; + else if (item->is_folder()) + [NSMenu popUpContextMenu:self.bookmark_folder_context_menu withEvent:event forView:view]; + } else { + self.selected_bookmark_menu_item_id = @""; + self.selected_bookmark_menu_target_folder_id = nil; + + [NSMenu popUpContextMenu:self.bookmarks_bar_context_menu withEvent:event forView:view]; + } +} + - (void)showContextMenuForEvent:(NSEvent*)event { if (auto* button = [self bookmarkButtonForEvent:event]) { diff --git a/UI/AppKit/Interface/Menu.mm b/UI/AppKit/Interface/Menu.mm index ff55bd4ca65..07b8d0c20b1 100644 --- a/UI/AppKit/Interface/Menu.mm +++ b/UI/AppKit/Interface/Menu.mm @@ -236,6 +236,9 @@ static void initialize_native_icon(WebView::Action& action, id control) set_control_image(control, @"magnifyingglass"); break; + case WebView::ActionID::ManageBookmarks: + set_control_image(control, @"bookmark"); + break; case WebView::ActionID::ToggleBookmark: [control setKeyEquivalent:@"d"]; break; diff --git a/UI/Qt/Application.cpp b/UI/Qt/Application.cpp index 5581f06220e..b4e2f8cb773 100644 --- a/UI/Qt/Application.cpp +++ b/UI/Qt/Application.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -248,6 +249,14 @@ void Application::update_bookmarks_bar_display(bool show_bookmarks_bar) const } } +void Application::show_bookmark_context_menu(Gfx::IntPoint content_position, Optional item, Optional target_folder_id) +{ + if (auto* active_tab = this->active_tab()) { + auto position = active_tab->view().mapToGlobal(QPoint { content_position.x(), content_position.y() }); + active_tab->bookmarks_bar().show_context_menu(position, item, target_folder_id); + } +} + Optional Application::bookmark_item_id_for_context_menu() const { if (auto* active_tab = this->active_tab()) { diff --git a/UI/Qt/Application.h b/UI/Qt/Application.h index c0f5546c4e4..174b2a3a74a 100644 --- a/UI/Qt/Application.h +++ b/UI/Qt/Application.h @@ -49,6 +49,7 @@ private: virtual void rebuild_bookmarks_menu() const override; virtual void update_bookmarks_bar_display(bool) const override; + virtual void show_bookmark_context_menu(Gfx::IntPoint, Optional, Optional target_folder_id) override; virtual Optional bookmark_item_id_for_context_menu() const override; virtual NonnullRefPtr display_add_bookmark_dialog() const override; virtual NonnullRefPtr display_edit_bookmark_dialog(WebView::BookmarkItem::Bookmark const& current_bookmark) const override; diff --git a/UI/Qt/BookmarksBar.cpp b/UI/Qt/BookmarksBar.cpp index 04e2aa0adeb..e320536ffbd 100644 --- a/UI/Qt/BookmarksBar.cpp +++ b/UI/Qt/BookmarksBar.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -95,6 +96,24 @@ void BookmarksBar::rebuild() } } +void BookmarksBar::show_context_menu(QPoint position, Optional item, Optional target_folder_id) +{ + if (item.has_value()) { + m_selected_bookmark_menu_item_id = item->id; + m_selected_bookmark_menu_target_folder_id = target_folder_id.copy(); + + if (item->is_bookmark()) + bookmark_context_menu().exec(position); + else if (item->is_folder()) + bookmark_folder_context_menu().exec(position); + } else { + m_selected_bookmark_menu_item_id = {}; + m_selected_bookmark_menu_target_folder_id = {}; + + bookmarks_bar_context_menu().exec(position); + } +} + bool BookmarksBar::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::MouseButtonPress) { diff --git a/UI/Qt/BookmarksBar.h b/UI/Qt/BookmarksBar.h index a491c535fd4..8f2ca6acb56 100644 --- a/UI/Qt/BookmarksBar.h +++ b/UI/Qt/BookmarksBar.h @@ -8,6 +8,7 @@ #include #include +#include #include @@ -24,6 +25,8 @@ public: String const& selected_bookmark_menu_item_id() const { return m_selected_bookmark_menu_item_id; } Optional const& selected_bookmark_menu_target_folder_id() const { return m_selected_bookmark_menu_target_folder_id; } + void show_context_menu(QPoint, Optional, Optional target_folder_id); + private: virtual bool eventFilter(QObject* object, QEvent* event) override; diff --git a/UI/cmake/ResourceFiles.cmake b/UI/cmake/ResourceFiles.cmake index 8d9fac359a3..ed020f13135 100644 --- a/UI/cmake/ResourceFiles.cmake +++ b/UI/cmake/ResourceFiles.cmake @@ -70,6 +70,7 @@ list(TRANSFORM INTERNAL_RESOURCES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladyb set(ABOUT_PAGES about.html + bookmarks.html newtab.html processes.html settings.html