Bug 1482205 - Add top search, fixed search and bug fixes to Activity Stream r=k88hudson

Differential Revision: https://phabricator.services.mozilla.com/D3047

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Ed Lee 2018-08-09 21:45:06 +00:00
Родитель 0273f8788d
Коммит bd78b91e1f
241 изменённых файлов: 1612 добавлений и 492 удалений

Просмотреть файл

@ -39,6 +39,7 @@ for (const type of [
"DIALOG_OPEN",
"DISABLE_ONBOARDING",
"DOWNLOAD_CHANGED",
"FILL_SEARCH_TERM",
"INIT",
"MIGRATION_CANCEL",
"MIGRATION_COMPLETED",

Просмотреть файл

@ -24,6 +24,20 @@ function addLocaleDataForReactIntl(locale) {
addLocaleData([{locale, parentLocale: "en"}]);
}
// Returns a function will not be continuously triggered when called. The
// function will be triggered if called again after `wait` milliseconds.
function debounce(func, wait) {
let timer;
return (...args) => {
if (timer) { return; }
let wakeUp = () => { timer = null; };
timer = setTimeout(wakeUp, wait);
func.apply(this, args);
};
}
export class _Base extends React.PureComponent {
componentWillMount() {
const {App, locale} = this.props;
@ -48,9 +62,29 @@ export class _Base extends React.PureComponent {
this.updateTheme();
}
componentWillUpdate({App}) {
hasTopStoriesSectionChanged(nextProps) {
const nPropsSections = nextProps.Sections.find(section => section.id === "topstories");
const tPropsSections = this.props.Sections.find(section => section.id === "topstories");
if (nPropsSections && nPropsSections.options) {
if (!tPropsSections || !tPropsSections.options) {
return true;
}
if (nPropsSections.options.show_spocs !== tPropsSections.options.show_spocs) {
return true;
}
if (nPropsSections.options.stories_endpoint !== tPropsSections.options.stories_endpoint) {
return true;
}
}
return false;
}
componentWillUpdate(nextProps) {
this.updateTheme();
this.sendNewTabRehydrated(App);
if (this.hasTopStoriesSectionChanged(nextProps)) {
this.renderNotified = false;
}
this.sendNewTabRehydrated(nextProps.App);
}
updateTheme() {
@ -106,6 +140,25 @@ export class BaseContent extends React.PureComponent {
constructor(props) {
super(props);
this.openPreferences = this.openPreferences.bind(this);
this.onWindowScroll = debounce(this.onWindowScroll.bind(this), 5);
this.state = {fixedSearch: false};
}
componentDidMount() {
global.addEventListener("scroll", this.onWindowScroll);
}
componentWillUnmount() {
global.removeEventListener("scroll", this.onWindowScroll);
}
onWindowScroll() {
const SCROLL_THRESHOLD = 34;
if (global.scrollY > SCROLL_THRESHOLD && !this.state.fixedSearch) {
this.setState({fixedSearch: true});
} else if (global.scrollY <= SCROLL_THRESHOLD && this.state.fixedSearch) {
this.setState({fixedSearch: false});
}
}
openPreferences() {
@ -123,7 +176,8 @@ export class BaseContent extends React.PureComponent {
const outerClassName = [
"outer-wrapper",
shouldBeFixedToTop && "fixed-to-top"
shouldBeFixedToTop && "fixed-to-top",
prefs.showSearch && this.state.fixedSearch && "fixed-search"
].filter(v => v).join(" ");
return (
@ -154,4 +208,4 @@ export class BaseContent extends React.PureComponent {
}
}
export const Base = connect(state => ({App: state.App, Prefs: state.Prefs}))(_Base);
export const Base = connect(state => ({App: state.App, Prefs: state.Prefs, Sections: state.Sections}))(_Base);

Просмотреть файл

@ -8,7 +8,7 @@
inset-inline-start: 100%;
position: absolute;
top: ($context-menu-button-size / 4);
z-index: 10000;
z-index: 8;
> ul {
list-style: none;

Просмотреть файл

@ -13,7 +13,7 @@ export class _LinkMenu extends React.PureComponent {
const {site, index, source, isPrivateBrowsingEnabled, siteInfo, platform} = props;
// Handle special case of default site
const propOptions = !site.isDefault ? props.options : DEFAULT_SITE_MENU_OPTIONS;
const propOptions = (!site.isDefault || site.searchTopSite) ? props.options : DEFAULT_SITE_MENU_OPTIONS;
const options = propOptions.map(o => LinkMenuOptions[o](site, index, source, isPrivateBrowsingEnabled, siteInfo, platform)).map(option => {
const {action, impression, id, string_id, type, userEvent} = option;

Просмотреть файл

@ -64,23 +64,25 @@ export class _Search extends React.PureComponent {
*/
render() {
return (<div className="search-wrapper">
<label htmlFor="newtab-search-text" className="search-label">
<span className="sr-only"><FormattedMessage id="search_web_placeholder" /></span>
</label>
<input
id="newtab-search-text"
maxLength="256"
placeholder={this.props.intl.formatMessage({id: "search_web_placeholder"})}
ref={this.onInputMount}
title={this.props.intl.formatMessage({id: "search_web_placeholder"})}
type="search" />
<button
id="searchSubmit"
className="search-button"
onClick={this.onClick}
title={this.props.intl.formatMessage({id: "search_button"})}>
<span className="sr-only"><FormattedMessage id="search_button" /></span>
</button>
<div className="search-inner-wrapper">
<label htmlFor="newtab-search-text" className="search-label">
<span className="sr-only"><FormattedMessage id="search_web_placeholder" /></span>
</label>
<input
id="newtab-search-text"
maxLength="256"
placeholder={this.props.intl.formatMessage({id: "search_web_placeholder"})}
ref={this.onInputMount}
title={this.props.intl.formatMessage({id: "search_web_placeholder"})}
type="search" />
<button
id="searchSubmit"
className="search-button"
onClick={this.onClick}
title={this.props.intl.formatMessage({id: "search_button"})}>
<span className="sr-only"><FormattedMessage id="search_button" /></span>
</button>
</div>
</div>);
}
}

Просмотреть файл

@ -1,21 +1,38 @@
.search-wrapper {
$search-height: 35px;
$search-icon-size: 18px;
$search-icon-padding: 8px;
$search-icon-width: 2 * $search-icon-padding + $search-icon-size;
$search-input-left-label-width: 35px;
$search-button-width: 36px;
$search-height: 48px;
$search-icon-size: 24px;
$search-icon-padding: 12px;
$search-icon-width: 2 * $search-icon-padding + $search-icon-size -2;
$search-button-width: 48px;
$glyph-forward: url('chrome://browser/skin/forward.svg');
cursor: default;
display: flex;
height: $search-height;
margin-bottom: $section-spacing;
position: relative;
width: 100%;
background-color: var(--newtab-background-color);
padding: 34px 0 64px;
@media (max-height: 700px) {
& {
padding: 0 0 30px;
}
}
.search-inner-wrapper {
cursor: default;
display: flex;
height: $search-height;
position: relative;
width: 100%;
}
@media (min-width: $break-point-large) {
.search-inner-wrapper {
margin: 0 auto;
width: $max-searchbar-width;
}
}
input {
background: var(--newtab-textbox-background-color) var(--newtab-search-icon) $search-icon-padding center / $search-icon-size no-repeat;
background: var(--newtab-textbox-background-color) var(--newtab-search-icon) $search-icon-padding center no-repeat;
background-size: $search-icon-size;
border: solid 1px var(--newtab-search-border-color);
box-shadow: $shadow-secondary, 0 0 0 1px $black-15;
font-size: 15px;
@ -69,6 +86,44 @@
}
}
@media (min-height: 701px) {
.fixed-search {
main {
padding-top: 146px;
}
.search-wrapper {
$search-header-bar-height: 95px;
$search-height: 35px;
$search-icon-size: 16px;
$search-icon-padding: 16px;
background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color);
height: $search-header-bar-height;
left: 0;
padding: 30px 0;
position: fixed;
top: 0;
width: 100%;
z-index: 9;
.search-inner-wrapper {
height: $search-height;
}
input {
background-position-x: $search-icon-padding;
background-size: $search-icon-size;
&:dir(rtl) {
background-position-x: right $search-icon-padding;
}
}
}
}
}
@at-root {
// Adjust the style of the contentSearchUI-generated table
.contentSearchSuggestionTable {
@ -150,4 +205,10 @@
}
}
}
.contentSearchHeaderRow > td > img,
.contentSearchSuggestionRow > td > .historyIcon {
margin-inline-start: 7px;
margin-inline-end: 15px;
}
}

Просмотреть файл

@ -26,7 +26,8 @@ export class _StartupOverlay extends React.PureComponent {
if (this.props.fxa_endpoint && !this.didFetch) {
try {
this.didFetch = true;
const response = await fetch(`${this.props.fxa_endpoint}/metrics-flow`);
const response = await fetch(`${this.props.fxa_endpoint}/metrics-flow?entrypoint=
activity-stream-firstrun&utm_source=activity-stream&utm_campaign=firstrun&form_type=email`);
if (response.status === 200) {
const {flowId, flowBeginTime} = await response.json();
this.setState({flowId, flowBeginTime});

Просмотреть файл

@ -4,6 +4,7 @@ import {
MIN_CORNER_FAVICON_SIZE,
MIN_RICH_FAVICON_SIZE,
TOP_SITES_CONTEXT_MENU_OPTIONS,
TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS,
TOP_SITES_SOURCE
} from "./TopSitesConstants";
import {LinkMenu} from "content-src/components/LinkMenu/LinkMenu";
@ -120,6 +121,13 @@ export class TopSiteLink extends React.PureComponent {
let hasScreenshotImage = this.state.screenshotImage && this.state.screenshotImage.url;
if (defaultStyle) { // force no styles (letter fallback) even if the link has imagery
smallFaviconFallback = false;
} else if (link.searchTopSite) {
imageClassName = "top-site-icon rich-icon";
imageStyle = {
backgroundColor: link.backgroundColor,
backgroundImage: `url(${tippyTopIcon})`
};
smallFaviconStyle = {backgroundImage: `url(${tippyTopIcon})`};
} else if (link.customScreenshotURL) {
// assume high quality custom screenshot and use rich icon styles and class names
imageClassName = "top-site-icon rich-icon";
@ -164,6 +172,7 @@ export class TopSiteLink extends React.PureComponent {
<a href={link.url} onClick={onClick}>
<div className="tile" aria-hidden={true} data-fallback={letterFallback}>
<div className={imageClassName} style={imageStyle} />
{link.searchTopSite && <div className="top-site-icon search-topsite" />}
{showSmallFavicon && <div
className="top-site-icon default-icon"
data-fallback={smallFaviconFallback && letterFallback}
@ -203,6 +212,11 @@ export class TopSite extends React.PureComponent {
if (this.props.link.isPinned) {
value.card_type = "pinned";
}
if (this.props.link.searchTopSite) {
// Set the card_type as "search" regardless of its pinning status
value.card_type = "search";
value.search_vendor = this.props.link.hostname;
}
return {value};
}
@ -221,10 +235,17 @@ export class TopSite extends React.PureComponent {
// specified as a property on the link.
event.preventDefault();
const {altKey, button, ctrlKey, metaKey, shiftKey} = event;
this.props.dispatch(ac.OnlyToMain({
type: at.OPEN_LINK,
data: Object.assign(this.props.link, {event: {altKey, button, ctrlKey, metaKey, shiftKey}})
}));
if (!this.props.link.searchTopSite) {
this.props.dispatch(ac.OnlyToMain({
type: at.OPEN_LINK,
data: Object.assign(this.props.link, {event: {altKey, button, ctrlKey, metaKey, shiftKey}})
}));
} else {
this.props.dispatch(ac.OnlyToMain({
type: at.FILL_SEARCH_TERM,
data: {label: this.props.link.label}
}));
}
}
onMenuButtonClick(event) {
@ -254,7 +275,7 @@ export class TopSite extends React.PureComponent {
dispatch={props.dispatch}
index={props.index}
onUpdate={this.onMenuUpdate}
options={TOP_SITES_CONTEXT_MENU_OPTIONS}
options={link.searchTopSite ? TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS : TOP_SITES_CONTEXT_MENU_OPTIONS}
site={link}
siteInfo={this._getTelemetryInfo()}
source={TOP_SITES_SOURCE} />
@ -361,7 +382,9 @@ export class _TopSiteList extends React.PureComponent {
site: {
url: this.state.draggedSite.url,
label: this.state.draggedTitle,
customScreenshotURL: this.state.draggedSite.customScreenshotURL
customScreenshotURL: this.state.draggedSite.customScreenshotURL,
// Only if the search topsites experiment is enabled
...(this.state.draggedSite.searchTopSite && {searchTopSite: true})
},
index,
draggedFromIndex: this.state.draggedIndex

Просмотреть файл

@ -1,6 +1,8 @@
export const TOP_SITES_SOURCE = "TOP_SITES";
export const TOP_SITES_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "EditTopSite", "Separator",
"OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"];
// the special top site for search shortcut experiment can only have the option to unpin (which removes) the topsite
export const TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "BlockUrl"];
// minimum size necessary to show a rich icon instead of a screenshot
export const MIN_RICH_FAVICON_SIZE = 96;
// minimum size necessary to show any icon in the top left corner with a screenshot

Просмотреть файл

@ -178,7 +178,8 @@ $half-base-gutter: $base-gutter / 2;
width: 100%;
}
.default-icon { // sass-lint:disable block property-sort-order
.default-icon,
.search-topsite {
background-size: $default-icon-size;
bottom: -$default-icon-offset;
height: $default-icon-wrapper-size;
@ -196,6 +197,16 @@ $half-base-gutter: $base-gutter / 2;
}
}
.search-topsite {
background-image: url('#{$image-path}glyph-search-16.svg');
background-size: 26px;
background-color: $blue-60;
border-radius: 42px;
-moz-context-properties: fill;
fill: $white;
box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color), var(--newtab-card-shadow);
}
.title {
color: var(--newtab-topsites-label-color);
font: message-box;

Просмотреть файл

@ -149,12 +149,18 @@ export const LinkMenuOptions = {
data: {url: site.url}
})
}),
PinTopSite: (site, index) => ({
PinTopSite: ({url, searchTopSite, label}, index) => ({
id: "menu_action_pin",
icon: "pin",
action: ac.AlsoToMain({
type: at.TOP_SITES_PIN,
data: {site: {url: site.url}, index}
data: {
site: {
url,
...(searchTopSite && {searchTopSite, label})
},
index
}
}),
userEvent: "PIN"
}),

Просмотреть файл

@ -58,6 +58,7 @@ body {
--newtab-search-border-color: transparent;
--newtab-search-dropdown-color: $white;
--newtab-search-dropdown-header-color: $grey-10;
--newtab-search-header-background-color: $grey-10-95;
--newtab-search-icon-color: $grey-90-40;
// Top Sites
@ -114,6 +115,7 @@ body {
--newtab-search-border-color: $grey-10-20;
--newtab-search-dropdown-color: $grey-70;
--newtab-search-dropdown-header-color: $grey-60;
--newtab-search-header-background-color: $grey-80-95;
--newtab-search-icon-color: $grey-10-60;
// Top Sites

Просмотреть файл

@ -23,9 +23,11 @@ $grey-10-20: rgba($grey-10, 0.2);
$grey-10-40: rgba($grey-10, 0.4);
$grey-10-60: rgba($grey-10, 0.6);
$grey-10-80: rgba($grey-10, 0.8);
$grey-10-95: rgba($grey-10, 0.95);
$grey-20-60: rgba($grey-20, 0.6);
$grey-20-80: rgba($grey-20, 0.8);
$grey-30-60: rgba($grey-30, 0.6);
$grey-80-95: rgba($grey-80, 0.95);
$grey-90-10: rgba($grey-90, 0.1);
$grey-90-20: rgba($grey-90, 0.2);
$grey-90-30: rgba($grey-90, 0.3);
@ -89,6 +91,8 @@ $break-point-medium: $wrapper-max-width-medium + $base-gutter * 2 + $scrollbar-w
$break-point-large: $wrapper-max-width-large + $base-gutter * 2 + $scrollbar-width;
$break-point-widest: $wrapper-max-width-widest + $base-gutter * 2 + $scrollbar-width;
$max-searchbar-width: $grid-unit * 6 + $base-gutter * 5;
$section-title-font-size: 13px;
$card-width: $grid-unit * 2 + $base-gutter;

Просмотреть файл

@ -55,6 +55,7 @@ body {
--newtab-search-border-color: transparent;
--newtab-search-dropdown-color: #FFF;
--newtab-search-dropdown-header-color: #F9F9FA;
--newtab-search-header-background-color: rgba(249, 249, 250, 0.95);
--newtab-search-icon-color: rgba(12, 12, 13, 0.4);
--newtab-topsites-background-color: #FFF;
--newtab-topsites-icon-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color);
@ -96,6 +97,7 @@ body {
--newtab-search-border-color: rgba(249, 249, 250, 0.2);
--newtab-search-dropdown-color: #38383D;
--newtab-search-dropdown-header-color: #4A4A4F;
--newtab-search-header-background-color: rgba(42, 42, 46, 0.95);
--newtab-search-icon-color: rgba(249, 249, 250, 0.6);
--newtab-topsites-background-color: #38383D;
--newtab-topsites-icon-shadow: none;
@ -552,7 +554,8 @@ main {
inset-inline-start: 0;
top: 0;
width: 100%; }
.top-site-outer .default-icon {
.top-site-outer .default-icon,
.top-site-outer .search-topsite {
background-size: 32px;
bottom: -6px;
height: 42px;
@ -562,8 +565,17 @@ main {
display: flex;
font-size: 20px;
justify-content: center; }
.top-site-outer .default-icon[data-fallback]::before {
.top-site-outer .default-icon[data-fallback]::before,
.top-site-outer .search-topsite[data-fallback]::before {
content: attr(data-fallback); }
.top-site-outer .search-topsite {
background-image: url("../data/content/assets/glyph-search-16.svg");
background-size: 26px;
background-color: #0060DF;
border-radius: 42px;
-moz-context-properties: fill;
fill: #FFF;
box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color), var(--newtab-card-shadow); }
.top-site-outer .title {
color: var(--newtab-topsites-label-color);
font: message-box;
@ -1119,25 +1131,35 @@ a.firstrun-link {
display: table; }
.search-wrapper {
cursor: default;
display: flex;
height: 35px;
margin-bottom: 20px;
position: relative;
width: 100%; }
background-color: var(--newtab-background-color);
padding: 34px 0 64px; }
@media (max-height: 700px) {
.search-wrapper {
padding: 0 0 30px; } }
.search-wrapper .search-inner-wrapper {
cursor: default;
display: flex;
height: 48px;
position: relative;
width: 100%; }
@media (min-width: 866px) {
.search-wrapper .search-inner-wrapper {
margin: 0 auto;
width: 736px; } }
.search-wrapper input {
background: var(--newtab-textbox-background-color) var(--newtab-search-icon) 8px center/18px no-repeat;
background: var(--newtab-textbox-background-color) var(--newtab-search-icon) 12px center no-repeat;
background-size: 24px;
border: solid 1px var(--newtab-search-border-color);
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.15);
font-size: 15px;
-moz-context-properties: fill;
fill: var(--newtab-search-icon-color);
padding: 0;
padding-inline-end: 36px;
padding-inline-start: 34px;
padding-inline-end: 48px;
padding-inline-start: 46px;
width: 100%; }
.search-wrapper input:dir(rtl) {
background-position-x: right 8px; }
background-position-x: right 12px; }
.search-wrapper:hover input {
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.25); }
.search-wrapper:active input,
@ -1154,7 +1176,7 @@ a.firstrun-link {
height: 100%;
inset-inline-end: 0;
position: absolute;
width: 36px; }
width: 48px; }
.search-wrapper .search-button:focus, .search-wrapper .search-button:hover {
background-color: rgba(12, 12, 13, 0.1);
cursor: pointer; }
@ -1163,6 +1185,27 @@ a.firstrun-link {
.search-wrapper .search-button:dir(rtl) {
transform: scaleX(-1); }
@media (min-height: 701px) {
.fixed-search main {
padding-top: 146px; }
.fixed-search .search-wrapper {
background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color);
height: 95px;
left: 0;
padding: 30px 0;
position: fixed;
top: 0;
width: 100%;
z-index: 9; }
.fixed-search .search-wrapper .search-inner-wrapper {
height: 35px; }
.fixed-search .search-wrapper input {
background-position-x: 16px;
background-size: 16px; }
.fixed-search .search-wrapper input:dir(rtl) {
background-position-x: right 16px; } }
.contentSearchSuggestionTable {
background-color: var(--newtab-search-dropdown-color);
border: 0;
@ -1204,6 +1247,10 @@ a.firstrun-link {
background: var(--newtab-element-hover-color);
color: var(--newtab-text-primary-color); }
.contentSearchHeaderRow > td > img, .contentSearchSuggestionRow > td > .historyIcon {
margin-inline-start: 7px;
margin-inline-end: 15px; }
.context-menu {
background: var(--newtab-contextmenu-background-color);
border-radius: 5px;
@ -1214,7 +1261,7 @@ a.firstrun-link {
inset-inline-start: 100%;
position: absolute;
top: 6.75px;
z-index: 10000; }
z-index: 8; }
.context-menu > ul {
list-style: none;
margin: 0;

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -58,6 +58,7 @@ body {
--newtab-search-border-color: transparent;
--newtab-search-dropdown-color: #FFF;
--newtab-search-dropdown-header-color: #F9F9FA;
--newtab-search-header-background-color: rgba(249, 249, 250, 0.95);
--newtab-search-icon-color: rgba(12, 12, 13, 0.4);
--newtab-topsites-background-color: #FFF;
--newtab-topsites-icon-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color);
@ -99,6 +100,7 @@ body {
--newtab-search-border-color: rgba(249, 249, 250, 0.2);
--newtab-search-dropdown-color: #38383D;
--newtab-search-dropdown-header-color: #4A4A4F;
--newtab-search-header-background-color: rgba(42, 42, 46, 0.95);
--newtab-search-icon-color: rgba(249, 249, 250, 0.6);
--newtab-topsites-background-color: #38383D;
--newtab-topsites-icon-shadow: none;
@ -555,7 +557,8 @@ main {
inset-inline-start: 0;
top: 0;
width: 100%; }
.top-site-outer .default-icon {
.top-site-outer .default-icon,
.top-site-outer .search-topsite {
background-size: 32px;
bottom: -6px;
height: 42px;
@ -565,8 +568,17 @@ main {
display: flex;
font-size: 20px;
justify-content: center; }
.top-site-outer .default-icon[data-fallback]::before {
.top-site-outer .default-icon[data-fallback]::before,
.top-site-outer .search-topsite[data-fallback]::before {
content: attr(data-fallback); }
.top-site-outer .search-topsite {
background-image: url("../data/content/assets/glyph-search-16.svg");
background-size: 26px;
background-color: #0060DF;
border-radius: 42px;
-moz-context-properties: fill;
fill: #FFF;
box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color), var(--newtab-card-shadow); }
.top-site-outer .title {
color: var(--newtab-topsites-label-color);
font: message-box;
@ -1122,25 +1134,35 @@ a.firstrun-link {
display: table; }
.search-wrapper {
cursor: default;
display: flex;
height: 35px;
margin-bottom: 20px;
position: relative;
width: 100%; }
background-color: var(--newtab-background-color);
padding: 34px 0 64px; }
@media (max-height: 700px) {
.search-wrapper {
padding: 0 0 30px; } }
.search-wrapper .search-inner-wrapper {
cursor: default;
display: flex;
height: 48px;
position: relative;
width: 100%; }
@media (min-width: 866px) {
.search-wrapper .search-inner-wrapper {
margin: 0 auto;
width: 736px; } }
.search-wrapper input {
background: var(--newtab-textbox-background-color) var(--newtab-search-icon) 8px center/18px no-repeat;
background: var(--newtab-textbox-background-color) var(--newtab-search-icon) 12px center no-repeat;
background-size: 24px;
border: solid 1px var(--newtab-search-border-color);
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.15);
font-size: 15px;
-moz-context-properties: fill;
fill: var(--newtab-search-icon-color);
padding: 0;
padding-inline-end: 36px;
padding-inline-start: 34px;
padding-inline-end: 48px;
padding-inline-start: 46px;
width: 100%; }
.search-wrapper input:dir(rtl) {
background-position-x: right 8px; }
background-position-x: right 12px; }
.search-wrapper:hover input {
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.25); }
.search-wrapper:active input,
@ -1157,7 +1179,7 @@ a.firstrun-link {
height: 100%;
inset-inline-end: 0;
position: absolute;
width: 36px; }
width: 48px; }
.search-wrapper .search-button:focus, .search-wrapper .search-button:hover {
background-color: rgba(12, 12, 13, 0.1);
cursor: pointer; }
@ -1166,6 +1188,27 @@ a.firstrun-link {
.search-wrapper .search-button:dir(rtl) {
transform: scaleX(-1); }
@media (min-height: 701px) {
.fixed-search main {
padding-top: 146px; }
.fixed-search .search-wrapper {
background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color);
height: 95px;
left: 0;
padding: 30px 0;
position: fixed;
top: 0;
width: 100%;
z-index: 9; }
.fixed-search .search-wrapper .search-inner-wrapper {
height: 35px; }
.fixed-search .search-wrapper input {
background-position-x: 16px;
background-size: 16px; }
.fixed-search .search-wrapper input:dir(rtl) {
background-position-x: right 16px; } }
.contentSearchSuggestionTable {
background-color: var(--newtab-search-dropdown-color);
border: 0;
@ -1207,6 +1250,10 @@ a.firstrun-link {
background: var(--newtab-element-hover-color);
color: var(--newtab-text-primary-color); }
.contentSearchHeaderRow > td > img, .contentSearchSuggestionRow > td > .historyIcon {
margin-inline-start: 7px;
margin-inline-end: 15px; }
.context-menu {
background: var(--newtab-contextmenu-background-color);
border-radius: 5px;
@ -1217,7 +1264,7 @@ a.firstrun-link {
inset-inline-start: 100%;
position: absolute;
top: 6.75px;
z-index: 10000; }
z-index: 8; }
.context-menu > ul {
list-style: none;
margin: 0;

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -55,6 +55,7 @@ body {
--newtab-search-border-color: transparent;
--newtab-search-dropdown-color: #FFF;
--newtab-search-dropdown-header-color: #F9F9FA;
--newtab-search-header-background-color: rgba(249, 249, 250, 0.95);
--newtab-search-icon-color: rgba(12, 12, 13, 0.4);
--newtab-topsites-background-color: #FFF;
--newtab-topsites-icon-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color);
@ -96,6 +97,7 @@ body {
--newtab-search-border-color: rgba(249, 249, 250, 0.2);
--newtab-search-dropdown-color: #38383D;
--newtab-search-dropdown-header-color: #4A4A4F;
--newtab-search-header-background-color: rgba(42, 42, 46, 0.95);
--newtab-search-icon-color: rgba(249, 249, 250, 0.6);
--newtab-topsites-background-color: #38383D;
--newtab-topsites-icon-shadow: none;
@ -552,7 +554,8 @@ main {
inset-inline-start: 0;
top: 0;
width: 100%; }
.top-site-outer .default-icon {
.top-site-outer .default-icon,
.top-site-outer .search-topsite {
background-size: 32px;
bottom: -6px;
height: 42px;
@ -562,8 +565,17 @@ main {
display: flex;
font-size: 20px;
justify-content: center; }
.top-site-outer .default-icon[data-fallback]::before {
.top-site-outer .default-icon[data-fallback]::before,
.top-site-outer .search-topsite[data-fallback]::before {
content: attr(data-fallback); }
.top-site-outer .search-topsite {
background-image: url("../data/content/assets/glyph-search-16.svg");
background-size: 26px;
background-color: #0060DF;
border-radius: 42px;
-moz-context-properties: fill;
fill: #FFF;
box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color), var(--newtab-card-shadow); }
.top-site-outer .title {
color: var(--newtab-topsites-label-color);
font: message-box;
@ -1119,25 +1131,35 @@ a.firstrun-link {
display: table; }
.search-wrapper {
cursor: default;
display: flex;
height: 35px;
margin-bottom: 20px;
position: relative;
width: 100%; }
background-color: var(--newtab-background-color);
padding: 34px 0 64px; }
@media (max-height: 700px) {
.search-wrapper {
padding: 0 0 30px; } }
.search-wrapper .search-inner-wrapper {
cursor: default;
display: flex;
height: 48px;
position: relative;
width: 100%; }
@media (min-width: 866px) {
.search-wrapper .search-inner-wrapper {
margin: 0 auto;
width: 736px; } }
.search-wrapper input {
background: var(--newtab-textbox-background-color) var(--newtab-search-icon) 8px center/18px no-repeat;
background: var(--newtab-textbox-background-color) var(--newtab-search-icon) 12px center no-repeat;
background-size: 24px;
border: solid 1px var(--newtab-search-border-color);
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.15);
font-size: 15px;
-moz-context-properties: fill;
fill: var(--newtab-search-icon-color);
padding: 0;
padding-inline-end: 36px;
padding-inline-start: 34px;
padding-inline-end: 48px;
padding-inline-start: 46px;
width: 100%; }
.search-wrapper input:dir(rtl) {
background-position-x: right 8px; }
background-position-x: right 12px; }
.search-wrapper:hover input {
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.25); }
.search-wrapper:active input,
@ -1154,7 +1176,7 @@ a.firstrun-link {
height: 100%;
inset-inline-end: 0;
position: absolute;
width: 36px; }
width: 48px; }
.search-wrapper .search-button:focus, .search-wrapper .search-button:hover {
background-color: rgba(12, 12, 13, 0.1);
cursor: pointer; }
@ -1163,6 +1185,27 @@ a.firstrun-link {
.search-wrapper .search-button:dir(rtl) {
transform: scaleX(-1); }
@media (min-height: 701px) {
.fixed-search main {
padding-top: 146px; }
.fixed-search .search-wrapper {
background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color);
height: 95px;
left: 0;
padding: 30px 0;
position: fixed;
top: 0;
width: 100%;
z-index: 9; }
.fixed-search .search-wrapper .search-inner-wrapper {
height: 35px; }
.fixed-search .search-wrapper input {
background-position-x: 16px;
background-size: 16px; }
.fixed-search .search-wrapper input:dir(rtl) {
background-position-x: right 16px; } }
.contentSearchSuggestionTable {
background-color: var(--newtab-search-dropdown-color);
border: 0;
@ -1204,6 +1247,10 @@ a.firstrun-link {
background: var(--newtab-element-hover-color);
color: var(--newtab-text-primary-color); }
.contentSearchHeaderRow > td > img, .contentSearchSuggestionRow > td > .historyIcon {
margin-inline-start: 7px;
margin-inline-end: 15px; }
.context-menu {
background: var(--newtab-contextmenu-background-color);
border-radius: 5px;
@ -1214,7 +1261,7 @@ a.firstrun-link {
inset-inline-start: 100%;
position: absolute;
top: 6.75px;
z-index: 10000; }
z-index: 8; }
.context-menu > ul {
list-style: none;
margin: 0;

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -205,7 +205,7 @@ const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS :
// }
const actionTypes = {};
for (const type of ["ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISABLE_ONBOARDING", "DOWNLOAD_CHANGED", "INIT", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_DOWNLOAD_FILE", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "OPEN_WEBEXT_SETTINGS", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_CHANGED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PLACES_SAVED_TO_POCKET", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "PREVIEW_REQUEST", "PREVIEW_REQUEST_CANCEL", "PREVIEW_RESPONSE", "REMOVE_DOWNLOAD_FILE", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_MOVE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_DOWNLOAD_FILE", "SHOW_FIREFOX_ACCOUNTS", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "TOTAL_BOOKMARKS_REQUEST", "TOTAL_BOOKMARKS_RESPONSE", "UNINIT", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
for (const type of ["ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISABLE_ONBOARDING", "DOWNLOAD_CHANGED", "FILL_SEARCH_TERM", "INIT", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_DOWNLOAD_FILE", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "OPEN_WEBEXT_SETTINGS", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_CHANGED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PLACES_SAVED_TO_POCKET", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "PREVIEW_REQUEST", "PREVIEW_REQUEST_CANCEL", "PREVIEW_RESPONSE", "REMOVE_DOWNLOAD_FILE", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_MOVE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_DOWNLOAD_FILE", "SHOW_FIREFOX_ACCOUNTS", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "TOTAL_BOOKMARKS_REQUEST", "TOTAL_BOOKMARKS_RESPONSE", "UNINIT", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
actionTypes[type] = type;
}
@ -1538,6 +1538,24 @@ function addLocaleDataForReactIntl(locale) {
Object(react_intl__WEBPACK_IMPORTED_MODULE_1__["addLocaleData"])([{ locale, parentLocale: "en" }]);
}
// Returns a function will not be continuously triggered when called. The
// function will be triggered if called again after `wait` milliseconds.
function debounce(func, wait) {
let timer;
return (...args) => {
if (timer) {
return;
}
let wakeUp = () => {
timer = null;
};
timer = setTimeout(wakeUp, wait);
func.apply(this, args);
};
}
class _Base extends react__WEBPACK_IMPORTED_MODULE_8___default.a.PureComponent {
componentWillMount() {
const { App, locale } = this.props;
@ -1562,9 +1580,29 @@ class _Base extends react__WEBPACK_IMPORTED_MODULE_8___default.a.PureComponent {
this.updateTheme();
}
componentWillUpdate({ App }) {
hasTopStoriesSectionChanged(nextProps) {
const nPropsSections = nextProps.Sections.find(section => section.id === "topstories");
const tPropsSections = this.props.Sections.find(section => section.id === "topstories");
if (nPropsSections && nPropsSections.options) {
if (!tPropsSections || !tPropsSections.options) {
return true;
}
if (nPropsSections.options.show_spocs !== tPropsSections.options.show_spocs) {
return true;
}
if (nPropsSections.options.stories_endpoint !== tPropsSections.options.stories_endpoint) {
return true;
}
}
return false;
}
componentWillUpdate(nextProps) {
this.updateTheme();
this.sendNewTabRehydrated(App);
if (this.hasTopStoriesSectionChanged(nextProps)) {
this.renderNotified = false;
}
this.sendNewTabRehydrated(nextProps.App);
}
updateTheme() {
@ -1621,6 +1659,25 @@ class BaseContent extends react__WEBPACK_IMPORTED_MODULE_8___default.a.PureCompo
constructor(props) {
super(props);
this.openPreferences = this.openPreferences.bind(this);
this.onWindowScroll = debounce(this.onWindowScroll.bind(this), 5);
this.state = { fixedSearch: false };
}
componentDidMount() {
global.addEventListener("scroll", this.onWindowScroll);
}
componentWillUnmount() {
global.removeEventListener("scroll", this.onWindowScroll);
}
onWindowScroll() {
const SCROLL_THRESHOLD = 34;
if (global.scrollY > SCROLL_THRESHOLD && !this.state.fixedSearch) {
this.setState({ fixedSearch: true });
} else if (global.scrollY <= SCROLL_THRESHOLD && this.state.fixedSearch) {
this.setState({ fixedSearch: false });
}
}
openPreferences() {
@ -1636,7 +1693,7 @@ class BaseContent extends react__WEBPACK_IMPORTED_MODULE_8___default.a.PureCompo
const shouldBeFixedToTop = common_PrerenderData_jsm__WEBPACK_IMPORTED_MODULE_7__["PrerenderData"].arePrefsValid(name => prefs[name]);
const outerClassName = ["outer-wrapper", shouldBeFixedToTop && "fixed-to-top"].filter(v => v).join(" ");
const outerClassName = ["outer-wrapper", shouldBeFixedToTop && "fixed-to-top", prefs.showSearch && this.state.fixedSearch && "fixed-search"].filter(v => v).join(" ");
return react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(
"div",
@ -1675,7 +1732,7 @@ class BaseContent extends react__WEBPACK_IMPORTED_MODULE_8___default.a.PureCompo
}
}
const Base = Object(react_redux__WEBPACK_IMPORTED_MODULE_4__["connect"])(state => ({ App: state.App, Prefs: state.Prefs }))(_Base);
const Base = Object(react_redux__WEBPACK_IMPORTED_MODULE_4__["connect"])(state => ({ App: state.App, Prefs: state.Prefs, Sections: state.Sections }))(_Base);
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
@ -2344,32 +2401,36 @@ class _Search extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComponent
"div",
{ className: "search-wrapper" },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"label",
{ htmlFor: "newtab-search-text", className: "search-label" },
"div",
{ className: "search-inner-wrapper" },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"span",
{ className: "sr-only" },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(react_intl__WEBPACK_IMPORTED_MODULE_0__["FormattedMessage"], { id: "search_web_placeholder" })
)
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
id: "newtab-search-text",
maxLength: "256",
placeholder: this.props.intl.formatMessage({ id: "search_web_placeholder" }),
ref: this.onInputMount,
title: this.props.intl.formatMessage({ id: "search_web_placeholder" }),
type: "search" }),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"button",
{
id: "searchSubmit",
className: "search-button",
onClick: this.onClick,
title: this.props.intl.formatMessage({ id: "search_button" }) },
"label",
{ htmlFor: "newtab-search-text", className: "search-label" },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"span",
{ className: "sr-only" },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(react_intl__WEBPACK_IMPORTED_MODULE_0__["FormattedMessage"], { id: "search_web_placeholder" })
)
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
id: "newtab-search-text",
maxLength: "256",
placeholder: this.props.intl.formatMessage({ id: "search_web_placeholder" }),
ref: this.onInputMount,
title: this.props.intl.formatMessage({ id: "search_web_placeholder" }),
type: "search" }),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"span",
{ className: "sr-only" },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(react_intl__WEBPACK_IMPORTED_MODULE_0__["FormattedMessage"], { id: "search_button" })
"button",
{
id: "searchSubmit",
className: "search-button",
onClick: this.onClick,
title: this.props.intl.formatMessage({ id: "search_button" }) },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"span",
{ className: "sr-only" },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(react_intl__WEBPACK_IMPORTED_MODULE_0__["FormattedMessage"], { id: "search_button" })
)
)
)
);
@ -2830,12 +2891,17 @@ const LinkMenuOptions = {
data: { url: site.url }
})
}),
PinTopSite: (site, index) => ({
PinTopSite: ({ url, searchTopSite, label }, index) => ({
id: "menu_action_pin",
icon: "pin",
action: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].AlsoToMain({
type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].TOP_SITES_PIN,
data: { site: { url: site.url }, index }
data: {
site: Object.assign({
url
}, searchTopSite && { searchTopSite, label }),
index
}
}),
userEvent: "PIN"
}),
@ -2927,7 +2993,7 @@ class _LinkMenu extends react__WEBPACK_IMPORTED_MODULE_5___default.a.PureCompone
const { site, index, source, isPrivateBrowsingEnabled, siteInfo, platform } = props;
// Handle special case of default site
const propOptions = !site.isDefault ? props.options : DEFAULT_SITE_MENU_OPTIONS;
const propOptions = !site.isDefault || site.searchTopSite ? props.options : DEFAULT_SITE_MENU_OPTIONS;
const options = propOptions.map(o => content_src_lib_link_menu_options__WEBPACK_IMPORTED_MODULE_4__["LinkMenuOptions"][o](site, index, source, isPrivateBrowsingEnabled, siteInfo, platform)).map(option => {
const { action, impression, id, string_id, type, userEvent } = option;
@ -4087,10 +4153,13 @@ const TopSites = Object(react_redux__WEBPACK_IMPORTED_MODULE_4__["connect"])(sta
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_SOURCE", function() { return TOP_SITES_SOURCE; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_CONTEXT_MENU_OPTIONS", function() { return TOP_SITES_CONTEXT_MENU_OPTIONS; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS", function() { return TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MIN_RICH_FAVICON_SIZE", function() { return MIN_RICH_FAVICON_SIZE; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MIN_CORNER_FAVICON_SIZE", function() { return MIN_CORNER_FAVICON_SIZE; });
const TOP_SITES_SOURCE = "TOP_SITES";
const TOP_SITES_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "EditTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"];
// the special top site for search shortcut experiment can only have the option to unpin (which removes) the topsite
const TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "BlockUrl"];
// minimum size necessary to show a rich icon instead of a screenshot
const MIN_RICH_FAVICON_SIZE = 96;
// minimum size necessary to show any icon in the top left corner with a screenshot
@ -4236,6 +4305,13 @@ class TopSiteLink extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompo
if (defaultStyle) {
// force no styles (letter fallback) even if the link has imagery
smallFaviconFallback = false;
} else if (link.searchTopSite) {
imageClassName = "top-site-icon rich-icon";
imageStyle = {
backgroundColor: link.backgroundColor,
backgroundImage: `url(${tippyTopIcon})`
};
smallFaviconStyle = { backgroundImage: `url(${tippyTopIcon})` };
} else if (link.customScreenshotURL) {
// assume high quality custom screenshot and use rich icon styles and class names
imageClassName = "top-site-icon rich-icon";
@ -4288,6 +4364,7 @@ class TopSiteLink extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompo
"div",
{ className: "tile", "aria-hidden": true, "data-fallback": letterFallback },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", { className: imageClassName, style: imageStyle }),
link.searchTopSite && react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", { className: "top-site-icon search-topsite" }),
showSmallFavicon && react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
className: "top-site-icon default-icon",
"data-fallback": smallFaviconFallback && letterFallback,
@ -4333,6 +4410,11 @@ class TopSite extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComponent
if (this.props.link.isPinned) {
value.card_type = "pinned";
}
if (this.props.link.searchTopSite) {
// Set the card_type as "search" regardless of its pinning status
value.card_type = "search";
value.search_vendor = this.props.link.hostname;
}
return { value };
}
@ -4351,10 +4433,17 @@ class TopSite extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComponent
// specified as a property on the link.
event.preventDefault();
const { altKey, button, ctrlKey, metaKey, shiftKey } = event;
this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({
type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].OPEN_LINK,
data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } })
}));
if (!this.props.link.searchTopSite) {
this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({
type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].OPEN_LINK,
data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } })
}));
} else {
this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({
type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].FILL_SEARCH_TERM,
data: { label: this.props.link.label }
}));
}
}
onMenuButtonClick(event) {
@ -4391,7 +4480,7 @@ class TopSite extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComponent
dispatch: props.dispatch,
index: props.index,
onUpdate: this.onMenuUpdate,
options: _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__["TOP_SITES_CONTEXT_MENU_OPTIONS"],
options: link.searchTopSite ? _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__["TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS"] : _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__["TOP_SITES_CONTEXT_MENU_OPTIONS"],
site: link,
siteInfo: this._getTelemetryInfo(),
source: _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__["TOP_SITES_SOURCE"] })
@ -4493,11 +4582,11 @@ class _TopSiteList extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComp
this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].AlsoToMain({
type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].TOP_SITES_INSERT,
data: {
site: {
site: Object.assign({
url: this.state.draggedSite.url,
label: this.state.draggedTitle,
customScreenshotURL: this.state.draggedSite.customScreenshotURL
},
}, this.state.draggedSite.searchTopSite && { searchTopSite: true }),
index,
draggedFromIndex: this.state.draggedIndex
}
@ -4653,7 +4742,8 @@ class _StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureC
if (_this.props.fxa_endpoint && !_this.didFetch) {
try {
_this.didFetch = true;
const response = yield fetch(`${_this.props.fxa_endpoint}/metrics-flow`);
const response = yield fetch(`${_this.props.fxa_endpoint}/metrics-flow?entrypoint=
activity-stream-firstrun&utm_source=activity-stream&utm_campaign=firstrun&form_type=email`);
if (response.status === 200) {
const { flowId, flowBeginTime } = yield response.json();
_this.setState({ flowId, flowBeginTime });

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="context-fill" d="M10 0a5.991 5.991 0 0 0-4.885 9.471L.293 14.293a1 1 0 1 0 1.414 1.414l4.822-4.822A6 6 0 1 0 10 0zm0 10a4 4 0 1 1 4-4 4 4 0 0 1-4 4z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 232 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.0 KiB

Просмотреть файл

@ -34,6 +34,11 @@
"url": "https://www.facebook.com/",
"image_url": "facebook-com@2x.png"
},
{
"title": "google",
"url": "https://www.google.com/",
"image_url": "google-com@2x.png"
},
{
"title": "leboncoin",
"url": "http://www.leboncoin.fr/",

Просмотреть файл

@ -183,7 +183,8 @@ Schema definitions/validations that can be used for tests can be found in `syste
| `action` | [Required] Either `activity_stream_event`, `activity_stream_session`, or `activity_stream_performance`. | :one:
| `addon_version` | [Required] Firefox build ID, i.e. `Services.appinfo.appBuildID`. | :one:
| `client_id` | [Required] An identifier for this client. | :one:
| `card_type` | [Optional] ("bookmark", "pocket", "trending", "pinned") | :one:
| `card_type` | [Optional] ("bookmark", "pocket", "trending", "pinned", "search") | :one:
| `search_vendor` | [Optional] the vendor of the search shortcut, one of ("google", "amazon", "wikipedia", "duckduckgo", "bing", etc.). This field only exists when `card_type = "search"` | :one:
| `date` | [Auto populated by Onyx] The date in YYYY-MM-DD format. | :three:
| `experiment_id` | [Optional] The unique identifier for a specific experiment. | :one:
| `event_id` | [Required] An identifier shared by multiple performance pings that describe ane entire request flow. | :one:

Просмотреть файл

@ -90,8 +90,54 @@ A user event ping includes some basic metadata (tab id, addon version, etc.) as
"source": "TOP_SITES",
  "action_position": 2,
"value": {
"card_type": "pinned",
"icon_type": ["screenshot_with_icon" | "screenshot" | "tippytop" | "rich_icon" | "no_image"]
"card_type": ["pinned" | "search"],
"icon_type": ["screenshot_with_icon" | "screenshot" | "tippytop" | "rich_icon" | "no_image"],
// only exists if its card_type = "search"
"search_vendor": "google"
}
// Basic metadata
"action": "activity_stream_event",
"page": ["about:newtab" | "about:home" | "about:welcome" | "unknown"],
"client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
"session_id": "005deed0-e3e4-4c02-a041-17405fd703f6",
  "addon_version": "20180710100040",
  "locale": "en-US",
"user_prefs": 7
}
```
#### Adding a search shortcut
```js
{
  "event": "ADD_SEARCH_SHORTCUT",
"source": "TOP_SITES",
  "action_position": 2,
"value": {
"card_type": "search",
"search_vendor": "google"
}
// Basic metadata
"action": "activity_stream_event",
"page": ["about:newtab" | "about:home" | "about:welcome" | "unknown"],
"client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
"session_id": "005deed0-e3e4-4c02-a041-17405fd703f6",
  "addon_version": "20180710100040",
  "locale": "en-US",
"user_prefs": 7
}
```
#### Editing a search shortcut
```js
{
  "event": "EDIT_SEARCH_SHORTCUT",
"source": "TOP_SITES",
  "action_position": 2,
"value": {
"card_type": "search",
"search_vendor": "google"
}
// Basic metadata
@ -136,8 +182,10 @@ A user event ping includes some basic metadata (tab id, addon version, etc.) as
"source": "TOP_SITES",
  "action_position": 2,
"value": {
"card_type": "pinned",
"icon_type": ["screenshot_with_icon" | "screenshot" | "tippytop" | "rich_icon" | "no_image"]
"card_type": ["pinned" | "search"],
"icon_type": ["screenshot_with_icon" | "screenshot" | "tippytop" | "rich_icon" | "no_image"],
// only exists if its card_type = "search"
"search_vendor": "google"
}
// Basic metadata

Просмотреть файл

@ -27,6 +27,7 @@ const DEFAULT_WHITELIST_HOSTS = {
const SNIPPETS_ENDPOINT_WHITELIST = "browser.newtab.activity-stream.asrouter.whitelistHosts";
const LOCAL_MESSAGE_PROVIDERS = {OnboardingMessageProvider};
const STARTPAGE_VERSION = "0.1.0";
const MessageLoaderUtils = {
/**
@ -180,6 +181,10 @@ class _ASRouter {
const localProvider = this._localProviders[provider.localProvider];
provider.messages = localProvider ? localProvider.getMessages() : [];
}
if (provider.type === "remote" && provider.url) {
provider.url = provider.url.replace(/%STARTPAGE_VERSION%/g, STARTPAGE_VERSION);
provider.url = Services.urlFormatter.formatURL(provider.url);
}
// Reset provider update timestamp to force message refresh
provider.lastUpdated = undefined;
});

Просмотреть файл

@ -163,6 +163,31 @@ const PREFS_CONFIG = new Map([
title: "Experiment to show special top sites that perform keyword searches",
value: true
}],
["improvesearch.topSiteSearchShortcuts.searchEngines", {
title: "An ordered, comma-delimited list of search shortcuts that we should try and pin",
// This pref is dynamic as the shortcuts vary depending on the region
getValue: ({geo}) => {
if (!geo) {
return "";
}
const searchShortcuts = [];
if (geo === "CN") {
searchShortcuts.push("baidu");
} else if (["BY", "KZ", "RU", "TR"].includes(geo)) {
searchShortcuts.push("yandex");
} else {
searchShortcuts.push("google");
}
// Always include Amazon - we will only be able to actually pin it if it
// is available as a default search engine in that region
searchShortcuts.push("amazon");
return searchShortcuts.join(",");
}
}],
["improvesearch.topSiteSearchShortcuts.havePinned", {
title: "A comma-delimited list of search shortcuts that have previously been pinned",
value: ""
}],
["asrouterExperimentEnabled", {
title: "Is the message center experiment on?",
value: false

Просмотреть файл

@ -278,6 +278,10 @@ class PlacesFeed {
}
}
fillSearchTopSiteTerm({_target, data}) {
_target.browser.ownerGlobal.gURLBar.search(`${data.label} `, {disableOneOffButtons: true, disableSearchSuggestionsNotification: true});
}
onAction(action) {
switch (action.type) {
case at.INIT:
@ -315,6 +319,9 @@ class PlacesFeed {
case at.SAVE_TO_POCKET:
this.saveToPocket(action.data.site, action._target.browser);
break;
case at.FILL_SEARCH_TERM:
this.fillSearchTopSiteTerm(action);
break;
case at.OPEN_LINK: {
this.openLink(action);
break;

Просмотреть файл

@ -0,0 +1,27 @@
/* 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/. */
"use strict";
// List of sites we match against Topsites in order to identify sites
// that should be converted to search Topsites
const SEARCH_SHORTCUTS = [
{keyword: "@google", shortURL: "google", url: "https://google.com", searchIdentifier: /^google/},
{keyword: "@baidu", shortURL: "baidu", url: "https://baidu.com", searchIdentifier: /^baidu/},
{keyword: "@yandex", shortURL: "yandex", url: "https://yandex.com", searchIdentifier: /^yandex/},
{keyword: "@amazon", shortURL: "amazon", url: "https://amazon.com", searchIdentifier: /^amazon/}
];
this.SEARCH_SHORTCUTS = SEARCH_SHORTCUTS;
// Note: you must add the activity stream branch to the beginning of this if using outside activity stream
this.SEARCH_SHORTCUTS_EXPERIMENT = "improvesearch.topSiteSearchShortcuts";
this.SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF = "improvesearch.topSiteSearchShortcuts.searchEngines";
this.SEARCH_SHORTCUTS_HAVE_PINNED_PREF = "improvesearch.topSiteSearchShortcuts.havePinned";
function getSearchProvider(candidateShortURL) {
return SEARCH_SHORTCUTS.filter(match => candidateShortURL === match.shortURL)[0] || null;
}
this.getSearchProvider = getSearchProvider;
const EXPORTED_SYMBOLS = ["getSearchProvider", "SEARCH_SHORTCUTS", "SEARCH_SHORTCUTS_EXPERIMENT",
"SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF", "SEARCH_SHORTCUTS_HAVE_PINNED_PREF"];

Просмотреть файл

@ -422,7 +422,7 @@ class SectionsFeed {
this.store.dispatch(ac.SetPref("sectionOrder", orderedSections.join(",")));
}
onAction(action) {
async onAction(action) {
switch (action.type) {
case at.INIT:
SectionsManager.onceInitialized(this.init);
@ -435,7 +435,7 @@ class SectionsFeed {
if (action.data) {
const matched = action.data.name.match(/^(feeds.section.(\S+)).options$/i);
if (matched) {
SectionsManager.addBuiltInSection(matched[1], action.data.value);
await SectionsManager.addBuiltInSection(matched[1], action.data.value);
this.store.dispatch({type: at.SECTION_OPTIONS_CHANGED, data: matched[2]});
}
}

Просмотреть файл

@ -11,6 +11,12 @@ const {insertPinned, TOP_SITES_MAX_SITES_PER_ROW} = ChromeUtils.import("resource
const {Dedupe} = ChromeUtils.import("resource://activity-stream/common/Dedupe.jsm", {});
const {shortURL} = ChromeUtils.import("resource://activity-stream/lib/ShortURL.jsm", {});
const {getDefaultOptions} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
const {
SEARCH_SHORTCUTS_EXPERIMENT,
SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF,
SEARCH_SHORTCUTS_HAVE_PINNED_PREF,
getSearchProvider
} = ChromeUtils.import("resource://activity-stream/lib/SearchShortcuts.jsm", {});
ChromeUtils.defineModuleGetter(this, "filterAdult",
"resource://activity-stream/lib/FilterAdult.jsm");
@ -128,14 +134,83 @@ this.TopSitesFeed = class TopSitesFeed {
if (!this.store.getState().Prefs.values[NO_DEFAULT_SEARCH_TILE_EXP_PREF]) {
return false;
}
// If TopSite Search Shortcuts is enabled we don't want to filter those sites out
if (this.store.getState().Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT] && getSearchProvider(hostname)) {
return false;
}
if (SEARCH_FILTERS.includes(hostname) || hostname === this._currentSearchHostname) {
return true;
}
return false;
}
/**
* _maybeInsertSearchShortcuts - if the search shortcuts experiment is running,
* insert search shortcuts if needed
* @param {Array} plainPinnedSites (from the pinnedSitesCache)
* @returns {Boolean} Did we insert any search shortcuts?
*/
async _maybeInsertSearchShortcuts(plainPinnedSites) {
// Only insert shortcuts if the experiment is running
if (this.store.getState().Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT]) {
// We don't want to insert shortcuts we've previously inserted
const prevInsertedShortcuts = this.store.getState().Prefs.values[SEARCH_SHORTCUTS_HAVE_PINNED_PREF]
.split(",").filter(s => s); // Filter out empty strings
const newInsertedShortcuts = [];
const shouldPin = this.store.getState().Prefs.values[SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF]
.split(",")
.map(getSearchProvider)
.filter(s => s);
// If we've previously inserted all search shortcuts return early
if (shouldPin.every(shortcut => prevInsertedShortcuts.includes(shortcut.shortURL))) {
return false;
}
const numberOfSlots = this.store.getState().Prefs.values[ROWS_PREF] * TOP_SITES_MAX_SITES_PER_ROW;
// The plainPinnedSites array is populated with pinned sites at their
// respective indices, and null everywhere else, but is not always the
// right length
const pinnedSites = [...plainPinnedSites].concat(
Array(numberOfSlots - plainPinnedSites.length).fill(null)
);
await new Promise(resolve => Services.search.init(resolve));
const tryToInsertSearchShortcut = shortcut => {
const nextAvailable = pinnedSites.indexOf(null);
// Only add a search shortcut if the site isn't already pinned, we
// haven't previously inserted it, there's space to pin it, and the
// search engine is available in Firefox
if (
!pinnedSites.find(s => s && s.hostname === shortcut.shortURL) &&
!prevInsertedShortcuts.includes(shortcut.shortURL) &&
nextAvailable > -1 &&
Services.search.getEngines().find(e => e.identifier.match(shortcut.searchIdentifier))
) {
const site = this.topSiteToSearchTopSite({url: shortcut.url});
this._pinSiteAt(site, nextAvailable);
pinnedSites[nextAvailable] = site;
newInsertedShortcuts.push(shortcut.shortURL);
}
};
shouldPin.forEach(shortcut => tryToInsertSearchShortcut(shortcut));
if (newInsertedShortcuts.length) {
this.store.dispatch(ac.SetPref(SEARCH_SHORTCUTS_HAVE_PINNED_PREF, prevInsertedShortcuts.concat(newInsertedShortcuts).join(",")));
return true;
}
}
return false;
}
async getLinksWithDefaults() {
const numItems = this.store.getState().Prefs.values[ROWS_PREF] * TOP_SITES_MAX_SITES_PER_ROW;
const searchShortcutsExperiment = this.store.getState().Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT];
// Get all frecent sites from history
const frecent = (await this.frecentCache.request({
@ -145,24 +220,44 @@ this.TopSitesFeed = class TopSitesFeed {
.reduce((validLinks, link) => {
const hostname = shortURL(link);
if (!this.isExperimentOnAndLinkFilteredSearch(hostname)) {
validLinks.push({...link, hostname});
validLinks.push({
...(searchShortcutsExperiment ? this.topSiteToSearchTopSite(link) : link),
hostname
});
}
return validLinks;
}, []);
// Remove any defaults that have been blocked
const notBlockedDefaultSites = DEFAULT_TOP_SITES
.filter(link => {
.reduce((topsites, link) => {
const searchProvider = getSearchProvider(shortURL(link));
if (NewTabUtils.blockedLinks.isBlocked({url: link.url})) {
return false;
return topsites;
} else if (this.isExperimentOnAndLinkFilteredSearch(link.hostname)) {
return false;
return topsites;
// If we've previously blocked a search shortcut, remove the default top site
// that matches the hostname
} else if (searchProvider && NewTabUtils.blockedLinks.isBlocked({url: searchProvider.url})) {
return topsites;
}
return true;
});
return [
...topsites,
searchShortcutsExperiment ? this.topSiteToSearchTopSite(link) : link
];
}, []);
// Get pinned links augmented with desired properties
const plainPinned = await this.pinnedCache.request();
let plainPinned = await this.pinnedCache.request();
// Insert search shortcuts if we need to.
// _maybeInsertSearchShortcuts returns true if any search shortcuts are
// inserted, meaning we need to expire and refresh the pinnedCache
if (await this._maybeInsertSearchShortcuts(plainPinned)) {
this.pinnedCache.expire();
plainPinned = await this.pinnedCache.request();
}
const pinned = await Promise.all(plainPinned.map(async link => {
if (!link) {
return link;
@ -178,8 +273,12 @@ this.TopSitesFeed = class TopSitesFeed {
}
// If the link is a frecent site, do not copy over 'isDefault', else check
// if the site is a default site
const copy = Object.assign({}, frecentSite ||
{isDefault: !!notBlockedDefaultSites.find(finder)}, link, {hostname: shortURL(link)});
const copy = Object.assign(
{},
frecentSite || {isDefault: !!notBlockedDefaultSites.find(finder)},
link,
{hostname: shortURL(link)}
);
// Add in favicons if we don't already have it
if (!copy.favicon) {
@ -216,6 +315,8 @@ this.TopSitesFeed = class TopSitesFeed {
// If there is a custom screenshot this is the only image we display
if (link.customScreenshotURL) {
this._fetchScreenshot(link, link.customScreenshotURL);
} else if (link.searchTopSite && !link.isDefault) {
this._tippyTopProvider.processSite(link);
} else {
this._fetchIcon(link);
}
@ -259,6 +360,18 @@ this.TopSitesFeed = class TopSitesFeed {
}
}
topSiteToSearchTopSite(site) {
const searchProvider = getSearchProvider(shortURL(site));
if (!searchProvider) {
return site;
}
return {
...site,
searchTopSite: true,
label: searchProvider.keyword
};
}
/**
* Get an image for the link preferring tippy top, rich favicon, screenshots.
*/
@ -337,7 +450,7 @@ this.TopSitesFeed = class TopSitesFeed {
* @param customScreenshotURL {string} User set URL of preview image for site
* @param label {string} User set string of custom site name
*/
async _pinSiteAt({customScreenshotURL, label, url}, index) {
async _pinSiteAt({customScreenshotURL, label, url, searchTopSite}, index) {
const toPin = {url};
if (label) {
toPin.label = label;
@ -345,6 +458,9 @@ this.TopSitesFeed = class TopSitesFeed {
if (customScreenshotURL) {
toPin.customScreenshotURL = customScreenshotURL;
}
if (searchTopSite) {
toPin.searchTopSite = searchTopSite;
}
NewTabUtils.pinnedLinks.pin(toPin, index);
await this._clearLinkCustomScreenshot({customScreenshotURL, url});
@ -391,6 +507,20 @@ this.TopSitesFeed = class TopSitesFeed {
this._broadcastPinnedSitesUpdated();
}
disableSearchImprovements() {
Services.prefs.clearUserPref(`browser.newtabpage.activity-stream.${SEARCH_SHORTCUTS_HAVE_PINNED_PREF}`);
this.unpinAllSearchShortcuts();
}
unpinAllSearchShortcuts() {
for (let pinnedLink of NewTabUtils.pinnedLinks.links) {
if (pinnedLink && pinnedLink.searchTopSite) {
NewTabUtils.pinnedLinks.unpin(pinnedLink);
}
}
this.pinnedCache.expire();
}
/**
* Insert a site to pin at a position shifting over any other pinned sites.
*/
@ -478,10 +608,20 @@ this.TopSitesFeed = class TopSitesFeed {
this.refresh({broadcast: true});
break;
case at.PREF_CHANGED:
if (action.data.name === DEFAULT_SITES_PREF) {
this.refreshDefaults(action.data.value);
} else if ([ROWS_PREF, NO_DEFAULT_SEARCH_TILE_EXP_PREF].includes(action.data.name)) {
this.refresh({broadcast: true});
switch (action.data.name) {
case DEFAULT_SITES_PREF:
this.refreshDefaults(action.data.value);
break;
case ROWS_PREF:
case NO_DEFAULT_SEARCH_TILE_EXP_PREF:
case SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF:
this.refresh({broadcast: true});
break;
case SEARCH_SHORTCUTS_EXPERIMENT:
if (!action.data.value) {
this.disableSearchImprovements();
}
this.refresh({broadcast: true});
}
break;
case at.UPDATE_SECTION_PREFS:

Просмотреть файл

@ -25,7 +25,7 @@ const DEFAULT_RECS_EXPIRE_TIME = 60 * 60 * 1000; // 1 hour
const SECTION_ID = "topstories";
const SPOC_IMPRESSION_TRACKING_PREF = "feeds.section.topstories.spoc.impressions";
const REC_IMPRESSION_TRACKING_PREF = "feeds.section.topstories.rec.impressions";
const MAX_LIFETIME_CAP = 100; // Guard against misconfiguration on the server
const MAX_LIFETIME_CAP = 500; // Guard against misconfiguration on the server
this.TopStoriesFeed = class TopStoriesFeed {
constructor() {
@ -35,33 +35,52 @@ this.TopStoriesFeed = class TopStoriesFeed {
this._prefs = new Prefs();
}
init() {
const initFeed = () => {
SectionsManager.enableSection(SECTION_ID);
try {
const {options} = SectionsManager.sections.get(SECTION_ID);
const apiKey = this.getApiKeyFromPref(options.api_key_pref);
this.stories_endpoint = this.produceFinalEndpointUrl(options.stories_endpoint, apiKey);
this.topics_endpoint = this.produceFinalEndpointUrl(options.topics_endpoint, apiKey);
this.read_more_endpoint = options.read_more_endpoint;
this.stories_referrer = options.stories_referrer;
this.personalized = options.personalized;
this.show_spocs = options.show_spocs;
this.maxHistoryQueryResults = options.maxHistoryQueryResults;
this.storiesLastUpdated = 0;
this.topicsLastUpdated = 0;
this.domainAffinitiesLastUpdated = 0;
async onInit() {
SectionsManager.enableSection(SECTION_ID);
try {
const {options} = SectionsManager.sections.get(SECTION_ID);
const apiKey = this.getApiKeyFromPref(options.api_key_pref);
// Set this to true if we're not loading from cache.
let shouldBroadcast = false;
this.stories_endpoint = this.produceFinalEndpointUrl(options.stories_endpoint, apiKey);
this.topics_endpoint = this.produceFinalEndpointUrl(options.topics_endpoint, apiKey);
this.read_more_endpoint = options.read_more_endpoint;
this.stories_referrer = options.stories_referrer;
this.personalized = options.personalized;
this.show_spocs = options.show_spocs;
this.maxHistoryQueryResults = options.maxHistoryQueryResults;
this.storiesLastUpdated = 0;
this.topicsLastUpdated = 0;
this.storiesLoaded = false;
this.domainAffinitiesLastUpdated = 0;
this.loadCachedData();
this.fetchStories();
this.fetchTopics();
Services.obs.addObserver(this, "idle-daily");
} catch (e) {
Cu.reportError(`Problem initializing top stories feed: ${e.message}`);
// Cache is used for new page loads, which shouldn't have changed data.
// If we have changed data, cache should be cleared,
// and last updated should be 0, and we can fetch.
// Think there is a bug here.
await this.loadCachedData();
if (this.storiesLastUpdated === 0) {
shouldBroadcast = true;
await this.fetchStories();
}
};
SectionsManager.onceInitialized(initFeed);
if (this.topicsLastUpdated === 0) {
shouldBroadcast = true;
await this.fetchTopics();
}
this.doContentUpdate(shouldBroadcast);
this.storiesLoaded = true;
// This is filtered so an update function can return true to retry on the next run
this.contentUpdateQueue = this.contentUpdateQueue.filter(update => update());
Services.obs.addObserver(this, "idle-daily");
} catch (e) {
Cu.reportError(`Problem initializing top stories feed: ${e.message}`);
}
}
init() {
SectionsManager.onceInitialized(this.onInit.bind(this));
}
observe(subject, topic, data) {
@ -73,10 +92,26 @@ this.TopStoriesFeed = class TopStoriesFeed {
}
uninit() {
this.storiesLoaded = false;
this.cache.set("stories", {});
this.cache.set("topics", {});
Services.obs.removeObserver(this, "idle-daily");
SectionsManager.disableSection(SECTION_ID);
}
doContentUpdate(shouldBroadcast) {
let updateProps = {};
if (this.stories) {
updateProps.rows = this.stories;
}
if (this.topics) {
Object.assign(updateProps, {topics: this.topics, read_more_endpoint: this.read_more_endpoint});
}
// We should only be calling this once per init.
this.dispatchUpdateEvent(shouldBroadcast, updateProps);
}
async fetchStories() {
if (!this.stories_endpoint) {
return;
@ -97,13 +132,8 @@ this.TopStoriesFeed = class TopStoriesFeed {
this.spocs = this.transform(body.spocs).filter(s => s.score >= s.min_score);
this.cleanUpCampaignImpressionPref();
}
this.dispatchUpdateEvent(this.storiesLastUpdated, {rows: this.stories});
this.storiesLastUpdated = Date.now();
body._timestamp = this.storiesLastUpdated;
// This is filtered so an update function can return true to retry on the next run
this.contentUpdateQueue = this.contentUpdateQueue.filter(update => update());
this.cache.set("stories", body);
} catch (error) {
Cu.reportError(`Failed to fetch content: ${error.message}`);
@ -123,11 +153,11 @@ this.TopStoriesFeed = class TopStoriesFeed {
if (stories && stories.length > 0 && this.storiesLastUpdated === 0) {
this.updateSettings(data.stories.settings);
const rows = this.transform(stories);
this.dispatchUpdateEvent(this.storiesLastUpdated, {rows});
this.stories = rows;
this.storiesLastUpdated = data.stories._timestamp;
}
if (topics && topics.length > 0 && this.topicsLastUpdated === 0) {
this.dispatchUpdateEvent(this.topicsLastUpdated, {topics, read_more_endpoint: this.read_more_endpoint});
this.topics = topics;
this.topicsLastUpdated = data.topics._timestamp;
}
}
@ -169,7 +199,7 @@ this.TopStoriesFeed = class TopStoriesFeed {
const body = await response.json();
const {topics} = body;
if (topics) {
this.dispatchUpdateEvent(this.topicsLastUpdated, {topics, read_more_endpoint: this.read_more_endpoint});
this.topics = topics;
this.topicsLastUpdated = Date.now();
body._timestamp = this.topicsLastUpdated;
this.cache.set("topics", body);
@ -179,8 +209,8 @@ this.TopStoriesFeed = class TopStoriesFeed {
}
}
dispatchUpdateEvent(lastUpdated, data) {
SectionsManager.updateSection(SECTION_ID, data, lastUpdated === 0);
dispatchUpdateEvent(shouldBroadcast, data) {
SectionsManager.updateSection(SECTION_ID, data, shouldBroadcast);
}
compareScore(a, b) {
@ -320,7 +350,7 @@ this.TopStoriesFeed = class TopStoriesFeed {
return false;
};
if (this.stories) {
if (this.storiesLoaded) {
updateContent();
} else {
// Delay updating tab content until initial data has been fetched
@ -448,18 +478,20 @@ this.TopStoriesFeed = class TopStoriesFeed {
this.init();
}
onAction(action) {
async onAction(action) {
switch (action.type) {
case at.INIT:
this.init();
break;
case at.SYSTEM_TICK:
if (Date.now() - this.storiesLastUpdated >= STORIES_UPDATE_TIME) {
this.fetchStories();
await this.fetchStories();
}
if (Date.now() - this.topicsLastUpdated >= TOPICS_UPDATE_TIME) {
this.fetchTopics();
await this.fetchTopics();
}
this.doContentUpdate(false);
break;
case at.UNINIT:
this.uninit();

Просмотреть файл

@ -128,7 +128,7 @@ topsites_form_title_placeholder=Digite um título
topsites_form_url_label=URL
topsites_form_image_url_label=URL de imagem personalizada
topsites_form_url_placeholder=Digite ou cole um URL
topsites_form_use_image_link=Utilizar uma imagem personalizada…
topsites_form_use_image_link=Usar uma imagem personalizada…
# LOCALIZATION NOTE (topsites_form_*_button): These are verbs/actions.
topsites_form_preview_button=Visualizar
topsites_form_add_button=Adicionar

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,7 +9,7 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head>
<body class="activity-stream">
<div id="root"><div data-reactroot=""><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>Otsi veebist</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="Otsi veebist" title="Otsi veebist"/><button id="searchSubmit" class="search-button" title="Otsi"><span class="sr-only"><span>Otsi</span></span></button></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>Top saidid</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket soovitab</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="topic"><span><span>Populaarsed teemad:</span></span><ul></ul></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>Esiletõstetud</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="Kohanda uue kaardi lehte"></button></div></div></main></div></div></div>
<div id="root"><div data-reactroot=""><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>Otsi veebist</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="Otsi veebist" title="Otsi veebist"/><button id="searchSubmit" class="search-button" title="Otsi"><span class="sr-only"><span>Otsi</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>Top saidid</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket soovitab</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="topic"><span><span>Populaarsed teemad:</span></span><ul></ul></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>Esiletõstetud</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="Kohanda uue kaardi lehte"></button></div></div></main></div></div></div>
<div id="snippets-container">
<div id="snippets"></div>
</div>

Просмотреть файл

@ -9,7 +9,7 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head>
<body class="activity-stream">
<div id="root"><div data-reactroot=""><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>Otsi veebist</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="Otsi veebist" title="Otsi veebist"/><button id="searchSubmit" class="search-button" title="Otsi"><span class="sr-only"><span>Otsi</span></span></button></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>Top saidid</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket soovitab</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="topic"><span><span>Populaarsed teemad:</span></span><ul></ul></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>Esiletõstetud</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="Kohanda uue kaardi lehte"></button></div></div></main></div></div></div>
<div id="root"><div data-reactroot=""><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>Otsi veebist</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="Otsi veebist" title="Otsi veebist"/><button id="searchSubmit" class="search-button" title="Otsi"><span class="sr-only"><span>Otsi</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>Top saidid</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="Muuda seda saiti"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket soovitab</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="topic"><span><span>Populaarsed teemad:</span></span><ul></ul></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>Esiletõstetud</span></span></h3><div><button class="context-menu-button icon"><span class="sr-only"><span>Ava osa kontekstimenüü</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="Kohanda uue kaardi lehte"></button></div></div></main></div></div></div>
<div id="snippets-container">
<div id="snippets"></div>
</div>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше