Bug 1519303 - Add hand-off triggers, add-on icons and bug fixes to Activity Stream r=k88hudson

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Ed Lee 2019-01-14 23:27:45 +00:00
Родитель eb75a7a831
Коммит da497294eb
51 изменённых файлов: 3086 добавлений и 1764 удалений

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

@ -42,6 +42,7 @@ for (const type of [
"DISCOVERY_STREAM_CONFIG_CHANGE",
"DISCOVERY_STREAM_CONFIG_SETUP",
"DISCOVERY_STREAM_CONFIG_SET_VALUE",
"DISCOVERY_STREAM_FEEDS_UPDATE",
"DISCOVERY_STREAM_LAYOUT_RESET",
"DISCOVERY_STREAM_LAYOUT_UPDATE",
"DOWNLOAD_CHANGED",

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

@ -455,6 +455,8 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
return {...prevState, lastUpdated: action.data.lastUpdated || null, layout: action.data.layout || []};
case at.DISCOVERY_STREAM_LAYOUT_RESET:
return {...prevState, lastUpdated: INITIAL_STATE.DiscoveryStream.lastUpdated, layout: INITIAL_STATE.DiscoveryStream.layout};
case at.DISCOVERY_STREAM_FEEDS_UPDATE:
return {...prevState, feeds: action.data || prevState.feeds};
default:
return prevState;
}

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

@ -71,6 +71,8 @@ export class SubmitFormSnippet extends React.PureComponent {
}
expandSnippet() {
this.props.sendUserActionTelemetry({event: "CLICK_BUTTON", value: "scene1-button-learn-more", id: this.props.UISurface});
this.setState({
expanded: true,
signupSuccess: false,

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

@ -39,8 +39,43 @@ class DiscoveryStreamAdmin extends React.PureComponent {
this.setConfigValue("enabled", event.target.checked);
}
renderComponent(width, component) {
return (
<table><tbody>
<Row>
<td className="min">Type</td>
<td>{component.type}</td>
</Row>
<Row>
<td className="min">Width</td>
<td>{width}</td>
</Row>
{component.feed && this.renderFeed(component.feed)}
</tbody></table>
);
}
renderFeed(feed) {
const {feeds} = this.props.state;
if (!feed.url) {
return null;
}
return (
<React.Fragment>
<Row>
<td className="min">Feed url</td>
<td>{feed.url}</td>
</Row>
<Row>
<td className="min">Data last fetched</td>
<td>{relativeTime(feeds[feed.url].lastUpdated) || "(no data)"}</td>
</Row>
</React.Fragment>
);
}
render() {
const {config, lastUpdated} = this.props.state;
const {config, lastUpdated, layout} = this.props.state;
return (<div>
<div className="dsEnabled"><input type="checkbox" checked={config.enabled} onChange={this.onEnableToggle} /> enabled</div>
@ -48,6 +83,18 @@ class DiscoveryStreamAdmin extends React.PureComponent {
<Row><td className="min">Data last fetched</td><td>{relativeTime(lastUpdated) || "(no data)"}</td></Row>
<Row><td className="min">Endpoint</td><td>{config.layout_endpoint || "(empty)"}</td></Row>
</tbody></table>
<h3>Layout</h3>
{layout.map((row, rowIndex) => (
<div key={`row-${rowIndex}`}>
{row.components.map((component, componentIndex) => (
<div key={`component-${componentIndex}`} className="ds-component">
{this.renderComponent(row.width, component)}
</div>
))}
</div>
))}
</div>);
}
}
@ -496,7 +543,7 @@ export class ASRouterAdminInner extends React.PureComponent {
return (<React.Fragment>
<h2>Discovery Stream</h2>
<DiscoveryStreamAdmin state={this.props.DiscoveryStream} dispatch={this.props.dispatch} />
</React.Fragment>);
</React.Fragment>);
default:
return (<React.Fragment>
<h2>Message Providers <button title="Restore all provider settings that ship with Firefox" className="button" onClick={this.resetPref}>Restore default prefs</button></h2>

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

@ -146,5 +146,9 @@
margin-bottom: 20px;
border: 1px solid $border-color;
}
.ds-component {
margin-bottom: 20px;
}
}

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

@ -15,13 +15,16 @@ export class _DiscoveryStreamBase extends React.PureComponent {
case "SectionTitle":
return (<SectionTitle />);
case "CardGrid":
return (<CardGrid />);
return (<CardGrid feed={component.feed} />);
case "Hero":
return (<Hero />);
return (<Hero
feed={component.feed}
style={component.properties.style}
items={component.properties.items} />);
case "HorizontalRule":
return (<HorizontalRule />);
case "List":
return (<List />);
return (<List feed={component.feed} />);
default:
return (<div>{component.type}</div>);
}
@ -30,9 +33,9 @@ export class _DiscoveryStreamBase extends React.PureComponent {
render() {
const {layout} = this.props.DiscoveryStream;
return (
<div className="discovery-stream layout">
<div className="discovery-stream ds-layout">
{layout.map((row, rowIndex) => (
<div key={`row-${rowIndex}`} className={`column column-${row.width}`}>
<div key={`row-${rowIndex}`} className={`ds-column ds-column-${row.width}`}>
{row.components.map((component, componentIndex) => (
<div key={`component-${componentIndex}`}>
{this.renderComponent(component)}

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

@ -1,16 +1,21 @@
.discovery-stream.layout {
.discovery-stream.ds-layout {
$columns: 12;
display: grid;
grid-template-columns: repeat($columns, 1fr);
grid-column-gap: 10px;
grid-column-gap: 48px;
grid-row-gap: 10px;
@while $columns > 0 {
.column-#{$columns} {
.ds-column-#{$columns} {
grid-column-start: auto;
grid-column-end: span $columns;
}
$columns: $columns - 1;
}
.ds-column {
display: grid;
grid-row-gap: 10px;
}
}

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

@ -1,7 +1,9 @@
import {connect} from "react-redux";
import React from "react";
export class CardGrid extends React.PureComponent {
export class _CardGrid extends React.PureComponent {
render() {
// const feed = this.props.DiscoveryStream.feeds[this.props.feed.url];
return (
<div>
Card Grid
@ -9,3 +11,5 @@ export class CardGrid extends React.PureComponent {
);
}
}
export const CardGrid = connect(state => ({DiscoveryStream: state.DiscoveryStream}))(_CardGrid);

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

@ -0,0 +1,16 @@
import React from "react";
export class DSCard extends React.PureComponent {
render() {
return (
<div className="ds-card">
<img src={this.props.image_src} />
<div className="meta">
<header>{this.props.title}</header>
<p>{this.props.excerpt}</p>
<p>{this.props.source}</p>
</div>
</div>
);
}
}

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

@ -0,0 +1,24 @@
.ds-card {
img {
width: 100%;
border: 0.5px solid $black-10;
box-sizing: border-box;
border-radius: 4px;
}
.meta {
padding: 16px;
}
header {
line-height: 24px;
font-size: 17px;
color: $grey-90;
}
p {
font-size: 13px;
line-height: 20px;
color: $grey-50;
}
}

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

@ -1,11 +1,51 @@
import {connect} from "react-redux";
import {DSCard} from "../DSCard/DSCard.jsx";
import React from "react";
export class Hero extends React.PureComponent {
export class _Hero extends React.PureComponent {
render() {
const feed = this.props.DiscoveryStream.feeds[this.props.feed.url];
// Handle a render before feed has been fetched by displaying nothing
if (!feed) {
return (
<div />
);
}
let [heroRec, ...otherRecs] = feed.data.recommendations;
// TODO: Let this count be determined by the endpoint
let cards = otherRecs.slice(1, 5).map((rec, index) => (
<DSCard
key={`dscard-${index}`}
image_src={rec.image_src}
title={rec.title}
excerpt={rec.excerpt}
source="TODO: SOURCE" />
));
return (
<div>
Hero
<div className={`ds-hero ds-hero-${this.props.style}`}>
<div className="wrapper">
<img src={heroRec.image_src} />
<div className="meta">
<header>{heroRec.title}</header>
<p>{heroRec.excerpt}</p>
<p>TODO: SOURCE</p>
</div>
</div>
<div className="cards">
{ cards }
</div>
</div>
);
}
}
_Hero.defaultProps = {
style: `border`,
items: 1, // Number of stories to display
};
export const Hero = connect(state => ({DiscoveryStream: state.DiscoveryStream}))(_Hero);

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

@ -0,0 +1,107 @@
.ds-hero {
// "1/3 width layout" (aka "Mobile First")
.wrapper {
img {
width: 100%;
height: auto;
}
.meta {
header {
font-size: 22px;
}
p {
font-size: 13px;
}
}
}
// "2/3 width layout"
.column-5 &,
.column-6 &,
.column-7 &,
.column-8 & {
.wrapper {
display: flex;
align-items: flex-start;
img {
width: 50%;
height: auto;
}
.meta {
width: 50%;
}
}
.cards {
display: flex;
flex-wrap: wrap;
.ds-card {
width: 50%;
padding: 12px;
&:nth-child(odd) {
padding: 12px 12px 12px 0;
}
&:nth-child(even) {
padding: 12px 0 12px 12px;
}
}
}
}
// "Full width layout"
.column-9 &,
.column-10 &,
.column-11 &,
.column-12 & {
.wrapper {
display: flex;
align-items: flex-start;
img {
width: 67%;
}
.meta {
width: 33%;
padding: 24px;
header {
font-size: 22px;
}
p {
font-size: 15px;
}
}
}
.cards {
display: flex;
.ds-card {
width: 25%;
padding: 12px;
.meta {
padding: 0;
}
&:first-child {
padding: 12px 12px 12px 0;
}
&:last-child {
padding: 12px 0 12px 12px;
}
}
}
}
}

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

@ -1,11 +1,15 @@
import {connect} from "react-redux";
import React from "react";
export class List extends React.PureComponent {
export class _List extends React.PureComponent {
render() {
// const feed = this.props.DiscoveryStream.feeds[this.props.feed.url];
return (
<div>
<div className="ds-list">
List
</div>
);
}
}
export const List = connect(state => ({DiscoveryStream: state.DiscoveryStream}))(_List);

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

@ -11,7 +11,7 @@ export class SectionTitle extends React.PureComponent {
render() {
const {topics} = this.props;
return (
<span className="section-title">
<span className="ds-section-title">
<ul>
{topics && topics.map(t => <Topic key={t.name} url={t.url} name={t.name} />)}
<li><a className="ds-more-recommendations">More Recommendations</a></li>

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

@ -1,4 +1,4 @@
.section-title {
.ds-section-title {
ul {
margin: 0;
padding: 0;

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

@ -1,11 +1,12 @@
import {connect} from "react-redux";
import {TopSites as OldTopSites} from "content-src/components/TopSites/TopSites";
import React from "react";
export class _TopSites extends React.PureComponent {
render() {
return (
<div className="ds-topsites">
Top Sites
<div className="ds-top-sites">
<OldTopSites />
</div>
);
}

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

@ -0,0 +1,14 @@
// ds topsites wraps the original topsites, with a few css changes.
.ds-top-sites {
// This is the override layer.
.top-sites {
// Slightly different alignment with the other DS components than AS has.
padding: 0;
// We hide this and don't support it in ds.
.section-top-bar {
display: none;
}
}
}

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

@ -12,7 +12,10 @@ export class _Search extends React.PureComponent {
super(props);
this.onSearchClick = this.onSearchClick.bind(this);
this.onSearchHandoffClick = this.onSearchHandoffClick.bind(this);
this.onSearchHandoffKeyDown = this.onSearchHandoffKeyDown.bind(this);
this.onSearchHandoffPaste = this.onSearchHandoffPaste.bind(this);
this.onInputMount = this.onInputMount.bind(this);
this.onSearchHandoffButtonMount = this.onSearchHandoffButtonMount.bind(this);
}
handleEvent(event) {
@ -42,7 +45,43 @@ export class _Search extends React.PureComponent {
// TODO: Send a telemetry ping. BUG 1514732
}
onSearchHandoffKeyDown(event) {
if (event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey) {
// We only care about key strokes that will produce a character.
const text = event.key;
this.props.dispatch(ac.OnlyToMain({type: at.HANDOFF_SEARCH_TO_AWESOMEBAR, data: {text}}));
// TODO: Send a telemetry ping. BUG 1514732
}
}
onSearchHandoffPaste(event) {
if (!this._searchHandoffButton ||
!this._searchHandoffButton.contains(global.document.activeElement)) {
// Don't handle every paste on the document. Filter out those that are
// not on the Search Hand-off button.
return;
}
event.preventDefault();
const text = event.clipboardData.getData("Text");
this.props.dispatch(ac.OnlyToMain({type: at.HANDOFF_SEARCH_TO_AWESOMEBAR, data: {text}}));
// TODO: Send a telemetry ping. BUG 1514732
}
componentWillMount() {
if (global.document) {
// We need to listen to paste events that bubble up from the Search Hand-off
// button. Adding the paste listener to the button itself or it's parent
// doesn't work consistently until the page is clicked on.
global.document.addEventListener("paste", this.onSearchHandoffPaste);
}
}
componentWillUnmount() {
if (global.document) {
global.document.removeEventListener("paste", this.onDocumentPaste);
}
delete window.gContentSearchController;
}
@ -74,6 +113,11 @@ export class _Search extends React.PureComponent {
}
}
onSearchHandoffButtonMount(button) {
// Keep a reference to the button for use during "paste" event handling.
this._searchHandoffButton = button;
}
/*
* Do not change the ID on the input field, as legacy newtab code
* specifically looks for the id 'newtab-search-text' on input fields
@ -118,9 +162,12 @@ export class _Search extends React.PureComponent {
<div className="search-inner-wrapper">
<button
className="search-handoff-button"
ref={this.onSearchHandoffButtonMount}
onClick={this.onSearchHandoffClick}
onKeyDown={this.onSearchHandoffKeyDown}
title={this.props.intl.formatMessage({id: "search_web_placeholder"})}>
<div className="fake-textbox">{this.props.intl.formatMessage({id: "search_web_placeholder"})}</div>
<div className="fake-editable" tabIndex="-1" aria-hidden="true" contentEditable="" />
<div className="fake-caret" />
</button>
{/*

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

@ -164,6 +164,10 @@ $glyph-forward: url('chrome://browser/skin/forward.svg');
.search-active & {
border: $input-border-active;
box-shadow: var(--newtab-textbox-focus-boxshadow);
.fake-caret {
display: block;
}
}
.search-hidden & {
@ -171,6 +175,20 @@ $glyph-forward: url('chrome://browser/skin/forward.svg');
visibility: hidden;
}
.fake-editable:focus {
outline: none;
caret-color: transparent;
}
.fake-editable {
color: transparent;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.fake-textbox {
opacity: 0.54;
text-align: start;
@ -191,10 +209,6 @@ $glyph-forward: url('chrome://browser/skin/forward.svg');
visibility: hidden;
}
}
.search-active & {
display: block;
}
}
}

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

@ -152,6 +152,7 @@ input {
@import '../components/DiscoveryStreamComponents/List/List';
@import '../components/DiscoveryStreamComponents/SectionTitle/SectionTitle';
@import '../components/DiscoveryStreamComponents/TopSites/TopSites';
@import '../components/DiscoveryStreamComponents/DSCard/DSCard';
// AS Router
@import '../asrouter/components/Button/Button';

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

@ -1070,9 +1070,22 @@ main {
.search-active .search-handoff-button {
border: 1px solid var(--newtab-textbox-focus-color);
box-shadow: var(--newtab-textbox-focus-boxshadow); }
.search-handoff-button:focus .fake-caret,
.search-active .search-handoff-button .fake-caret {
display: block; }
.search-hidden .search-handoff-button {
opacity: 0;
visibility: hidden; }
.search-handoff-button .fake-editable:focus {
outline: none;
caret-color: transparent; }
.search-handoff-button .fake-editable {
color: transparent;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0; }
.search-handoff-button .fake-textbox {
opacity: 0.54;
text-align: start; }
@ -1089,8 +1102,6 @@ main {
@keyframes caret-animation {
to {
visibility: hidden; } }
.search-active .search-handoff-button .fake-caret {
display: block; }
@media (min-height: 701px) {
.fixed-search main {
@ -1701,6 +1712,8 @@ main {
font-size: 16px;
margin-bottom: 20px;
border: 1px solid var(--newtab-border-secondary-color); }
.asrouter-admin .ds-component {
margin-bottom: 20px; }
.pocket-logged-in-cta {
font-size: 13px;
@ -1749,61 +1762,170 @@ main {
.more-recommendations:dir(rtl)::after {
transform: scaleX(-1); }
.discovery-stream.layout {
.discovery-stream.ds-layout {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-column-gap: 10px;
grid-column-gap: 48px;
grid-row-gap: 10px; }
.discovery-stream.layout .column-12 {
.discovery-stream.ds-layout .ds-column-12 {
grid-column-start: auto;
grid-column-end: span 12; }
.discovery-stream.layout .column-11 {
.discovery-stream.ds-layout .ds-column-11 {
grid-column-start: auto;
grid-column-end: span 11; }
.discovery-stream.layout .column-10 {
.discovery-stream.ds-layout .ds-column-10 {
grid-column-start: auto;
grid-column-end: span 10; }
.discovery-stream.layout .column-9 {
.discovery-stream.ds-layout .ds-column-9 {
grid-column-start: auto;
grid-column-end: span 9; }
.discovery-stream.layout .column-8 {
.discovery-stream.ds-layout .ds-column-8 {
grid-column-start: auto;
grid-column-end: span 8; }
.discovery-stream.layout .column-7 {
.discovery-stream.ds-layout .ds-column-7 {
grid-column-start: auto;
grid-column-end: span 7; }
.discovery-stream.layout .column-6 {
.discovery-stream.ds-layout .ds-column-6 {
grid-column-start: auto;
grid-column-end: span 6; }
.discovery-stream.layout .column-5 {
.discovery-stream.ds-layout .ds-column-5 {
grid-column-start: auto;
grid-column-end: span 5; }
.discovery-stream.layout .column-4 {
.discovery-stream.ds-layout .ds-column-4 {
grid-column-start: auto;
grid-column-end: span 4; }
.discovery-stream.layout .column-3 {
.discovery-stream.ds-layout .ds-column-3 {
grid-column-start: auto;
grid-column-end: span 3; }
.discovery-stream.layout .column-2 {
.discovery-stream.ds-layout .ds-column-2 {
grid-column-start: auto;
grid-column-end: span 2; }
.discovery-stream.layout .column-1 {
.discovery-stream.ds-layout .ds-column-1 {
grid-column-start: auto;
grid-column-end: span 1; }
.discovery-stream.ds-layout .ds-column {
display: grid;
grid-row-gap: 10px; }
.section-title ul {
.ds-hero .wrapper img {
width: 100%;
height: auto; }
.ds-hero .wrapper .meta header {
font-size: 22px; }
.ds-hero .wrapper .meta p {
font-size: 13px; }
.column-5 .ds-hero .wrapper,
.column-6 .ds-hero .wrapper,
.column-7 .ds-hero .wrapper,
.column-8 .ds-hero .wrapper {
display: flex;
align-items: flex-start; }
.column-5 .ds-hero .wrapper img,
.column-6 .ds-hero .wrapper img,
.column-7 .ds-hero .wrapper img,
.column-8 .ds-hero .wrapper img {
width: 50%;
height: auto; }
.column-5 .ds-hero .wrapper .meta,
.column-6 .ds-hero .wrapper .meta,
.column-7 .ds-hero .wrapper .meta,
.column-8 .ds-hero .wrapper .meta {
width: 50%; }
.column-5 .ds-hero .cards,
.column-6 .ds-hero .cards,
.column-7 .ds-hero .cards,
.column-8 .ds-hero .cards {
display: flex;
flex-wrap: wrap; }
.column-5 .ds-hero .cards .ds-card,
.column-6 .ds-hero .cards .ds-card,
.column-7 .ds-hero .cards .ds-card,
.column-8 .ds-hero .cards .ds-card {
width: 50%;
padding: 12px; }
.column-5 .ds-hero .cards .ds-card:nth-child(odd),
.column-6 .ds-hero .cards .ds-card:nth-child(odd),
.column-7 .ds-hero .cards .ds-card:nth-child(odd),
.column-8 .ds-hero .cards .ds-card:nth-child(odd) {
padding: 12px 12px 12px 0; }
.column-5 .ds-hero .cards .ds-card:nth-child(even),
.column-6 .ds-hero .cards .ds-card:nth-child(even),
.column-7 .ds-hero .cards .ds-card:nth-child(even),
.column-8 .ds-hero .cards .ds-card:nth-child(even) {
padding: 12px 0 12px 12px; }
.column-9 .ds-hero .wrapper,
.column-10 .ds-hero .wrapper,
.column-11 .ds-hero .wrapper,
.column-12 .ds-hero .wrapper {
display: flex;
align-items: flex-start; }
.column-9 .ds-hero .wrapper img,
.column-10 .ds-hero .wrapper img,
.column-11 .ds-hero .wrapper img,
.column-12 .ds-hero .wrapper img {
width: 67%; }
.column-9 .ds-hero .wrapper .meta,
.column-10 .ds-hero .wrapper .meta,
.column-11 .ds-hero .wrapper .meta,
.column-12 .ds-hero .wrapper .meta {
width: 33%;
padding: 24px; }
.column-9 .ds-hero .wrapper .meta header,
.column-10 .ds-hero .wrapper .meta header,
.column-11 .ds-hero .wrapper .meta header,
.column-12 .ds-hero .wrapper .meta header {
font-size: 22px; }
.column-9 .ds-hero .wrapper .meta p,
.column-10 .ds-hero .wrapper .meta p,
.column-11 .ds-hero .wrapper .meta p,
.column-12 .ds-hero .wrapper .meta p {
font-size: 15px; }
.column-9 .ds-hero .cards,
.column-10 .ds-hero .cards,
.column-11 .ds-hero .cards,
.column-12 .ds-hero .cards {
display: flex; }
.column-9 .ds-hero .cards .ds-card,
.column-10 .ds-hero .cards .ds-card,
.column-11 .ds-hero .cards .ds-card,
.column-12 .ds-hero .cards .ds-card {
width: 25%;
padding: 12px; }
.column-9 .ds-hero .cards .ds-card .meta,
.column-10 .ds-hero .cards .ds-card .meta,
.column-11 .ds-hero .cards .ds-card .meta,
.column-12 .ds-hero .cards .ds-card .meta {
padding: 0; }
.column-9 .ds-hero .cards .ds-card:first-child,
.column-10 .ds-hero .cards .ds-card:first-child,
.column-11 .ds-hero .cards .ds-card:first-child,
.column-12 .ds-hero .cards .ds-card:first-child {
padding: 12px 12px 12px 0; }
.column-9 .ds-hero .cards .ds-card:last-child,
.column-10 .ds-hero .cards .ds-card:last-child,
.column-11 .ds-hero .cards .ds-card:last-child,
.column-12 .ds-hero .cards .ds-card:last-child {
padding: 12px 0 12px 12px; }
.ds-section-title ul {
margin: 0;
padding: 0; }
.section-title ul li {
.ds-section-title ul li {
display: inline-block; }
.section-title ul li::after {
.ds-section-title ul li::after {
content: '•';
padding: 8px; }
.section-title ul li:last-child::after {
.ds-section-title ul li:last-child::after {
content: none; }
.section-title .ds-more-recommendations::after {
.ds-section-title .ds-more-recommendations::after {
background: url("../data/content/assets/topic-show-more-12.svg") no-repeat center center;
content: '';
-moz-context-properties: fill;
@ -1813,6 +1935,30 @@ main {
vertical-align: top;
width: 12px; }
.ds-top-sites .top-sites {
padding: 0; }
.ds-top-sites .top-sites .section-top-bar {
display: none; }
.ds-card img {
width: 100%;
border: 0.5px solid rgba(0, 0, 0, 0.1);
box-sizing: border-box;
border-radius: 4px; }
.ds-card .meta {
padding: 16px; }
.ds-card header {
line-height: 24px;
font-size: 17px;
color: #0C0C0D; }
.ds-card p {
font-size: 13px;
line-height: 20px;
color: #737373; }
.ASRouterButton {
font-weight: 600;
font-size: 14px;

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

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

@ -1073,9 +1073,22 @@ main {
.search-active .search-handoff-button {
border: 1px solid var(--newtab-textbox-focus-color);
box-shadow: var(--newtab-textbox-focus-boxshadow); }
.search-handoff-button:focus .fake-caret,
.search-active .search-handoff-button .fake-caret {
display: block; }
.search-hidden .search-handoff-button {
opacity: 0;
visibility: hidden; }
.search-handoff-button .fake-editable:focus {
outline: none;
caret-color: transparent; }
.search-handoff-button .fake-editable {
color: transparent;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0; }
.search-handoff-button .fake-textbox {
opacity: 0.54;
text-align: start; }
@ -1092,8 +1105,6 @@ main {
@keyframes caret-animation {
to {
visibility: hidden; } }
.search-active .search-handoff-button .fake-caret {
display: block; }
@media (min-height: 701px) {
.fixed-search main {
@ -1704,6 +1715,8 @@ main {
font-size: 16px;
margin-bottom: 20px;
border: 1px solid var(--newtab-border-secondary-color); }
.asrouter-admin .ds-component {
margin-bottom: 20px; }
.pocket-logged-in-cta {
font-size: 13px;
@ -1752,61 +1765,170 @@ main {
.more-recommendations:dir(rtl)::after {
transform: scaleX(-1); }
.discovery-stream.layout {
.discovery-stream.ds-layout {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-column-gap: 10px;
grid-column-gap: 48px;
grid-row-gap: 10px; }
.discovery-stream.layout .column-12 {
.discovery-stream.ds-layout .ds-column-12 {
grid-column-start: auto;
grid-column-end: span 12; }
.discovery-stream.layout .column-11 {
.discovery-stream.ds-layout .ds-column-11 {
grid-column-start: auto;
grid-column-end: span 11; }
.discovery-stream.layout .column-10 {
.discovery-stream.ds-layout .ds-column-10 {
grid-column-start: auto;
grid-column-end: span 10; }
.discovery-stream.layout .column-9 {
.discovery-stream.ds-layout .ds-column-9 {
grid-column-start: auto;
grid-column-end: span 9; }
.discovery-stream.layout .column-8 {
.discovery-stream.ds-layout .ds-column-8 {
grid-column-start: auto;
grid-column-end: span 8; }
.discovery-stream.layout .column-7 {
.discovery-stream.ds-layout .ds-column-7 {
grid-column-start: auto;
grid-column-end: span 7; }
.discovery-stream.layout .column-6 {
.discovery-stream.ds-layout .ds-column-6 {
grid-column-start: auto;
grid-column-end: span 6; }
.discovery-stream.layout .column-5 {
.discovery-stream.ds-layout .ds-column-5 {
grid-column-start: auto;
grid-column-end: span 5; }
.discovery-stream.layout .column-4 {
.discovery-stream.ds-layout .ds-column-4 {
grid-column-start: auto;
grid-column-end: span 4; }
.discovery-stream.layout .column-3 {
.discovery-stream.ds-layout .ds-column-3 {
grid-column-start: auto;
grid-column-end: span 3; }
.discovery-stream.layout .column-2 {
.discovery-stream.ds-layout .ds-column-2 {
grid-column-start: auto;
grid-column-end: span 2; }
.discovery-stream.layout .column-1 {
.discovery-stream.ds-layout .ds-column-1 {
grid-column-start: auto;
grid-column-end: span 1; }
.discovery-stream.ds-layout .ds-column {
display: grid;
grid-row-gap: 10px; }
.section-title ul {
.ds-hero .wrapper img {
width: 100%;
height: auto; }
.ds-hero .wrapper .meta header {
font-size: 22px; }
.ds-hero .wrapper .meta p {
font-size: 13px; }
.column-5 .ds-hero .wrapper,
.column-6 .ds-hero .wrapper,
.column-7 .ds-hero .wrapper,
.column-8 .ds-hero .wrapper {
display: flex;
align-items: flex-start; }
.column-5 .ds-hero .wrapper img,
.column-6 .ds-hero .wrapper img,
.column-7 .ds-hero .wrapper img,
.column-8 .ds-hero .wrapper img {
width: 50%;
height: auto; }
.column-5 .ds-hero .wrapper .meta,
.column-6 .ds-hero .wrapper .meta,
.column-7 .ds-hero .wrapper .meta,
.column-8 .ds-hero .wrapper .meta {
width: 50%; }
.column-5 .ds-hero .cards,
.column-6 .ds-hero .cards,
.column-7 .ds-hero .cards,
.column-8 .ds-hero .cards {
display: flex;
flex-wrap: wrap; }
.column-5 .ds-hero .cards .ds-card,
.column-6 .ds-hero .cards .ds-card,
.column-7 .ds-hero .cards .ds-card,
.column-8 .ds-hero .cards .ds-card {
width: 50%;
padding: 12px; }
.column-5 .ds-hero .cards .ds-card:nth-child(odd),
.column-6 .ds-hero .cards .ds-card:nth-child(odd),
.column-7 .ds-hero .cards .ds-card:nth-child(odd),
.column-8 .ds-hero .cards .ds-card:nth-child(odd) {
padding: 12px 12px 12px 0; }
.column-5 .ds-hero .cards .ds-card:nth-child(even),
.column-6 .ds-hero .cards .ds-card:nth-child(even),
.column-7 .ds-hero .cards .ds-card:nth-child(even),
.column-8 .ds-hero .cards .ds-card:nth-child(even) {
padding: 12px 0 12px 12px; }
.column-9 .ds-hero .wrapper,
.column-10 .ds-hero .wrapper,
.column-11 .ds-hero .wrapper,
.column-12 .ds-hero .wrapper {
display: flex;
align-items: flex-start; }
.column-9 .ds-hero .wrapper img,
.column-10 .ds-hero .wrapper img,
.column-11 .ds-hero .wrapper img,
.column-12 .ds-hero .wrapper img {
width: 67%; }
.column-9 .ds-hero .wrapper .meta,
.column-10 .ds-hero .wrapper .meta,
.column-11 .ds-hero .wrapper .meta,
.column-12 .ds-hero .wrapper .meta {
width: 33%;
padding: 24px; }
.column-9 .ds-hero .wrapper .meta header,
.column-10 .ds-hero .wrapper .meta header,
.column-11 .ds-hero .wrapper .meta header,
.column-12 .ds-hero .wrapper .meta header {
font-size: 22px; }
.column-9 .ds-hero .wrapper .meta p,
.column-10 .ds-hero .wrapper .meta p,
.column-11 .ds-hero .wrapper .meta p,
.column-12 .ds-hero .wrapper .meta p {
font-size: 15px; }
.column-9 .ds-hero .cards,
.column-10 .ds-hero .cards,
.column-11 .ds-hero .cards,
.column-12 .ds-hero .cards {
display: flex; }
.column-9 .ds-hero .cards .ds-card,
.column-10 .ds-hero .cards .ds-card,
.column-11 .ds-hero .cards .ds-card,
.column-12 .ds-hero .cards .ds-card {
width: 25%;
padding: 12px; }
.column-9 .ds-hero .cards .ds-card .meta,
.column-10 .ds-hero .cards .ds-card .meta,
.column-11 .ds-hero .cards .ds-card .meta,
.column-12 .ds-hero .cards .ds-card .meta {
padding: 0; }
.column-9 .ds-hero .cards .ds-card:first-child,
.column-10 .ds-hero .cards .ds-card:first-child,
.column-11 .ds-hero .cards .ds-card:first-child,
.column-12 .ds-hero .cards .ds-card:first-child {
padding: 12px 12px 12px 0; }
.column-9 .ds-hero .cards .ds-card:last-child,
.column-10 .ds-hero .cards .ds-card:last-child,
.column-11 .ds-hero .cards .ds-card:last-child,
.column-12 .ds-hero .cards .ds-card:last-child {
padding: 12px 0 12px 12px; }
.ds-section-title ul {
margin: 0;
padding: 0; }
.section-title ul li {
.ds-section-title ul li {
display: inline-block; }
.section-title ul li::after {
.ds-section-title ul li::after {
content: '•';
padding: 8px; }
.section-title ul li:last-child::after {
.ds-section-title ul li:last-child::after {
content: none; }
.section-title .ds-more-recommendations::after {
.ds-section-title .ds-more-recommendations::after {
background: url("../data/content/assets/topic-show-more-12.svg") no-repeat center center;
content: '';
-moz-context-properties: fill;
@ -1816,6 +1938,30 @@ main {
vertical-align: top;
width: 12px; }
.ds-top-sites .top-sites {
padding: 0; }
.ds-top-sites .top-sites .section-top-bar {
display: none; }
.ds-card img {
width: 100%;
border: 0.5px solid rgba(0, 0, 0, 0.1);
box-sizing: border-box;
border-radius: 4px; }
.ds-card .meta {
padding: 16px; }
.ds-card header {
line-height: 24px;
font-size: 17px;
color: #0C0C0D; }
.ds-card p {
font-size: 13px;
line-height: 20px;
color: #737373; }
.ASRouterButton {
font-weight: 600;
font-size: 14px;

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

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

@ -1070,9 +1070,22 @@ main {
.search-active .search-handoff-button {
border: 1px solid var(--newtab-textbox-focus-color);
box-shadow: var(--newtab-textbox-focus-boxshadow); }
.search-handoff-button:focus .fake-caret,
.search-active .search-handoff-button .fake-caret {
display: block; }
.search-hidden .search-handoff-button {
opacity: 0;
visibility: hidden; }
.search-handoff-button .fake-editable:focus {
outline: none;
caret-color: transparent; }
.search-handoff-button .fake-editable {
color: transparent;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0; }
.search-handoff-button .fake-textbox {
opacity: 0.54;
text-align: start; }
@ -1089,8 +1102,6 @@ main {
@keyframes caret-animation {
to {
visibility: hidden; } }
.search-active .search-handoff-button .fake-caret {
display: block; }
@media (min-height: 701px) {
.fixed-search main {
@ -1701,6 +1712,8 @@ main {
font-size: 16px;
margin-bottom: 20px;
border: 1px solid var(--newtab-border-secondary-color); }
.asrouter-admin .ds-component {
margin-bottom: 20px; }
.pocket-logged-in-cta {
font-size: 13px;
@ -1749,61 +1762,170 @@ main {
.more-recommendations:dir(rtl)::after {
transform: scaleX(-1); }
.discovery-stream.layout {
.discovery-stream.ds-layout {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-column-gap: 10px;
grid-column-gap: 48px;
grid-row-gap: 10px; }
.discovery-stream.layout .column-12 {
.discovery-stream.ds-layout .ds-column-12 {
grid-column-start: auto;
grid-column-end: span 12; }
.discovery-stream.layout .column-11 {
.discovery-stream.ds-layout .ds-column-11 {
grid-column-start: auto;
grid-column-end: span 11; }
.discovery-stream.layout .column-10 {
.discovery-stream.ds-layout .ds-column-10 {
grid-column-start: auto;
grid-column-end: span 10; }
.discovery-stream.layout .column-9 {
.discovery-stream.ds-layout .ds-column-9 {
grid-column-start: auto;
grid-column-end: span 9; }
.discovery-stream.layout .column-8 {
.discovery-stream.ds-layout .ds-column-8 {
grid-column-start: auto;
grid-column-end: span 8; }
.discovery-stream.layout .column-7 {
.discovery-stream.ds-layout .ds-column-7 {
grid-column-start: auto;
grid-column-end: span 7; }
.discovery-stream.layout .column-6 {
.discovery-stream.ds-layout .ds-column-6 {
grid-column-start: auto;
grid-column-end: span 6; }
.discovery-stream.layout .column-5 {
.discovery-stream.ds-layout .ds-column-5 {
grid-column-start: auto;
grid-column-end: span 5; }
.discovery-stream.layout .column-4 {
.discovery-stream.ds-layout .ds-column-4 {
grid-column-start: auto;
grid-column-end: span 4; }
.discovery-stream.layout .column-3 {
.discovery-stream.ds-layout .ds-column-3 {
grid-column-start: auto;
grid-column-end: span 3; }
.discovery-stream.layout .column-2 {
.discovery-stream.ds-layout .ds-column-2 {
grid-column-start: auto;
grid-column-end: span 2; }
.discovery-stream.layout .column-1 {
.discovery-stream.ds-layout .ds-column-1 {
grid-column-start: auto;
grid-column-end: span 1; }
.discovery-stream.ds-layout .ds-column {
display: grid;
grid-row-gap: 10px; }
.section-title ul {
.ds-hero .wrapper img {
width: 100%;
height: auto; }
.ds-hero .wrapper .meta header {
font-size: 22px; }
.ds-hero .wrapper .meta p {
font-size: 13px; }
.column-5 .ds-hero .wrapper,
.column-6 .ds-hero .wrapper,
.column-7 .ds-hero .wrapper,
.column-8 .ds-hero .wrapper {
display: flex;
align-items: flex-start; }
.column-5 .ds-hero .wrapper img,
.column-6 .ds-hero .wrapper img,
.column-7 .ds-hero .wrapper img,
.column-8 .ds-hero .wrapper img {
width: 50%;
height: auto; }
.column-5 .ds-hero .wrapper .meta,
.column-6 .ds-hero .wrapper .meta,
.column-7 .ds-hero .wrapper .meta,
.column-8 .ds-hero .wrapper .meta {
width: 50%; }
.column-5 .ds-hero .cards,
.column-6 .ds-hero .cards,
.column-7 .ds-hero .cards,
.column-8 .ds-hero .cards {
display: flex;
flex-wrap: wrap; }
.column-5 .ds-hero .cards .ds-card,
.column-6 .ds-hero .cards .ds-card,
.column-7 .ds-hero .cards .ds-card,
.column-8 .ds-hero .cards .ds-card {
width: 50%;
padding: 12px; }
.column-5 .ds-hero .cards .ds-card:nth-child(odd),
.column-6 .ds-hero .cards .ds-card:nth-child(odd),
.column-7 .ds-hero .cards .ds-card:nth-child(odd),
.column-8 .ds-hero .cards .ds-card:nth-child(odd) {
padding: 12px 12px 12px 0; }
.column-5 .ds-hero .cards .ds-card:nth-child(even),
.column-6 .ds-hero .cards .ds-card:nth-child(even),
.column-7 .ds-hero .cards .ds-card:nth-child(even),
.column-8 .ds-hero .cards .ds-card:nth-child(even) {
padding: 12px 0 12px 12px; }
.column-9 .ds-hero .wrapper,
.column-10 .ds-hero .wrapper,
.column-11 .ds-hero .wrapper,
.column-12 .ds-hero .wrapper {
display: flex;
align-items: flex-start; }
.column-9 .ds-hero .wrapper img,
.column-10 .ds-hero .wrapper img,
.column-11 .ds-hero .wrapper img,
.column-12 .ds-hero .wrapper img {
width: 67%; }
.column-9 .ds-hero .wrapper .meta,
.column-10 .ds-hero .wrapper .meta,
.column-11 .ds-hero .wrapper .meta,
.column-12 .ds-hero .wrapper .meta {
width: 33%;
padding: 24px; }
.column-9 .ds-hero .wrapper .meta header,
.column-10 .ds-hero .wrapper .meta header,
.column-11 .ds-hero .wrapper .meta header,
.column-12 .ds-hero .wrapper .meta header {
font-size: 22px; }
.column-9 .ds-hero .wrapper .meta p,
.column-10 .ds-hero .wrapper .meta p,
.column-11 .ds-hero .wrapper .meta p,
.column-12 .ds-hero .wrapper .meta p {
font-size: 15px; }
.column-9 .ds-hero .cards,
.column-10 .ds-hero .cards,
.column-11 .ds-hero .cards,
.column-12 .ds-hero .cards {
display: flex; }
.column-9 .ds-hero .cards .ds-card,
.column-10 .ds-hero .cards .ds-card,
.column-11 .ds-hero .cards .ds-card,
.column-12 .ds-hero .cards .ds-card {
width: 25%;
padding: 12px; }
.column-9 .ds-hero .cards .ds-card .meta,
.column-10 .ds-hero .cards .ds-card .meta,
.column-11 .ds-hero .cards .ds-card .meta,
.column-12 .ds-hero .cards .ds-card .meta {
padding: 0; }
.column-9 .ds-hero .cards .ds-card:first-child,
.column-10 .ds-hero .cards .ds-card:first-child,
.column-11 .ds-hero .cards .ds-card:first-child,
.column-12 .ds-hero .cards .ds-card:first-child {
padding: 12px 12px 12px 0; }
.column-9 .ds-hero .cards .ds-card:last-child,
.column-10 .ds-hero .cards .ds-card:last-child,
.column-11 .ds-hero .cards .ds-card:last-child,
.column-12 .ds-hero .cards .ds-card:last-child {
padding: 12px 0 12px 12px; }
.ds-section-title ul {
margin: 0;
padding: 0; }
.section-title ul li {
.ds-section-title ul li {
display: inline-block; }
.section-title ul li::after {
.ds-section-title ul li::after {
content: '•';
padding: 8px; }
.section-title ul li:last-child::after {
.ds-section-title ul li:last-child::after {
content: none; }
.section-title .ds-more-recommendations::after {
.ds-section-title .ds-more-recommendations::after {
background: url("../data/content/assets/topic-show-more-12.svg") no-repeat center center;
content: '';
-moz-context-properties: fill;
@ -1813,6 +1935,30 @@ main {
vertical-align: top;
width: 12px; }
.ds-top-sites .top-sites {
padding: 0; }
.ds-top-sites .top-sites .section-top-bar {
display: none; }
.ds-card img {
width: 100%;
border: 0.5px solid rgba(0, 0, 0, 0.1);
box-sizing: border-box;
border-radius: 4px; }
.ds-card .meta {
padding: 16px; }
.ds-card header {
line-height: 24px;
font-size: 17px;
color: #0C0C0D; }
.ds-card p {
font-size: 13px;
line-height: 20px;
color: #737373; }
.ASRouterButton {
font-weight: 600;
font-size: 14px;

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

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

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

@ -205,6 +205,10 @@ const MessageLoaderUtils = {
messages = [];
Cu.reportError(new Error(`Tried to load messages for ${provider.id} but the result was not an Array.`));
}
// Filter out messages we temporarily want to exclude
if (provider.exclude && provider.exclude.length) {
messages = messages.filter(message => !provider.exclude.includes(message.id));
}
const lastUpdated = Date.now();
return {
messages: messages.map(msg => ({weight: 100, ...msg, provider: provider.id}))
@ -213,8 +217,30 @@ const MessageLoaderUtils = {
};
},
/**
* _loadAddonIconInURLBar - load addons-notification icon by displaying
* box containing addons icon in urlbar. See Bug 1513882
*
* @param {XULElement} Target browser element for showing addons icon
*/
_loadAddonIconInURLBar(browser) {
if (!browser) {
return;
}
const chromeDoc = browser.ownerDocument;
let notificationPopupBox = chromeDoc.getElementById("notification-popup-box");
if (!notificationPopupBox) {
return;
}
if (notificationPopupBox.style.display === "none" ||
notificationPopupBox.style.display === "") {
notificationPopupBox.style.display = "block";
}
},
async installAddonFromURL(browser, url) {
try {
MessageLoaderUtils._loadAddonIconInURLBar(browser);
const aUri = Services.io.newURI(url);
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
@ -340,15 +366,10 @@ class _ASRouter {
}
}
// Group existing blocked messages with messages blocked through preferences
const excludeList = ASRouterPreferences.providers.filter(p => p.exclude)
.reduce((blocked, p) => blocked.concat(p.exclude), this.state.messageBlockList);
this.setState(prevState => ({
providers,
// Clear any messages from removed providers
messages: [...prevState.messages.filter(message => providerIDs.includes(message.provider))],
messageBlockList: excludeList,
}));
}

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

@ -12,6 +12,7 @@ const {PersistentCache} = ChromeUtils.import("resource://activity-stream/lib/Per
const CACHE_KEY = "discovery_stream";
const LAYOUT_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
const COMPONENT_FEEDS_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
const CONFIG_PREF_NAME = "browser.newtabpage.activity-stream.discoverystream.config";
this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
@ -69,7 +70,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
const response = await fetch(endpoint, {credentials: "omit"});
if (!response.ok) {
// istanbul ignore next
throw new Error(`Stories endpoint returned unexpected status: ${response.status}`);
throw new Error(`Layout endpoint returned unexpected status: ${response.status}`);
}
return response.json();
} catch (error) {
@ -80,7 +81,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
return null;
}
async loadCachedData() {
async loadLayout() {
const cachedData = await this.cache.get() || {};
let {layout: layoutResponse} = cachedData;
if (!layoutResponse || !(Date.now() - layoutResponse._timestamp < LAYOUT_UPDATE_TIME)) {
@ -104,8 +105,65 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
}
}
async loadComponentFeeds() {
const {DiscoveryStream} = this.store.getState();
const newFeeds = {};
if (DiscoveryStream && DiscoveryStream.layout) {
for (let row of DiscoveryStream.layout) {
if (!row || !row.components) {
continue;
}
for (let component of row.components) {
if (component && component.feed) {
const {url} = component.feed;
newFeeds[url] = await this.getComponentFeed(url);
}
}
}
await this.cache.set("feeds", newFeeds);
this.store.dispatch(ac.BroadcastToContent({type: at.DISCOVERY_STREAM_FEEDS_UPDATE, data: newFeeds}));
}
}
async getComponentFeed(feedUrl) {
const cachedData = await this.cache.get() || {};
const {feeds} = cachedData;
let feed = feeds && feeds[feedUrl];
if (!feed || !(Date.now() - feed.lastUpdated < COMPONENT_FEEDS_UPDATE_TIME)) {
const feedResponse = await this.fetchComponentFeed(feedUrl);
if (feedResponse) {
feed = {
lastUpdated: Date.now(),
data: feedResponse,
};
} else {
Cu.reportError("No response for feed");
}
}
return feed;
}
async fetchComponentFeed(feedUrl) {
try {
const response = await fetch(feedUrl, {credentials: "omit"});
if (!response.ok) {
// istanbul ignore next
throw new Error(`Component feed endpoint returned unexpected status: ${response.status}`);
}
return response.json();
} catch (error) {
// istanbul ignore next
Cu.reportError(`Failed to fetch Component feed: ${error.message}`);
}
// istanbul ignore next
return null;
}
async enable() {
await this.loadCachedData();
await this.loadLayout();
await this.loadComponentFeeds();
this.loaded = true;
}
@ -118,6 +176,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
async clearCache() {
await this.cache.set("layout", {});
await this.cache.set("feeds", {});
}
async onPrefChange() {

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

@ -286,21 +286,32 @@ class PlacesFeed {
handoffSearchToAwesomebar({_target, data, meta}) {
const urlBar = _target.browser.ownerGlobal.gURLBar;
if (!data.hiddenFocus) {
if (!data.hiddenFocus && !data.text) {
// Do a normal focus of awesomebar and reset the in content search (remove fake focus styles).
urlBar.focus();
this.store.dispatch(ac.OnlyToOneContent({type: at.SHOW_SEARCH}, meta.fromTarget));
// We are done here. return early.
return;
}
// Focus the awesomebar without the style changes.
urlBar.hiddenFocus();
const onKeydown = () => {
// Once the user starts typing, we want to hide the in content search box
// and show the focus styles on the awesomebar.
if (data.text) {
// Pass the provided text to the awesomebar.
urlBar.search(data.text);
this.store.dispatch(ac.OnlyToOneContent({type: at.HIDE_SEARCH}, meta.fromTarget));
urlBar.removeHiddenFocus();
urlBar.removeEventListener("keydown", onKeydown);
} else {
// Focus the awesomebar without the style changes.
urlBar.hiddenFocus();
}
const onKeydown = event => {
// We only care about key strokes that will produce a character.
if (event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey) {
// Once the user starts typing, we want to hide the in content search box
// and show the focus styles on the awesomebar.
this.store.dispatch(ac.OnlyToOneContent({type: at.HIDE_SEARCH}, meta.fromTarget));
urlBar.removeHiddenFocus();
urlBar.removeEventListener("keydown", onKeydown);
}
};
const onDone = () => {
// When done, let's cleanup everything.

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

@ -40,7 +40,7 @@ confirm_history_delete_p1=Imoko ni imito kwanyo nyig jami weng me potbuk man ki
# the same dialog as confirm_history_delete_p1. "This action" refers to deleting a
# page from history.
confirm_history_delete_notice_p2=Pe ki twero gonyo tic man.
menu_action_save_to_pocket=Gwoki i jaba
menu_action_save_to_pocket=Gwok i Pocket
menu_action_delete_pocket=Kwany ki ii Pocket
menu_action_archive_pocket=Kan i Pocket
@ -141,7 +141,6 @@ pocket_read_more=Lok macuk gi lamal:
# LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
# end of the list of popular topic links.
pocket_read_even_more=Nen Lok mapol
pocket_learn_more=Nong ngec mapol
pocket_how_it_works=Kit ma tiyo kwede
pocket_cta_button=Nong Pocket
pocket_cta_text=Gwok lok ma imaro ii Pocket, ka i pik wii ki jami me akwana ma mako wii.

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

@ -144,9 +144,9 @@ pocket_read_more=জনপ্রিয় বিষয়:
# end of the list of popular topic links.
pocket_read_even_more=আরও গল্প দেখুন
pocket_more_reccommendations=আরও সুপারিশ
pocket_learn_more=আরও জানুন
pocket_how_it_works=কিভাবে এটা কাজ করে
pocket_cta_button=Pocket ব্যবহার করুন
pocket_cta_text=Pocket এ আপনার পছন্দের গল্পগুলো সংরক্ষণ করুন, এবং চমৎকার সব লেখা পড়ে আপনার মনের ইন্ধন যোগান।
highlights_empty_state=ব্রাউজি করা শুরু করুন, এবং কিছু গুরুত্বপূর্ণ নিবন্ধ, ভিডিও, এবং আপনি সম্প্রতি পরিদর্শন বা বুকমার্ক করেছেন এমন কিছু পৃষ্ঠা আমরা এখানে প্রদর্শন করব।
# LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,

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

@ -144,10 +144,7 @@ pocket_read_more=موضوع‌های محبوب:
# end of the list of popular topic links.
pocket_read_even_more=مشاهده داستان‌های بیشتر
pocket_more_reccommendations=توصیه‌های بیشتر
pocket_learn_more=بیشتر بدانید
pocket_how_it_works=این چجوری کار میکنه
pocket_cta_button=دریافت پاکت
pocket_cta_text=داستان‌هایی را که دوست دارید در پاکت خود ذخیره کنید و ذهن خود را با خواندنی‌های جذاب پرورش دهید.
highlights_empty_state=مرور کردن را شروع کنید و شاهد تعداد زیادی مقاله، فیلم و صفحات خوبی باشید که اخیر مشاهده کرده اید یا نشانگ گذاری کرده اید.
# LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,

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

@ -38,7 +38,6 @@ confirm_history_delete_p1=Վստահ եք, որ ցանկանում եք ջնջե
# the same dialog as confirm_history_delete_p1. "This action" refers to deleting a
# page from history.
confirm_history_delete_notice_p2=Այս գործողությունը չի կարող վերացվել.
menu_action_save_to_pocket=Պահպանեք գրպանում
# LOCALIZATION NOTE (menu_action_show_file_*): These are platform specific strings
# found in the context menu of an item that has been downloaded. The intention behind

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

@ -144,7 +144,6 @@ pocket_read_more=人気のトピック:
# end of the list of popular topic links.
pocket_read_even_more=他の記事を見る
pocket_more_reccommendations=他のおすすめ
pocket_learn_more=詳細
pocket_how_it_works=使い方
pocket_cta_button=Pocket を入手
pocket_cta_text=お気に入りに記事を Pocket に保存して、魅力的な読み物を思う存分楽しみましょう。

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

@ -144,7 +144,6 @@ pocket_read_more=人気のトピック:
# end of the list of popular topic links.
pocket_read_even_more=他の記事を見る
pocket_more_reccommendations=他のおすすめ
pocket_learn_more=詳細
pocket_how_it_works=使い方
pocket_cta_button=Pocket を入手
pocket_cta_text=お気に入りに記事を Pocket に保存して、魅力的な読み物を思う存分楽しみましょう。

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

@ -21,7 +21,7 @@ window.gActivityStreamStrings = {
"menu_action_unpin": "War",
"confirm_history_delete_p1": "Imoko ni imito kwanyo nyig jami weng me potbuk man ki i gin mukato mamegi?",
"confirm_history_delete_notice_p2": "Pe ki twero gonyo tic man.",
"menu_action_save_to_pocket": "Gwoki i jaba",
"menu_action_save_to_pocket": "Gwok i Pocket",
"menu_action_delete_pocket": "Kwany ki ii Pocket",
"menu_action_archive_pocket": "Kan i Pocket",
"menu_action_show_file_mac_os": "Nyut i Gin nongo",
@ -107,6 +107,5 @@ window.gActivityStreamStrings = {
"firstrun_privacy_notice": "Ngec me mung",
"firstrun_continue_to_login": "Mede",
"firstrun_skip_login": "Kal citep man",
"context_menu_title": "Yab jami ayera",
"pocket_learn_more": "Nong ngec mapol"
"context_menu_title": "Yab jami ayera"
};

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

@ -77,7 +77,7 @@ window.gActivityStreamStrings = {
"pocket_more_reccommendations": "আরও সুপারিশ",
"pocket_how_it_works": "কিভাবে এটা কাজ করে",
"pocket_cta_button": "Pocket ব্যবহার করুন",
"pocket_cta_text": "আপনার পছন্দের গল্পগুলো Pocket এ সংরক্ষণ করুন, এবং আকর্ষণীয় মনে পড়ুন।",
"pocket_cta_text": "Pocket এ আপনার পছন্দের গল্পগুলো সংরক্ষণ করুন, এবং চমৎকার সব লেখা পড়ে আপনার মনের ইন্ধন যোগান।",
"highlights_empty_state": "ব্রাউজি করা শুরু করুন, এবং কিছু গুরুত্বপূর্ণ নিবন্ধ, ভিডিও, এবং আপনি সম্প্রতি পরিদর্শন বা বুকমার্ক করেছেন এমন কিছু পৃষ্ঠা আমরা এখানে প্রদর্শন করব।",
"topstories_empty_state": "কিছু একটা ঠিক নেই। {provider} এর শীর্ষ গল্পগুলো পেতে কিছুক্ষণ পর আবার দেখুন। অপেক্ষা করতে চান না? বিশ্বের সেরা গল্পগুলো পেতে কোন জনপ্রিয় বিষয় নির্বাচন করুন।",
"manual_migration_explanation2": "অন্য ব্রাউজার থেকে আনা বুকমার্ক, ইতিহাস এবং পাসওয়ার্ডগুলির সাথে ফায়ারফক্স ব্যবহার করে দেখুন।",
@ -108,5 +108,5 @@ window.gActivityStreamStrings = {
"firstrun_continue_to_login": "চালিয়ে যান",
"firstrun_skip_login": "এই ধাপটি বাদ দিন",
"context_menu_title": "মেনু খুলুন",
"pocket_learn_more": "আর জানুন"
"pocket_learn_more": "আর জানুন"
};

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

@ -76,8 +76,8 @@ window.gActivityStreamStrings = {
"pocket_read_even_more": "مشاهده داستان‌های بیشتر",
"pocket_more_reccommendations": "توصیه‌های بیشتر",
"pocket_how_it_works": "این چجوری کار میکنه",
"pocket_cta_button": "دریافت پاکت",
"pocket_cta_text": "داستان‌هایی را که دوست دارید در پاکت خود ذخیره کنید و ذهن خود را با خواندنی‌های جذاب پرورش دهید.",
"pocket_cta_button": "Get Pocket",
"pocket_cta_text": "Save the stories you love in Pocket, and fuel your mind with fascinating reads.",
"highlights_empty_state": "مرور کردن را شروع کنید و شاهد تعداد زیادی مقاله، فیلم و صفحات خوبی باشید که اخیر مشاهده کرده اید یا نشانگ گذاری کرده اید.",
"topstories_empty_state": "فعلا تموم شد. بعدا دوباره سر بزن تا مطالب جدید از {provider} ببینی. نمی‌تونی صبر کنی؟ یک موضوع محبوب رو انتخاب کن تا مطالب جالب مرتبط از سراسر دنیا رو پیدا کنی.",
"manual_migration_explanation2": "فایرفاکس را با نشانک‌ها،‌ تاریخچه‌ها و کلمات عبور از سایر مرورگر ها تجربه کنید.",
@ -107,6 +107,5 @@ window.gActivityStreamStrings = {
"firstrun_privacy_notice": "نکات حریم‌خصوصی",
"firstrun_continue_to_login": "ادامه",
"firstrun_skip_login": "پرش از این مرحله",
"context_menu_title": "باز کردن منو",
"pocket_learn_more": "بیشتر بدانید"
"context_menu_title": "باز کردن منو"
};

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

@ -21,7 +21,7 @@ window.gActivityStreamStrings = {
"menu_action_unpin": "Ապամրացնել",
"confirm_history_delete_p1": "Վստահ եք, որ ցանկանում եք ջնջել այս էջի ամեն մի օրինակ ձեր պատմությունից?",
"confirm_history_delete_notice_p2": "Այս գործողությունը չի կարող վերացվել.",
"menu_action_save_to_pocket": "Պահպանեք գրպանում",
"menu_action_save_to_pocket": "Save to Pocket",
"menu_action_delete_pocket": "Delete from Pocket",
"menu_action_archive_pocket": "Archive in Pocket",
"menu_action_show_file_mac_os": "Show in Finder",

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

@ -107,6 +107,5 @@ window.gActivityStreamStrings = {
"firstrun_privacy_notice": "プライバシーに関する通知",
"firstrun_continue_to_login": "続ける",
"firstrun_skip_login": "この手順をスキップ",
"context_menu_title": "メニューを開きます",
"pocket_learn_more": "詳細"
"context_menu_title": "メニューを開きます"
};

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

@ -107,6 +107,5 @@ window.gActivityStreamStrings = {
"firstrun_privacy_notice": "プライバシーに関する通知",
"firstrun_continue_to_login": "続ける",
"firstrun_skip_login": "この手順をスキップ",
"context_menu_title": "メニューを開きます",
"pocket_learn_more": "詳細"
"context_menu_title": "メニューを開きます"
};

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

@ -130,14 +130,6 @@ describe("ASRouter", () => {
assert.deepEqual(Router.state.messageBlockList, ["foo"]);
});
it("should set state.messageBlockList to the block list in ASRouterPreferences", async () => {
setMessageProviderPref([{id: "onboarding", type: "local", exclude: ["RTAMO"]}]);
messageBlockList = ["foo"];
Router = new _ASRouter();
await Router.init(channel, createFakeStorage(), dispatchStub);
assert.deepEqual(Router.state.messageBlockList, ["foo", "RTAMO"]);
});
it("should set state.messageImpressions to the messageImpressions object in persistent storage", async () => {
// Note that messageImpressions are only kept if a message exists in router and has a .frequency property,
// otherwise they will be cleaned up by .cleanupImpressions()

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

@ -36,6 +36,14 @@ describe("MessageLoaderUtils", () => {
assert.propertyVal(message, "id", "foo");
assert.propertyVal(message, "provider", "provider123");
});
it("should filter out local messages listed in the `exclude` field", async () => {
const sourceMessage = {id: "foo"};
const provider = {id: "provider123", type: "local", messages: [sourceMessage], exclude: ["foo"]};
const result = await MessageLoaderUtils.loadMessagesForProvider(provider, FAKE_STORAGE);
assert.lengthOf(result.messages, 0);
});
it("should return messages for remote provider", async () => {
const sourceMessage = {id: "foo"};
fetchStub.resolves({ok: true, status: 200, json: () => Promise.resolve({messages: [sourceMessage]}), headers: FAKE_RESPONSE_HEADERS});
@ -225,6 +233,45 @@ describe("MessageLoaderUtils", () => {
});
});
describe("#_loadAddonIconInURLBar", () => {
let sandbox;
let notificationContainerEl;
let browser;
let getContainerStub;
beforeEach(() => {
sandbox = sinon.createSandbox();
notificationContainerEl = {style: {}};
browser = {ownerDocument: {getElementById() { return {}; }}};
getContainerStub = sandbox.stub(browser.ownerDocument, "getElementById");
});
afterEach(() => {
sandbox.restore();
});
it("should return for empty args", () => {
MessageLoaderUtils._loadAddonIconInURLBar();
assert.notCalled(getContainerStub);
});
it("should return if notification popup box not found", () => {
getContainerStub.returns(null);
MessageLoaderUtils._loadAddonIconInURLBar(browser);
assert.calledOnce(getContainerStub);
});
it("should unhide notification popup box with display style as none", () => {
getContainerStub.returns(notificationContainerEl);
notificationContainerEl.style.display = "none";
MessageLoaderUtils._loadAddonIconInURLBar(browser);
assert.calledWith(browser.ownerDocument.getElementById, "notification-popup-box");
assert.equal(notificationContainerEl.style.display, "block");
});
it("should unhide notification popup box with display style empty", () => {
getContainerStub.returns(notificationContainerEl);
notificationContainerEl.style.display = "";
MessageLoaderUtils._loadAddonIconInURLBar(browser);
assert.calledWith(browser.ownerDocument.getElementById, "notification-popup-box");
assert.equal(notificationContainerEl.style.display, "block");
});
});
describe("#installAddonFromURL", () => {
let globals;
let sandbox;
@ -235,6 +282,7 @@ describe("MessageLoaderUtils", () => {
sandbox = sinon.createSandbox();
getInstallStub = sandbox.stub();
installAddonStub = sandbox.stub();
sandbox.stub(MessageLoaderUtils, "_loadAddonIconInURLBar").returns(null);
globals.set("AddonManager", {
getInstallForURL: getInstallStub,
installAddonFromWebpage: installAddonStub,

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

@ -141,6 +141,13 @@ describe("SubmitFormSnippet", () => {
assert.isTrue(wrapper.state().expanded);
assert.isTrue(wrapper.find("form").exists());
});
it("should submit telemetry when the action button is clicked", () => {
assert.isFalse(wrapper.find("form").exists());
wrapper.find(".ASRouterButton").simulate("click");
assert.equal(wrapper.props().sendUserActionTelemetry.firstCall.args[0].value, "scene1-button-learn-more");
});
it("should submit form data when submitted", () => {
sandbox.stub(window, "fetch").resolves(fetchOk);
wrapper.setState({expanded: true});

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

@ -606,7 +606,7 @@ describe("Reducers", () => {
assert.propertyVal(state, "version", data.version);
});
it("should reset to the initial state on a SNIPPETS_RESET action", () => {
const state = Snippets({initalized: true, foo: "bar"}, {type: at.SNIPPETS_RESET});
const state = Snippets({initialized: true, foo: "bar"}, {type: at.SNIPPETS_RESET});
assert.equal(state, INITIAL_STATE.Snippets);
});
it("should set the new blocklist on SNIPPET_BLOCKED", () => {

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

@ -0,0 +1,17 @@
import {TopSites as OldTopSites} from "content-src/components/TopSites/TopSites";
import React from "react";
import {shallow} from "enzyme";
import {_TopSites as TopSites} from "content-src/components/DiscoveryStreamComponents/TopSites/TopSites";
describe("Discovery Stream <TopSites>", () => {
it("should return a wrapper around old TopSites", () => {
const wrapper = shallow(<TopSites />);
const oldTopSites = wrapper.find(OldTopSites);
const dsTopSitesWrapper = wrapper.find(".ds-top-sites");
assert.ok(wrapper.exists());
assert.lengthOf(oldTopSites, 1);
assert.lengthOf(dsTopSitesWrapper, 1);
});
});

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

@ -83,7 +83,7 @@ describe("<Search>", () => {
it("should hand-off search when button is clicked with mouse", () => {
const dispatch = sinon.spy();
const wrapper = shallowWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
wrapper.instance().onSearchHandoffClick({clientX: 101, clientY: 102});
wrapper.find(".search-handoff-button").simulate("click", {clientX: 101, clientY: 102});
assert.calledWith(dispatch, {
data: {hiddenFocus: true},
meta: {from: "ActivityStream:Content", skipLocal: true, to: "ActivityStream:Main"},
@ -94,7 +94,7 @@ describe("<Search>", () => {
it("should hand-off search when button is clicked with keyboard", () => {
const dispatch = sinon.spy();
const wrapper = shallowWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
wrapper.instance().onSearchHandoffClick({clientX: 0, clientY: 0});
wrapper.find(".search-handoff-button").simulate("click", {clientX: 0, clientY: 0});
assert.calledWith(dispatch, {
data: {hiddenFocus: false},
meta: {from: "ActivityStream:Content", skipLocal: true, to: "ActivityStream:Main"},
@ -102,5 +102,49 @@ describe("<Search>", () => {
});
assert.calledWith(dispatch, {type: "FOCUS_SEARCH"});
});
it("should hand-off search when user types", () => {
const dispatch = sinon.spy();
const wrapper = shallowWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
wrapper.find(".search-handoff-button").simulate("keydown", {key: "f"});
assert.calledWith(dispatch, {
data: {text: "f"},
meta: {from: "ActivityStream:Content", skipLocal: true, to: "ActivityStream:Main"},
type: "HANDOFF_SEARCH_TO_AWESOMEBAR",
});
});
it("should NOT hand-off search when user types with with ctrl pressed", () => {
const dispatch = sinon.spy();
const wrapper = shallowWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
wrapper.find(".search-handoff-button").simulate("keydown", {key: "f", ctrlKey: true});
assert.notCalled(dispatch);
});
it("should NOT hand-off search when user types with with alt pressed", () => {
const dispatch = sinon.spy();
const wrapper = shallowWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
wrapper.find(".search-handoff-button").simulate("keydown", {key: "f", altKey: true});
assert.notCalled(dispatch);
});
it("should NOT hand-off search when user types with with meta pressed", () => {
const dispatch = sinon.spy();
const wrapper = shallowWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
wrapper.find(".search-handoff-button").simulate("keydown", {key: "f", metaKey: true});
assert.notCalled(dispatch);
});
it("should hand-off search on paste", () => {
const dispatch = sinon.spy();
const wrapper = mountWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
wrapper.instance()._searchHandoffButton = {contains: () => true};
wrapper.instance().onSearchHandoffPaste({
clipboardData: {
getData: () => "some copied text",
},
preventDefault: () => {},
});
assert.calledWith(dispatch, {
data: {text: "some copied text"},
meta: {from: "ActivityStream:Content", skipLocal: true, to: "ActivityStream:Main"},
type: "HANDOFF_SEARCH_TO_AWESOMEBAR",
});
});
});
});

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

@ -46,7 +46,7 @@ describe("DiscoveryStreamFeed", () => {
});
});
describe("#loadCachedData", () => {
describe("#loadLayout", () => {
it("should fetch data and populate the cache if it is empty", async () => {
const resp = {layout: ["foo", "bar"]};
const fakeCache = {};
@ -55,7 +55,7 @@ describe("DiscoveryStreamFeed", () => {
fetchStub.resolves({ok: true, json: () => Promise.resolve(resp)});
await feed.loadCachedData();
await feed.loadLayout();
assert.calledOnce(fetchStub);
assert.calledWith(feed.cache.set, "layout", resp);
@ -70,7 +70,7 @@ describe("DiscoveryStreamFeed", () => {
fetchStub.resolves({ok: true, json: () => Promise.resolve(resp)});
clock.tick(THIRTY_MINUTES + 1);
await feed.loadCachedData();
await feed.loadLayout();
assert.calledOnce(fetchStub);
assert.calledWith(feed.cache.set, "layout", resp);
@ -82,21 +82,95 @@ describe("DiscoveryStreamFeed", () => {
sandbox.stub(feed.cache, "set").returns(Promise.resolve());
clock.tick(THIRTY_MINUTES - 1);
await feed.loadCachedData();
await feed.loadLayout();
assert.notCalled(fetchStub);
assert.notCalled(feed.cache.set);
});
});
describe("#loadComponentFeeds", () => {
it("should populate feeds cache", async () => {
const fakeComponents = {components: [{feed: {url: "foo.com"}}]};
const fakeLayout = [fakeComponents, {components: [{}]}, {}];
const fakeDiscoveryStream = {DiscoveryStream: {layout: fakeLayout}};
sandbox.stub(feed.store, "getState").returns(fakeDiscoveryStream);
sandbox.stub(feed.cache, "set").returns(Promise.resolve());
const fakeCache = {feeds: {"foo.com": {"lastUpdated": Date.now(), "data": "data"}}};
sandbox.stub(feed.cache, "get").returns(Promise.resolve(fakeCache));
await feed.loadComponentFeeds();
assert.calledWith(feed.cache.set, "feeds", {"foo.com": {"data": "data", "lastUpdated": 0}});
});
});
describe("#getComponentFeed", () => {
it("should fetch fresh data if cache is empty", async () => {
const fakeCache = {};
sandbox.stub(feed.cache, "get").returns(Promise.resolve(fakeCache));
sandbox.stub(feed, "fetchComponentFeed").returns(Promise.resolve("data"));
const feedResp = await feed.getComponentFeed("foo.com");
assert.deepEqual(feedResp.data, "data");
});
it("should fetch fresh data if cache is old", async () => {
const fakeCache = {feeds: {"foo.com": {lastUpdated: Date.now()}}};
sandbox.stub(feed.cache, "get").returns(Promise.resolve(fakeCache));
sandbox.stub(feed, "fetchComponentFeed").returns(Promise.resolve("data"));
clock.tick(THIRTY_MINUTES + 1);
const feedResp = await feed.getComponentFeed("foo.com");
assert.equal(feedResp.data, "data");
});
it("should return data from cache if it is fresh", async () => {
const fakeCache = {feeds: {"foo.com": {lastUpdated: Date.now(), data: "data"}}};
sandbox.stub(feed.cache, "get").returns(Promise.resolve(fakeCache));
sandbox.stub(feed, "fetchComponentFeed").returns(Promise.resolve("old data"));
clock.tick(THIRTY_MINUTES - 1);
const feedResp = await feed.getComponentFeed("foo.com");
assert.equal(feedResp.data, "data");
});
});
describe("#fetchComponentFeed", () => {
it("should return old feed if fetch failed", async () => {
fetchStub.resolves({ok: false, json: () => Promise.resolve({})});
const fakeCache = {feeds: {"foo.com": {lastUpdated: Date.now(), data: "old data"}}};
sandbox.stub(feed.cache, "get").returns(Promise.resolve(fakeCache));
clock.tick(THIRTY_MINUTES + 1);
const feedResp = await feed.getComponentFeed("foo.com");
assert.equal(feedResp.data, "old data");
});
it("should return new feed if fetch succeeds", async () => {
fetchStub.resolves({ok: true, json: () => Promise.resolve("data")});
const fakeCache = {feeds: {"foo.com": {lastUpdated: Date.now(), data: "old data"}}};
sandbox.stub(feed.cache, "get").returns(Promise.resolve(fakeCache));
clock.tick(THIRTY_MINUTES + 1);
const feedResp = await feed.getComponentFeed("foo.com");
assert.equal(feedResp.data, "data");
});
});
describe("#clearCache", () => {
it("should set .layout to {}", async () => {
it("should set .layout and .feeds to {}", async () => {
sandbox.stub(feed.cache, "set").returns(Promise.resolve());
await feed.clearCache();
assert.calledOnce(feed.cache.set);
assert.calledWith(feed.cache.set, "layout", {});
assert.calledTwice(feed.cache.set);
const {firstCall} = feed.cache.set;
const {secondCall} = feed.cache.set;
assert.deepEqual(firstCall.args, ["layout", {}]);
assert.deepEqual(secondCall.args, ["feeds", {}]);
});
});
@ -105,13 +179,14 @@ describe("DiscoveryStreamFeed", () => {
assert.isFalse(feed.loaded);
});
it("should load data, add pref observer, and set .loaded=true if config.enabled is true", async () => {
sandbox.stub(feed.cache, "set").returns(Promise.resolve());
configPrefStub.returns(JSON.stringify({enabled: true}));
sandbox.stub(feed, "loadCachedData").returns(Promise.resolve());
sandbox.stub(feed, "loadLayout").returns(Promise.resolve());
sandbox.stub(global.Services.prefs, "addObserver");
await feed.onAction({type: at.INIT});
assert.calledOnce(feed.loadCachedData);
assert.calledOnce(feed.loadLayout);
assert.calledWith(global.Services.prefs.addObserver, CONFIG_PREF_NAME, feed);
assert.isTrue(feed.loaded);
});
@ -129,7 +204,7 @@ describe("DiscoveryStreamFeed", () => {
});
describe("#onAction: DISCOVERY_STREAM_CONFIG_CHANGE", () => {
it("should call this.loadCachedData if config.enabled changes to true ", async () => {
it("should call this.loadLayout if config.enabled changes to true ", async () => {
sandbox.stub(feed.cache, "set").returns(Promise.resolve());
// First initialize
await feed.onAction({type: at.INIT});
@ -140,14 +215,15 @@ describe("DiscoveryStreamFeed", () => {
configPrefStub.returns(JSON.stringify({enabled: true}));
sandbox.stub(feed, "clearCache").returns(Promise.resolve());
sandbox.stub(feed, "loadCachedData").returns(Promise.resolve());
sandbox.stub(feed, "loadLayout").returns(Promise.resolve());
await feed.onAction({type: at.DISCOVERY_STREAM_CONFIG_CHANGE});
assert.calledOnce(feed.loadCachedData);
assert.calledOnce(feed.loadLayout);
assert.calledOnce(feed.clearCache);
assert.isTrue(feed.loaded);
});
it("should clear the cache if a config change happens and config.enabled is true", async () => {
sandbox.stub(feed.cache, "set").returns(Promise.resolve());
// force clear cached pref value
feed._prefCache = {};
configPrefStub.returns(JSON.stringify({enabled: true}));
@ -157,7 +233,7 @@ describe("DiscoveryStreamFeed", () => {
assert.calledOnce(feed.clearCache);
});
it("should not call this.loadCachedData if config.enabled changes to false", async () => {
it("should not call this.loadLayout if config.enabled changes to false", async () => {
sandbox.stub(feed.cache, "set").returns(Promise.resolve());
// force clear cached pref value
feed._prefCache = {};
@ -169,10 +245,10 @@ describe("DiscoveryStreamFeed", () => {
feed._prefCache = {};
configPrefStub.returns(JSON.stringify({enabled: false}));
sandbox.stub(feed, "clearCache").returns(Promise.resolve());
sandbox.stub(feed, "loadCachedData").returns(Promise.resolve());
sandbox.stub(feed, "loadLayout").returns(Promise.resolve());
await feed.onAction({type: at.DISCOVERY_STREAM_CONFIG_CHANGE});
assert.notCalled(feed.loadCachedData);
assert.notCalled(feed.loadLayout);
assert.calledOnce(feed.clearCache);
assert.isFalse(feed.loaded);
});

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

@ -326,6 +326,7 @@ describe("PlacesFeed", () => {
beforeEach(() => {
fakeUrlBar = {
focus: sinon.spy(),
search: sinon.spy(),
hiddenFocus: sinon.spy(),
removeHiddenFocus: sinon.spy(),
addEventListener: (ev, cb) => {
@ -364,9 +365,21 @@ describe("PlacesFeed", () => {
assert.notCalled(fakeUrlBar.focus);
assert.notCalled(feed.store.dispatch);
// Now call keydown listener.
// Now call keydown listener with "Ctrl".
feed.store.dispatch.resetHistory();
listeners.keydown();
listeners.keydown({key: "Ctrl"});
assert.notCalled(fakeUrlBar.removeHiddenFocus);
assert.notCalled(feed.store.dispatch);
// Now call keydown listener with "Ctrl+f".
feed.store.dispatch.resetHistory();
listeners.keydown({key: "f", ctrlKey: true});
assert.notCalled(fakeUrlBar.removeHiddenFocus);
assert.notCalled(feed.store.dispatch);
// Now call keydown listener with "f".
feed.store.dispatch.resetHistory();
listeners.keydown({key: "f"});
assert.calledOnce(fakeUrlBar.removeHiddenFocus);
assert.calledOnce(feed.store.dispatch);
assert.calledWith(feed.store.dispatch, {
@ -395,6 +408,27 @@ describe("PlacesFeed", () => {
type: "SHOW_SEARCH",
});
});
it("should properly handle text data passed in", () => {
feed.handoffSearchToAwesomebar({
_target: {browser: {ownerGlobal: {gURLBar: fakeUrlBar}}},
data: {text: "f"},
meta: {fromTarget: {}},
});
assert.calledOnce(fakeUrlBar.search);
assert.calledWith(fakeUrlBar.search, "f");
assert.notCalled(fakeUrlBar.hiddenFocus);
assert.notCalled(fakeUrlBar.focus);
assert.calledOnce(feed.store.dispatch);
assert.calledWith(feed.store.dispatch, {
meta: {
from: "ActivityStream:Main",
skipMain: true,
to: "ActivityStream:Content",
toTarget: {},
},
type: "HIDE_SEARCH",
});
});
});
describe("#observe", () => {