[Search]: Add Brave Search Widget to the New Tab Page (#22231)
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include "brave/browser/brave_features_internal_names.h"
|
||||
#include "brave/browser/ethereum_remote_client/buildflags/buildflags.h"
|
||||
#include "brave/browser/ethereum_remote_client/features.h"
|
||||
#include "brave/browser/ui/brave_ui_features.h"
|
||||
#include "brave/browser/ui/tabs/features.h"
|
||||
#include "brave/components/ai_chat/core/common/buildflags/buildflags.h"
|
||||
#include "brave/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_feature.h"
|
||||
@@ -500,6 +501,13 @@
|
||||
FEATURE_VALUE_TYPE( \
|
||||
ntp_background_images::features::kBraveNTPBrandedWallpaperDemo), \
|
||||
}, \
|
||||
{ \
|
||||
"brave-ntp-search-widget", \
|
||||
"Brave Search Widget on the NTP", \
|
||||
"Enables searching directly from the New Tab Page", \
|
||||
kOsDesktop, \
|
||||
FEATURE_VALUE_TYPE(features::kBraveNtpSearchWidget), \
|
||||
}, \
|
||||
{ \
|
||||
"brave-adblock-cname-uncloaking", \
|
||||
"Enable CNAME uncloaking", \
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "brave/browser/profiles/brave_renderer_updater_factory.h"
|
||||
#include "brave/browser/profiles/profile_util.h"
|
||||
#include "brave/browser/skus/skus_service_factory.h"
|
||||
#include "brave/browser/ui/brave_ui_features.h"
|
||||
#include "brave/browser/ui/webui/skus_internals_ui.h"
|
||||
#include "brave/components/ai_chat/core/common/buildflags/buildflags.h"
|
||||
#include "brave/components/brave_federated/features.h"
|
||||
@@ -236,6 +237,7 @@ using extensions::ChromeContentBrowserClientExtensionsPart;
|
||||
#include "brave/components/brave_shields/core/common/cookie_list_opt_in.mojom.h"
|
||||
#include "brave/components/commands/common/commands.mojom.h"
|
||||
#include "brave/components/commands/common/features.h"
|
||||
#include "components/omnibox/browser/omnibox.mojom.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
@@ -627,6 +629,15 @@ void BraveContentBrowserClient::RegisterWebUIInterfaceBrokers(
|
||||
}
|
||||
|
||||
#if !BUILDFLAG(IS_ANDROID)
|
||||
auto ntp_registration =
|
||||
registry.ForWebUI<BraveNewTabUI>()
|
||||
.Add<brave_new_tab_page::mojom::PageHandlerFactory>()
|
||||
.Add<brave_news::mojom::BraveNewsController>();
|
||||
|
||||
if (base::FeatureList::IsEnabled(features::kBraveNtpSearchWidget)) {
|
||||
ntp_registration.Add<omnibox::mojom::PageHandler>();
|
||||
}
|
||||
|
||||
if (base::FeatureList::IsEnabled(
|
||||
brave_news::features::kBraveNewsFeedUpdate)) {
|
||||
registry.ForWebUI<BraveNewsInternalsUI>()
|
||||
@@ -825,22 +836,11 @@ void BraveContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Brave News
|
||||
#if !BUILDFLAG(IS_ANDROID)
|
||||
content::RegisterWebUIControllerInterfaceBinder<
|
||||
brave_news::mojom::BraveNewsController, BraveNewTabUI>(map);
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_SPEEDREADER) && !BUILDFLAG(IS_ANDROID)
|
||||
content::RegisterWebUIControllerInterfaceBinder<
|
||||
speedreader::mojom::ToolbarFactory, SpeedreaderToolbarUI>(map);
|
||||
#endif
|
||||
|
||||
#if !BUILDFLAG(IS_ANDROID)
|
||||
content::RegisterWebUIControllerInterfaceBinder<
|
||||
brave_new_tab_page::mojom::PageHandlerFactory, BraveNewTabUI>(map);
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
map->Add<playlist::mojom::PlaylistMediaHandler>(
|
||||
base::BindRepeating(&BindPlaylistMediaHandler));
|
||||
|
||||
@@ -36,6 +36,8 @@ source_set("ui") {
|
||||
|
||||
public_deps = []
|
||||
sources = [
|
||||
"brave_ui_features.cc",
|
||||
"brave_ui_features.h",
|
||||
"webui/brave_adblock_internals_ui.cc",
|
||||
"webui/brave_adblock_internals_ui.h",
|
||||
"webui/brave_adblock_ui.cc",
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "brave/browser/ui/brave_ui_features.h"
|
||||
|
||||
namespace features {
|
||||
|
||||
BASE_FEATURE(kBraveNtpSearchWidget,
|
||||
"BraveNtpSearchWidget",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifndef BRAVE_BROWSER_UI_BRAVE_UI_FEATURES_H_
|
||||
#define BRAVE_BROWSER_UI_BRAVE_UI_FEATURES_H_
|
||||
|
||||
#include "base/feature_list.h"
|
||||
|
||||
namespace features {
|
||||
|
||||
BASE_DECLARE_FEATURE(kBraveNtpSearchWidget);
|
||||
|
||||
}
|
||||
|
||||
#endif // BRAVE_BROWSER_UI_BRAVE_UI_FEATURES_H_
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "brave/browser/ui/webui/new_tab_page/brave_new_tab_ui.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/check.h"
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "brave/browser/brave_news/brave_news_controller_factory.h"
|
||||
#include "brave/browser/new_tab/new_tab_shows_options.h"
|
||||
#include "brave/browser/ntp_background/brave_ntp_custom_background_service_factory.h"
|
||||
#include "brave/browser/ui/brave_ui_features.h"
|
||||
#include "brave/browser/ui/webui/brave_webui_source.h"
|
||||
#include "brave/browser/ui/webui/new_tab_page/brave_new_tab_message_handler.h"
|
||||
#include "brave/browser/ui/webui/new_tab_page/brave_new_tab_page_handler.h"
|
||||
@@ -25,11 +27,13 @@
|
||||
#include "chrome/browser/search_engines/template_url_service_factory.h"
|
||||
#include "chrome/common/pref_names.h"
|
||||
#include "components/grit/brave_components_resources.h"
|
||||
#include "components/omnibox/browser/omnibox.mojom.h"
|
||||
#include "components/strings/grit/components_strings.h"
|
||||
#include "content/public/browser/navigation_entry.h"
|
||||
#include "content/public/browser/url_data_source.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_ui_data_source.h"
|
||||
#include "mojo/public/cpp/bindings/pending_receiver.h"
|
||||
|
||||
using ntp_background_images::NTPCustomImagesSource;
|
||||
|
||||
@@ -78,6 +82,10 @@ BraveNewTabUI::BraveNewTabUI(content::WebUI* web_ui, const std::string& name)
|
||||
"featureFlagBraveNewsFeedV2Enabled",
|
||||
base::FeatureList::IsEnabled(brave_news::features::kBraveNewsFeedUpdate));
|
||||
|
||||
source->AddBoolean(
|
||||
"featureFlagSearchWidget",
|
||||
base::FeatureList::IsEnabled(features::kBraveNtpSearchWidget));
|
||||
|
||||
web_ui->AddMessageHandler(base::WrapUnique(
|
||||
BraveNewTabMessageHandler::Create(source, profile, was_restored)));
|
||||
web_ui->AddMessageHandler(
|
||||
@@ -116,6 +124,16 @@ void BraveNewTabUI::BindInterface(
|
||||
page_factory_receiver_.Bind(std::move(pending_receiver));
|
||||
}
|
||||
|
||||
void BraveNewTabUI::BindInterface(
|
||||
mojo::PendingReceiver<omnibox::mojom::PageHandler> pending_page_handler) {
|
||||
auto* profile = Profile::FromWebUI(web_ui());
|
||||
DCHECK(profile);
|
||||
|
||||
realbox_handler_ = std::make_unique<RealboxHandler>(
|
||||
std::move(pending_page_handler), profile, web_ui()->GetWebContents(),
|
||||
nullptr, nullptr);
|
||||
}
|
||||
|
||||
void BraveNewTabUI::CreatePageHandler(
|
||||
mojo::PendingRemote<brave_new_tab_page::mojom::Page> pending_page,
|
||||
mojo::PendingReceiver<brave_new_tab_page::mojom::PageHandler>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#include "brave/components/brave_new_tab_ui/brave_new_tab_page.mojom.h"
|
||||
#include "brave/components/brave_news/common/brave_news.mojom.h"
|
||||
#include "chrome/browser/ui/webui/realbox/realbox_handler.h"
|
||||
#include "components/omnibox/browser/omnibox.mojom.h"
|
||||
#include "content/public/browser/web_ui_controller.h"
|
||||
#include "mojo/public/cpp/bindings/pending_receiver.h"
|
||||
#include "mojo/public/cpp/bindings/pending_remote.h"
|
||||
@@ -40,6 +42,9 @@ class BraveNewTabUI : public ui::MojoWebUIController,
|
||||
mojo::PendingReceiver<brave_new_tab_page::mojom::PageHandlerFactory>
|
||||
pending_receiver);
|
||||
|
||||
void BindInterface(
|
||||
mojo::PendingReceiver<omnibox::mojom::PageHandler> pending_page_handler);
|
||||
|
||||
private:
|
||||
// new_tab_page::mojom::PageHandlerFactory:
|
||||
void CreatePageHandler(
|
||||
@@ -48,6 +53,7 @@ class BraveNewTabUI : public ui::MojoWebUIController,
|
||||
pending_page_handler) override;
|
||||
|
||||
std::unique_ptr<BraveNewTabPageHandler> page_handler_;
|
||||
std::unique_ptr<RealboxHandler> realbox_handler_;
|
||||
mojo::Receiver<brave_new_tab_page::mojom::PageHandlerFactory>
|
||||
page_factory_receiver_;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "chrome/browser/ui/webui/realbox/realbox_handler.h"
|
||||
|
||||
#include "brave/components/vector_icons/vector_icons.h"
|
||||
#include "components/omnibox/browser/vector_icons.h"
|
||||
|
||||
namespace {
|
||||
constexpr char kLeoWindowTabNewIconResourceName[] =
|
||||
"//resources/brave-icons/window-tab-new.svg";
|
||||
}
|
||||
|
||||
// This patches |PedalVectorIconToResourceName| to include the additional
|
||||
// omnibox icons we add in Brave. Unfortunately, the method is used inside the
|
||||
// file, so we can't just override the method. There is only one usage of
|
||||
// kSwitchIcon in the file, so we add additional statements from it.
|
||||
#define kSwitchIcon kSwitchIcon.name) {} \
|
||||
if (icon.name == kLeoWindowTabNewIcon.name) { \
|
||||
return kLeoWindowTabNewIconResourceName; \
|
||||
} \
|
||||
if (icon.name == omnibox::kSwitchIcon
|
||||
|
||||
#include "src/chrome/browser/ui/webui/realbox/realbox_handler.cc"
|
||||
|
||||
#undef kSwitchIcon
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "src/components/omnibox/browser/omnibox_client.cc"
|
||||
|
||||
bool OmniboxClient::IsAutocompleteEnabled() const {
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifndef BRAVE_CHROMIUM_SRC_COMPONENTS_OMNIBOX_BROWSER_OMNIBOX_CLIENT_H_
|
||||
#define BRAVE_CHROMIUM_SRC_COMPONENTS_OMNIBOX_BROWSER_OMNIBOX_CLIENT_H_
|
||||
|
||||
#define CurrentPageExists \
|
||||
IsAutocompleteEnabled() const; \
|
||||
virtual bool CurrentPageExists
|
||||
|
||||
#include "src/components/omnibox/browser/omnibox_client.h" // IWYU pragma: export
|
||||
|
||||
#undef CurrentPageExists
|
||||
|
||||
#endif // BRAVE_CHROMIUM_SRC_COMPONENTS_OMNIBOX_BROWSER_OMNIBOX_CLIENT_H_
|
||||
@@ -5,16 +5,13 @@
|
||||
|
||||
#include "components/omnibox/browser/omnibox_controller.h"
|
||||
|
||||
#include "brave/components/omnibox/browser/brave_omnibox_client.h"
|
||||
|
||||
#define StartAutocomplete StartAutocomplete_ChromiumImpl
|
||||
#include "src/components/omnibox/browser/omnibox_controller.cc"
|
||||
#undef StartAutocomplete
|
||||
|
||||
void OmniboxController::StartAutocomplete(
|
||||
const AutocompleteInput& input) const {
|
||||
auto* client = static_cast<BraveOmniboxClient*>(client_.get());
|
||||
if (!client->IsAutocompleteEnabled()) {
|
||||
if (!client_->IsAutocompleteEnabled()) {
|
||||
ClearPopupKeywordMode();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ transpile_web_ui("brave_new_tab_ui") {
|
||||
deps = [
|
||||
":mojom_js",
|
||||
"//brave/components/brave_news/common:mojom_js",
|
||||
"//components/omnibox/browser:mojo_bindings",
|
||||
"//components/omnibox/browser:mojo_bindings_js",
|
||||
]
|
||||
resource_name = "brave_new_tab"
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as React from 'react'
|
||||
import styled, { createGlobalStyle, css } from 'styled-components'
|
||||
import { requestAnimationFrameThrottle } from '../../../../common/throttle'
|
||||
import { defaultState } from '../../../storage/new_tab_storage'
|
||||
import { spacing } from '@brave/leo/tokens/css'
|
||||
|
||||
const breakpointLargeBlocks = '980px'
|
||||
const breakpointEveryBlock = '870px'
|
||||
@@ -278,6 +279,10 @@ export const GridItemNavigationBraveNews = styled('div') <{}>`
|
||||
[data-show-news-prompt] & {
|
||||
bottom: 120px;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${spacing.l};
|
||||
`
|
||||
|
||||
export const Footer = styled('footer') <{}>`
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
import Flex from '$web-common/Flex';
|
||||
import Dropdown from '@brave/leo/react/dropdown';
|
||||
import Icon from '@brave/leo/react/icon';
|
||||
import Input from '@brave/leo/react/input';
|
||||
import { icon, spacing } from '@brave/leo/tokens/css';
|
||||
import { stringToMojoString16 } from 'chrome://resources/js/mojo_type_util.js';
|
||||
import { AutocompleteResult, OmniboxPopupSelection, PageHandler, PageHandlerRemote, PageInterface, PageReceiver } from 'gen/components/omnibox/browser/omnibox.mojom.m';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface SearchEngine {
|
||||
name: string,
|
||||
icon: string
|
||||
}
|
||||
const searchEngines: SearchEngine[] = [{
|
||||
name: 'Brave',
|
||||
icon: 'brave-icon-search-color'
|
||||
}, {
|
||||
name: 'Google',
|
||||
icon: 'google-color'
|
||||
}, {
|
||||
name: 'DuckDuckGo',
|
||||
icon: 'duckduckgo-color'
|
||||
}, {
|
||||
name: 'Bing',
|
||||
icon: 'bing-color'
|
||||
}]
|
||||
|
||||
const SearchInput = styled(Input)`
|
||||
--leo-control-padding: 6px;
|
||||
|
||||
display: inline-block;
|
||||
width: 540px;
|
||||
`
|
||||
|
||||
const EnginePicker = styled(Dropdown)`
|
||||
`
|
||||
|
||||
const SelectedIcon = styled(Icon)`
|
||||
--leo-icon-size: ${icon.l};
|
||||
`
|
||||
|
||||
const SearchIconContainer = styled.div`
|
||||
padding-right: ${spacing.m};
|
||||
`
|
||||
|
||||
const Option = styled.div`
|
||||
display: flex;
|
||||
gap: ${spacing.m};
|
||||
`
|
||||
|
||||
export const omniboxController: PageHandlerRemote = PageHandler.getRemote();
|
||||
(window as any).omnibox = omniboxController;
|
||||
|
||||
class SearchPage implements PageInterface {
|
||||
private receiver = new PageReceiver(this)
|
||||
private result: AutocompleteResult | undefined
|
||||
private resultListeners: Array<(result?: AutocompleteResult) => void> = []
|
||||
private selectionListeners: Array<(selection: OmniboxPopupSelection) => void> = []
|
||||
|
||||
constructor() {
|
||||
omniboxController.setPage(this.receiver.$.bindNewPipeAndPassRemote())
|
||||
}
|
||||
|
||||
addResultListener(listener: (result?: AutocompleteResult) => void) {
|
||||
this.resultListeners.push(listener)
|
||||
if (this.result) listener(this.result)
|
||||
}
|
||||
|
||||
removeResultListener(listener: (result?: AutocompleteResult) => void) {
|
||||
this.resultListeners = this.resultListeners.filter(r => r !== listener)
|
||||
}
|
||||
|
||||
addSelectionListener(listener: (selection: OmniboxPopupSelection) => void) {
|
||||
this.selectionListeners.push(listener)
|
||||
}
|
||||
|
||||
removeSelectionListener(listener: (selection: OmniboxPopupSelection) => void) {
|
||||
this.selectionListeners = this.selectionListeners.filter(s => s !== listener)
|
||||
}
|
||||
|
||||
autocompleteResultChanged(result: AutocompleteResult) {
|
||||
this.result = result;
|
||||
for (const listener of this.resultListeners) listener(result)
|
||||
}
|
||||
|
||||
updateSelection(selection: OmniboxPopupSelection) {
|
||||
for (const listener of this.selectionListeners) listener(selection)
|
||||
}
|
||||
}
|
||||
|
||||
export const search = new SearchPage()
|
||||
|
||||
export default function SearchBox() {
|
||||
const [query, setQuery] = React.useState('')
|
||||
const [searchEngine, setSearchEngine] = React.useState('Brave')
|
||||
|
||||
React.useEffect(() => {
|
||||
omniboxController.queryAutocomplete(stringToMojoString16(query), false);
|
||||
}, [query])
|
||||
|
||||
const searchInput = React.useRef<HTMLElement>()
|
||||
return <SearchInput tabIndex={0} type="text" ref={searchInput} value={query} onInput={e => setQuery(e.detail.value)} placeholder="Search the web privately">
|
||||
<Flex slot="left-icon">
|
||||
<EnginePicker positionStrategy='fixed' value={searchEngine} onChange={e => setSearchEngine(e.detail.value)}>
|
||||
<div slot="value">
|
||||
<SelectedIcon name={searchEngines.find(s => s.name === searchEngine)?.icon} />
|
||||
</div>
|
||||
{searchEngines.map(s => <leo-option value={s.name} key={s.name}>
|
||||
<Option>
|
||||
<Icon name={s.icon} />{s.name}
|
||||
</Option>
|
||||
</leo-option>)}
|
||||
</EnginePicker>
|
||||
</Flex>
|
||||
<SearchIconContainer slot="right-icon">
|
||||
<Icon name="search" />
|
||||
</SearchIconContainer>
|
||||
</SearchInput>
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
import * as React from 'react';
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import SearchBox from './SearchBox';
|
||||
import SearchResults from './SearchResults';
|
||||
import { spacing } from '@brave/leo/tokens/css';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void
|
||||
offsetY: number
|
||||
}
|
||||
|
||||
const duration = '0.12s'
|
||||
|
||||
const enterDialog = keyframes`
|
||||
from {
|
||||
transform: translateY(calc(var(--offset-y) - var(--margin-top)));
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
`
|
||||
|
||||
const exitDialog = keyframes`
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(calc(var(--offset-y) - var(--margin-top)));
|
||||
}
|
||||
`
|
||||
|
||||
const enterBackdrop = keyframes`
|
||||
from {
|
||||
background: transparent;
|
||||
backdrop-filter: blur(0);
|
||||
}
|
||||
|
||||
to {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(64px);
|
||||
}
|
||||
`
|
||||
|
||||
const exitBackdrop = keyframes`
|
||||
from {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(64px);
|
||||
}
|
||||
|
||||
to {
|
||||
background: transparent;
|
||||
backdrop-filter: blur(0);
|
||||
}
|
||||
`
|
||||
|
||||
const Dialog = styled.dialog<{ offsetY: number }>`
|
||||
--margin-top: ${spacing['9Xl']};
|
||||
--offset-y: ${p => p.offsetY}px;
|
||||
|
||||
outline: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
margin-top: var(--margin-top);
|
||||
padding: 2px;
|
||||
|
||||
animation: ${enterDialog} ${duration} ease-in-out;
|
||||
|
||||
&::backdrop {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(64px);
|
||||
animation: ${enterBackdrop} ${duration} ease-in-out;
|
||||
}
|
||||
|
||||
&.closing {
|
||||
animation: ${exitDialog} ${duration} ease-in-out;
|
||||
|
||||
&::backdrop {
|
||||
animation: ${exitBackdrop} ${duration} ease-in-out;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Component(props: Props) {
|
||||
const ref = React.useRef<HTMLDialogElement>(null)
|
||||
|
||||
React.useEffect(() => {
|
||||
ref.current?.showModal()
|
||||
}, [])
|
||||
|
||||
const doClose = React.useCallback(() => {
|
||||
const el = ref.current!
|
||||
el.addEventListener('animationend', () => props.onClose());
|
||||
el.classList.add('closing');
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
const el = ref.current
|
||||
if (!el) return
|
||||
|
||||
const closeHandler = (e: CloseEvent) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
el.addEventListener('close', closeHandler)
|
||||
el.addEventListener('cancel', e => e.preventDefault())
|
||||
return () => {
|
||||
el.removeEventListener('close', closeHandler)
|
||||
el.removeEventListener('cancel', closeHandler)
|
||||
}
|
||||
}, [props.onClose])
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
(ref.current?.querySelector('leo-input') as HTMLElement)?.focus()
|
||||
})
|
||||
}, [])
|
||||
return createPortal(<Dialog ref={ref} offsetY={props.offsetY} onClick={e => {
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
|
||||
const clickedInDialog = (
|
||||
rect.top <= e.clientY &&
|
||||
e.clientY <= rect.top + rect.height &&
|
||||
rect.left <= e.clientX &&
|
||||
e.clientX <= rect.left + rect.width
|
||||
);
|
||||
if (!clickedInDialog) {
|
||||
doClose()
|
||||
}
|
||||
}}>
|
||||
<SearchBox />
|
||||
<SearchResults />
|
||||
</Dialog>, document.body)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
import * as React from 'react';
|
||||
import SearchBox from './SearchBox';
|
||||
import SearchDialog from './SearchDialog';
|
||||
|
||||
export default function SearchPlaceholder() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [boxPos, setBoxPos] = React.useState(0)
|
||||
return <>
|
||||
{!open && <div onClick={e => {
|
||||
setOpen(true)
|
||||
setBoxPos(e.currentTarget.getBoundingClientRect().y)
|
||||
}}>
|
||||
<SearchBox />
|
||||
</div>}
|
||||
{open && <SearchDialog offsetY={boxPos} onClose={() => setOpen(false)} />}
|
||||
</>
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
import { color, font, radius, spacing } from '@brave/leo/tokens/css';
|
||||
import { mojoString16ToString } from 'chrome://resources/js/mojo_type_util.js';
|
||||
import { AutocompleteMatch } from 'gen/components/omnibox/browser/omnibox.mojom.m';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Flex from '../../../common/Flex';
|
||||
import { omniboxController } from './SearchBox';
|
||||
|
||||
interface Props {
|
||||
match: AutocompleteMatch
|
||||
line: number
|
||||
selected: boolean
|
||||
}
|
||||
|
||||
const Container = styled.a`
|
||||
padding: ${spacing.s} ${spacing['2Xl']};
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: ${spacing.l};
|
||||
|
||||
text-decoration: none;
|
||||
|
||||
&[aria-selected=true] {
|
||||
background: ${color.container.interactive};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ${color.container.highlight};
|
||||
}
|
||||
`
|
||||
|
||||
const IconContainer = styled.div`
|
||||
border-radius: ${radius.m};
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
background: ${color.container.highlight};
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
flex-shrink: 0;
|
||||
`
|
||||
|
||||
const FavIcon = styled.img`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`
|
||||
|
||||
const Content = styled.span`
|
||||
font: ${font.large.regular};
|
||||
color: ${color.text.secondary};
|
||||
`
|
||||
|
||||
const Description = styled.span`
|
||||
font: ${font.small.regular};
|
||||
color: ${color.text.tertiary};
|
||||
`
|
||||
|
||||
const Hint = styled.span`
|
||||
color: ${color.text.interactive};
|
||||
`
|
||||
|
||||
export default function SearchResult({ match, line, selected }: Props) {
|
||||
const contents = mojoString16ToString(match.swapContentsAndDescription ? match.description : match.contents)
|
||||
const description = mojoString16ToString(match.swapContentsAndDescription ? match.contents : match.description)
|
||||
|
||||
const hint = description && match.destinationUrl.url
|
||||
? description
|
||||
: ''
|
||||
|
||||
const subtitle = match.destinationUrl.url || description
|
||||
return <Container href={match.destinationUrl.url} aria-selected={selected} onClick={e => {
|
||||
e.preventDefault()
|
||||
omniboxController.openAutocompleteMatch(line, match.destinationUrl, true, e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey)
|
||||
}}>
|
||||
<IconContainer>
|
||||
<FavIcon src={match.iconUrl} />
|
||||
</IconContainer>
|
||||
<Flex direction='column'>
|
||||
<Content>{contents}<Hint>{hint ? ` - ${hint}` : ''}</Hint></Content>
|
||||
<Description>{subtitle}</Description>
|
||||
</Flex>
|
||||
</Container>
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2024 The Brave Authors. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
import { color, radius, spacing } from '@brave/leo/tokens/css'
|
||||
import { AutocompleteResult, OmniboxPopupSelection } from 'gen/components/omnibox/browser/omnibox.mojom.m'
|
||||
import * as React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { omniboxController, search } from './SearchBox'
|
||||
import SearchResult from './SearchResult'
|
||||
|
||||
const Container = styled.div`
|
||||
margin-top: ${spacing.m};
|
||||
background: ${color.container.background};
|
||||
border-radius: ${radius.m};
|
||||
padding: ${spacing.m} 0;
|
||||
gap: ${spacing.m};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 540px;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
`
|
||||
|
||||
export default function SearchResults() {
|
||||
const [result, setResult] = React.useState<AutocompleteResult>()
|
||||
const [selectedMatch, setSelectedMatch] = React.useState<number>();
|
||||
|
||||
React.useEffect(() => {
|
||||
const listener = (result?: AutocompleteResult) => {
|
||||
setResult(result)
|
||||
setSelectedMatch(prev => {
|
||||
if (!result) return undefined
|
||||
|
||||
if (!prev) {
|
||||
const defaultMatchIndex = result.matches.findIndex(r => r.allowedToBeDefaultMatch)
|
||||
if (defaultMatchIndex !== -1) return defaultMatchIndex
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (prev >= result.matches.length) {
|
||||
return result.matches.length - 1
|
||||
}
|
||||
|
||||
return prev
|
||||
})
|
||||
}
|
||||
search.addResultListener(listener)
|
||||
return () => search.removeResultListener(listener)
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
const listener = (selection: OmniboxPopupSelection) => {
|
||||
console.log("Set selection:", selection)
|
||||
setSelectedMatch(selection.line)
|
||||
}
|
||||
search.addSelectionListener(listener)
|
||||
return () => {
|
||||
search.removeSelectionListener(listener)
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
const keyHandler = (e: KeyboardEvent) => {
|
||||
const handledKeys = ['ArrowUp', 'ArrowDown']
|
||||
if (!handledKeys.includes(e.key)) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
const direction = e.key === 'ArrowUp' ? -1 : 1
|
||||
setSelectedMatch(s => {
|
||||
if (!result || result.matches.length === 0) return undefined
|
||||
|
||||
const start = s ?? -1
|
||||
const next = start + direction
|
||||
if (next < 0) return (result.matches.length - 1)
|
||||
if (next >= result.matches.length) return 0
|
||||
return next
|
||||
})
|
||||
}
|
||||
document.addEventListener('keydown', keyHandler)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyHandler)
|
||||
}
|
||||
|
||||
}, [result])
|
||||
|
||||
React.useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key !== 'Enter') return
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
const match = result?.matches[selectedMatch!]
|
||||
if (!match) return
|
||||
|
||||
omniboxController.openAutocompleteMatch(selectedMatch!, match.destinationUrl, true, 0, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey);
|
||||
}
|
||||
document.addEventListener('keydown', handler)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handler)
|
||||
}
|
||||
}, [result, selectedMatch])
|
||||
return result && result?.matches.length ? <Container>
|
||||
{result?.matches.map((r, i) => <SearchResult key={i} selected={i === selectedMatch} line={i} match={r} />)}
|
||||
</Container> : null
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import Stats from './stats'
|
||||
// Helpers
|
||||
import { getLocale } from '$web-common/locale'
|
||||
import VisibilityTimer from '$web-common/visibilityTimer'
|
||||
import { loadTimeData } from '$web-common/loadTimeData'
|
||||
import isReadableOnBackground from '../../helpers/colorUtil'
|
||||
|
||||
// Types
|
||||
@@ -46,6 +47,7 @@ import * as style from './style'
|
||||
import { defaultState } from '../../storage/new_tab_storage'
|
||||
|
||||
const BraveNewsPeek = React.lazy(() => import('../../../brave_news/browser/resources/Peek'))
|
||||
const SearchWidget = React.lazy(() => import('../../components/search/SearchPlaceholder'))
|
||||
|
||||
interface Props {
|
||||
newTabData: NewTab.State
|
||||
@@ -702,6 +704,10 @@ class NewTabPage extends React.Component<Props, State> {
|
||||
</Page.Footer>
|
||||
{newTabData.showToday &&
|
||||
<Page.GridItemNavigationBraveNews>
|
||||
{loadTimeData.getBoolean('featureFlagSearchWidget')
|
||||
&& <React.Suspense fallback={null}>
|
||||
<SearchWidget />
|
||||
</React.Suspense>}
|
||||
{defaultState.featureFlagBraveNewsFeedV2Enabled
|
||||
? <React.Suspense fallback={null}>
|
||||
<BraveNewsPeek/>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/* Copyright (c) 2019 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/components/omnibox/browser/brave_omnibox_client.h"
|
||||
|
||||
bool BraveOmniboxClient::IsAutocompleteEnabled() const {
|
||||
return true;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/* Copyright (c) 2019 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_OMNIBOX_BROWSER_BRAVE_OMNIBOX_CLIENT_H_
|
||||
#define BRAVE_COMPONENTS_OMNIBOX_BROWSER_BRAVE_OMNIBOX_CLIENT_H_
|
||||
|
||||
#include "components/omnibox/browser/omnibox_client.h"
|
||||
|
||||
class BraveOmniboxClient : public OmniboxClient {
|
||||
public:
|
||||
virtual bool IsAutocompleteEnabled() const;
|
||||
|
||||
protected:
|
||||
~BraveOmniboxClient() override {}
|
||||
};
|
||||
|
||||
#endif // BRAVE_COMPONENTS_OMNIBOX_BROWSER_BRAVE_OMNIBOX_CLIENT_H_
|
||||
@@ -15,8 +15,6 @@ brave_components_omnibox_browser_sources = [
|
||||
"//brave/components/omnibox/browser/brave_history_url_provider.h",
|
||||
"//brave/components/omnibox/browser/brave_local_history_zero_suggest_provider.cc",
|
||||
"//brave/components/omnibox/browser/brave_local_history_zero_suggest_provider.h",
|
||||
"//brave/components/omnibox/browser/brave_omnibox_client.cc",
|
||||
"//brave/components/omnibox/browser/brave_omnibox_client.h",
|
||||
"//brave/components/omnibox/browser/brave_omnibox_prefs.cc",
|
||||
"//brave/components/omnibox/browser/brave_omnibox_prefs.h",
|
||||
"//brave/components/omnibox/browser/brave_search_provider.cc",
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_client.h b/chrome/browser/ui/omnibox/chrome_omnibox_client.h
|
||||
index db1e94ddecdefe05ef45d090722eeee3e5eb0105..d79e108c47a13155f0f0e558564d1baf29b7606c 100644
|
||||
index db1e94ddecdef..4ad6d835f8d50 100644
|
||||
--- a/chrome/browser/ui/omnibox/chrome_omnibox_client.h
|
||||
+++ b/chrome/browser/ui/omnibox/chrome_omnibox_client.h
|
||||
@@ -17,13 +17,14 @@
|
||||
#include "components/omnibox/browser/favicon_cache.h"
|
||||
#include "components/omnibox/browser/omnibox.mojom-shared.h"
|
||||
#include "components/omnibox/browser/omnibox_client.h"
|
||||
+#include "brave/components/omnibox/browser/brave_omnibox_client.h"
|
||||
|
||||
class Browser;
|
||||
class GURL;
|
||||
@@ -23,7 +23,7 @@ class GURL;
|
||||
class LocationBar;
|
||||
class Profile;
|
||||
|
||||
-class ChromeOmniboxClient final : public OmniboxClient {
|
||||
+class ChromeOmniboxClient : public BraveOmniboxClient {
|
||||
+class ChromeOmniboxClient : public OmniboxClient {
|
||||
public:
|
||||
ChromeOmniboxClient(LocationBar* location_bar,
|
||||
Browser* browser,
|
||||
|
||||
@@ -10,3 +10,11 @@ index feb42d8d0a7f231d162085f0076092f0a022157a..c38fe2e842f9606aa0216651334ee6a7
|
||||
|
||||
configs += [ "//build/config/compiler:wexit_time_destructors" ]
|
||||
|
||||
@@ -929,6 +930,7 @@ fuzzer_test("search_suggestion_parser_fuzzer") {
|
||||
|
||||
mojom("mojo_bindings") {
|
||||
sources = [ "omnibox.mojom" ]
|
||||
+ generate_legacy_js_bindings = true
|
||||
webui_module_path = "/"
|
||||
|
||||
public_deps = [
|
||||
|
||||
@@ -153,9 +153,11 @@ leo_icons = [
|
||||
"autoplay-on.svg",
|
||||
"backward.svg",
|
||||
"bar-chart.svg",
|
||||
"bing-color.svg",
|
||||
"bluetooth-off.svg",
|
||||
"bluetooth.svg",
|
||||
"brave-icon-release-color.svg",
|
||||
"brave-icon-search-color.svg",
|
||||
"browser-extensions.svg",
|
||||
"browser-ntp-widget.svg",
|
||||
"carat-down.svg",
|
||||
@@ -189,6 +191,7 @@ leo_icons = [
|
||||
"discover.svg",
|
||||
"download-off.svg",
|
||||
"download.svg",
|
||||
"duckduckgo-color.svg",
|
||||
"edit-box.svg",
|
||||
"edit-pencil.svg",
|
||||
"erase.svg",
|
||||
@@ -208,6 +211,7 @@ leo_icons = [
|
||||
"font-size.svg",
|
||||
"forward-15.svg",
|
||||
"fullscreen-on.svg",
|
||||
"google-color.svg",
|
||||
"graph.svg",
|
||||
"grid04.svg",
|
||||
"hamburger-menu.svg",
|
||||
|
||||
Reference in New Issue
Block a user