Bug 1535460 - Add dark theme styles, Discovery stream blocking and bug fixes to Activity Stream r=r1cky

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
k88hudson 2019-03-14 22:27:33 +00:00
Родитель 2cf028fbc4
Коммит bdcd004f99
28 изменённых файлов: 399 добавлений и 56 удалений

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

@ -484,6 +484,34 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
spocs_endpoint: action.data || INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint,
},
};
case at.PLACES_LINK_BLOCKED:
// Return if action data is empty, or spocs or feeds data is not loaded
if (!action.data || !prevState.spocs.loaded || !prevState.feeds.loaded) {
return prevState;
}
// Filter spocs and recommendations data inside feeds by removing action.data.url
// received on PLACES_LINK_BLOCKED triggered by dismiss link menu option
return {
...prevState,
spocs: {
...prevState.spocs,
data: prevState.spocs.data.spocs ? {
spocs: prevState.spocs.data.spocs.filter(s => s.url !== action.data.url),
} : {},
},
feeds: {
...prevState.feeds,
data: Object.keys(prevState.feeds.data).reduce((accumulator, feed_url) => {
accumulator[feed_url] = {
data: {
...prevState.feeds.data[feed_url].data,
recommendations: prevState.feeds.data[feed_url].data.recommendations.filter(r => r.url !== action.data.url),
},
};
return accumulator;
}, {}),
},
};
case at.DISCOVERY_STREAM_SPOCS_UPDATE:
if (action.data) {
return {

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

@ -60,7 +60,8 @@ export class DSCard extends React.PureComponent {
source={this.props.type} />
</SafeAnchor>
<DSLinkMenu
index={this.props.index}
id={this.props.id}
index={this.props.pos}
dispatch={this.props.dispatch}
intl={this.props.intl}
url={this.props.url}

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

@ -21,6 +21,10 @@ $excerpt-line-height: 20;
&:active {
header {
@include dark-theme-only {
color: $blue-50;
}
color: $blue-70;
}
}

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

@ -42,9 +42,9 @@ export class _DSLinkMenu extends React.PureComponent {
render() {
const {index, dispatch} = this.props;
const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index;
const TOP_STORIES_SOURCE = "TOP_STORIES";
const TOP_STORIES_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow"];
const TOP_STORIES_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
const title = this.props.title || this.props.source;
const type = this.props.type || "DISCOVERY_STREAM";
return (<div>
<button ref={this.contextMenuButtonRef}
@ -59,15 +59,17 @@ export class _DSLinkMenu extends React.PureComponent {
<LinkMenu
dispatch={dispatch}
index={index}
source={TOP_STORIES_SOURCE}
source={type.toUpperCase()}
onUpdate={this.onMenuUpdate}
onShow={this.onMenuShow}
options={TOP_STORIES_CONTEXT_MENU_OPTIONS}
shouldSendImpressionStats={true}
site={{
referrer: "https://getpocket.com/recommendations",
title: this.props.title,
type: this.props.type,
url: this.props.url,
guid: this.props.id,
}} />
}
</div>);

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

@ -32,7 +32,7 @@ export class Hero extends React.PureComponent {
const {data} = this.props;
// Handle a render before feed has been fetched by displaying nothing
if (!data || !data.recommendations) {
if (!data || !data.recommendations || !data.recommendations.length) {
return (
<div />
);
@ -97,7 +97,8 @@ export class Hero extends React.PureComponent {
source={this.props.type} />
</SafeAnchor>
<DSLinkMenu
index={this.props.index}
id={heroRec.id}
index={heroRec.pos}
dispatch={this.props.dispatch}
intl={this.props.intl}
url={heroRec.url}

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

@ -172,6 +172,7 @@ $card-header-in-hero-line-height: 20;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px;
grid-auto-rows: min-content;
}
}
@ -231,6 +232,7 @@ $card-header-in-hero-line-height: 20;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px;
grid-auto-rows: min-content;
.ds-card {
&:hover {

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

@ -63,7 +63,8 @@ export class ListItem extends React.PureComponent {
source={this.props.type} />
</SafeAnchor>
<DSLinkMenu
index={this.props.index}
id={this.props.id}
index={this.props.pos}
dispatch={this.props.dispatch}
intl={this.props.intl}
url={this.props.url}

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

@ -2065,7 +2065,8 @@ main {
.ds-column-8 .ds-hero .cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
grid-column-gap: 24px;
grid-auto-rows: min-content; }
.ds-column-9 .ds-hero,
.ds-column-10 .ds-hero,
.ds-column-11 .ds-hero,
@ -2143,7 +2144,8 @@ main {
.ds-column-12 .ds-hero .cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
grid-column-gap: 24px;
grid-auto-rows: min-content; }
[lwt-newtab-brighttext]:not(.force-light-theme) .ds-column-9 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
.ds-column-10 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
.ds-column-11 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
@ -2560,6 +2562,8 @@ main {
color: #45A1FF; }
.ds-card:active header {
color: #003EAA; }
[lwt-newtab-brighttext]:not(.force-light-theme) .ds-card:active header {
color: #0A84FF; }
.ds-card .img-wrapper {
width: 100%; }
.ds-card .img {

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

@ -2068,7 +2068,8 @@ main {
.ds-column-8 .ds-hero .cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
grid-column-gap: 24px;
grid-auto-rows: min-content; }
.ds-column-9 .ds-hero,
.ds-column-10 .ds-hero,
.ds-column-11 .ds-hero,
@ -2146,7 +2147,8 @@ main {
.ds-column-12 .ds-hero .cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
grid-column-gap: 24px;
grid-auto-rows: min-content; }
[lwt-newtab-brighttext]:not(.force-light-theme) .ds-column-9 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
.ds-column-10 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
.ds-column-11 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
@ -2563,6 +2565,8 @@ main {
color: #45A1FF; }
.ds-card:active header {
color: #003EAA; }
[lwt-newtab-brighttext]:not(.force-light-theme) .ds-card:active header {
color: #0A84FF; }
.ds-card .img-wrapper {
width: 100%; }
.ds-card .img {

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

@ -2065,7 +2065,8 @@ main {
.ds-column-8 .ds-hero .cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
grid-column-gap: 24px;
grid-auto-rows: min-content; }
.ds-column-9 .ds-hero,
.ds-column-10 .ds-hero,
.ds-column-11 .ds-hero,
@ -2143,7 +2144,8 @@ main {
.ds-column-12 .ds-hero .cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
grid-column-gap: 24px;
grid-auto-rows: min-content; }
[lwt-newtab-brighttext]:not(.force-light-theme) .ds-column-9 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
.ds-column-10 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
.ds-column-11 .ds-hero .cards .ds-card:hover, [lwt-newtab-brighttext]:not(.force-light-theme)
@ -2560,6 +2562,8 @@ main {
color: #45A1FF; }
.ds-card:active header {
color: #003EAA; }
[lwt-newtab-brighttext]:not(.force-light-theme) .ds-card:active header {
color: #0A84FF; }
.ds-card .img-wrapper {
width: 100%; }
.ds-card .img {

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

@ -7296,9 +7296,9 @@ class DSLinkMenu_DSLinkMenu extends external_React_default.a.PureComponent {
render() {
const { index, dispatch } = this.props;
const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index;
const TOP_STORIES_SOURCE = "TOP_STORIES";
const TOP_STORIES_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow"];
const TOP_STORIES_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
const title = this.props.title || this.props.source;
const type = this.props.type || "DISCOVERY_STREAM";
return external_React_default.a.createElement(
"div",
@ -7318,15 +7318,17 @@ class DSLinkMenu_DSLinkMenu extends external_React_default.a.PureComponent {
isContextMenuOpen && external_React_default.a.createElement(LinkMenu["LinkMenu"], {
dispatch: dispatch,
index: index,
source: TOP_STORIES_SOURCE,
source: type.toUpperCase(),
onUpdate: this.onMenuUpdate,
onShow: this.onMenuShow,
options: TOP_STORIES_CONTEXT_MENU_OPTIONS,
shouldSendImpressionStats: true,
site: {
referrer: "https://getpocket.com/recommendations",
title: this.props.title,
type: this.props.type,
url: this.props.url
url: this.props.url,
guid: this.props.id
} })
);
}
@ -7483,7 +7485,8 @@ class DSCard_DSCard extends external_React_default.a.PureComponent {
source: this.props.type })
),
external_React_default.a.createElement(DSLinkMenu, {
index: this.props.index,
id: this.props.id,
index: this.props.pos,
dispatch: this.props.dispatch,
intl: this.props.intl,
url: this.props.url,
@ -7670,7 +7673,8 @@ class List_ListItem extends external_React_default.a.PureComponent {
source: this.props.type })
),
external_React_default.a.createElement(DSLinkMenu, {
index: this.props.index,
id: this.props.id,
index: this.props.pos,
dispatch: this.props.dispatch,
intl: this.props.intl,
url: this.props.url,
@ -7764,7 +7768,7 @@ class Hero_Hero extends external_React_default.a.PureComponent {
const { data } = this.props;
// Handle a render before feed has been fetched by displaying nothing
if (!data || !data.recommendations) {
if (!data || !data.recommendations || !data.recommendations.length) {
return external_React_default.a.createElement("div", null);
}
@ -7852,7 +7856,8 @@ class Hero_Hero extends external_React_default.a.PureComponent {
source: this.props.type })
),
external_React_default.a.createElement(DSLinkMenu, {
index: this.props.index,
id: heroRec.id,
index: heroRec.pos,
dispatch: this.props.dispatch,
intl: this.props.intl,
url: heroRec.url,
@ -12357,6 +12362,30 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
spocs_endpoint: action.data || INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint
})
});
case Actions["actionTypes"].PLACES_LINK_BLOCKED:
// Return if action data is empty, or spocs or feeds data is not loaded
if (!action.data || !prevState.spocs.loaded || !prevState.feeds.loaded) {
return prevState;
}
// Filter spocs and recommendations data inside feeds by removing action.data.url
// received on PLACES_LINK_BLOCKED triggered by dismiss link menu option
return Object.assign({}, prevState, {
spocs: Object.assign({}, prevState.spocs, {
data: prevState.spocs.data.spocs ? {
spocs: prevState.spocs.data.spocs.filter(s => s.url !== action.data.url)
} : {}
}),
feeds: Object.assign({}, prevState.feeds, {
data: Object.keys(prevState.feeds.data).reduce((accumulator, feed_url) => {
accumulator[feed_url] = {
data: Object.assign({}, prevState.feeds.data[feed_url].data, {
recommendations: prevState.feeds.data[feed_url].data.recommendations.filter(r => r.url !== action.data.url)
})
};
return accumulator;
}, {})
})
});
case Actions["actionTypes"].DISCOVERY_STREAM_SPOCS_UPDATE:
if (action.data) {
return Object.assign({}, prevState, {

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

@ -243,6 +243,10 @@ const PREFS_CONFIG = new Map([
});
},
}],
["discoverystream.endpoints", {
title: "Endpoint prefixes (comma-separated) that are allowed to be requested",
value: "https://getpocket.cdn.mozilla.net/",
}],
["discoverystream.optOut.0", {
title: "Opt out of new layout v0",
value: false,

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

@ -4,6 +4,7 @@
"use strict";
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const {NewTabUtils} = ChromeUtils.import("resource://gre/modules/NewTabUtils.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
ChromeUtils.defineModuleGetter(this, "perfService", "resource://activity-stream/common/PerfService.jsm");
@ -19,6 +20,7 @@ const SPOCS_FEEDS_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
const DEFAULT_RECS_EXPIRE_TIME = 60 * 60 * 1000; // 1 hour
const MAX_LIFETIME_CAP = 500; // Guard against misconfiguration on the server
const PREF_CONFIG = "discoverystream.config";
const PREF_ENDPOINTS = "discoverystream.endpoints";
const PREF_OPT_OUT = "discoverystream.optOut.0";
const PREF_SHOW_SPONSORED = "showSponsored";
const PREF_SPOC_IMPRESSIONS = "discoverystream.spoc.impressions";
@ -88,17 +90,20 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
return null;
}
try {
// Make sure the requested endpoint is allowed
const allowed = this.store.getState().Prefs.values[PREF_ENDPOINTS].split(",");
if (!allowed.some(prefix => endpoint.startsWith(prefix))) {
throw new Error(`Not one of allowed prefixes (${allowed})`);
}
const response = await fetch(endpoint, {credentials: "omit"});
if (!response.ok) {
// istanbul ignore next
throw new Error(`${endpoint} returned unexpected status: ${response.status}`);
throw new Error(`Unexpected status (${response.status})`);
}
return response.json();
} catch (error) {
// istanbul ignore next
Cu.reportError(`Failed to fetch ${endpoint}: ${error.message}`);
}
// istanbul ignore next
return null;
}
@ -200,11 +205,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
// We initially stub this out so we don't fetch dupes,
// we then fill in with the proper object inside the promise.
newFeeds[url] = {};
const feedPromise = this.getComponentFeed(url, isStartup);
feedPromise.then(data => {
newFeeds[url] = data;
feedPromise.then(feed => {
newFeeds[url] = this.filterRecommendations(feed);
}).catch(/* istanbul ignore next */ error => {
Cu.reportError(`Error trying to load component feed ${url}: ${error}`);
});
@ -214,6 +217,13 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
};
}
filterRecommendations(feed) {
if (feed && feed.data && feed.data.recommendations && feed.data.recommendations.length) {
return {data: this.filterBlocked(feed.data, "recommendations")};
}
return feed;
}
/**
* reduceFeedComponents - Filters out components with no feeds, and combines
* all feeds on this component with the feeds from other components.
@ -309,12 +319,24 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
data: {
lastUpdated: spocs.lastUpdated,
spocs: this.transform(this.filterSpocs(spocs.data)),
spocs: this.transform(this.frequencyCapSpocs(spocs.data)),
},
});
}
transform(data) {
filterBlocked(data, type) {
if (data && data[type] && data[type].length) {
const filteredItems = data[type].filter(item => !NewTabUtils.blockedLinks.isBlocked({"url": item.url}));
return {
...data,
[type]: filteredItems,
};
}
return data;
}
transform(spocs) {
const data = this.filterBlocked(spocs, "spocs");
if (data && data.spocs && data.spocs.length) {
const spocsPerDomain = this.store.getState().DiscoveryStream.spocs.spocs_per_domain || 1;
const campaignMap = {};
@ -340,7 +362,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
}
// Filter spocs based on frequency caps
filterSpocs(data) {
frequencyCapSpocs(data) {
if (data && data.spocs && data.spocs.length) {
const {spocs} = data;
const impressions = this.readImpressionsPref(PREF_SPOC_IMPRESSIONS);
@ -711,7 +733,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
data: {
lastUpdated: spocs.lastUpdated,
spocs: this.transform(this.filterSpocs(spocs.data)),
spocs: this.transform(this.frequencyCapSpocs(spocs.data)),
},
}));
}

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

@ -93,6 +93,8 @@ prefs_home_header=Konten Beranda Firefox
prefs_home_description=Pilih konten yang ingin Anda tampilkan dalam Beranda Firefox.
prefs_content_discovery_header=Beranda Firefox
prefs_content_discovery_description=Penemuan Konten dalam Firefox Home memungkinkan Anda untuk menemukan artikel bermutu tinggi dan relevan dari seluruh web.
prefs_content_discovery_button=Matikan Penemuan Konten
# LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
# plural forms used in a drop down of multiple row options (1 row, 2 rows).

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

@ -1,4 +1,4 @@
newtab_page_title=Neuvo Feuggio
newtab_page_title=Neuvo feuggio
header_top_sites=I megio sciti
header_highlights=In evidensa
@ -52,8 +52,8 @@ menu_action_archive_pocket=Archivia in Pocket
menu_action_show_file_mac_os=Fanni vedde in Finder
menu_action_show_file_windows=Arvi cartella
menu_action_show_file_linux=Arvi cartella
menu_action_show_file_default=Fanni vedde file
menu_action_open_file=Arvi file
menu_action_show_file_default=Mostra o schedaio
menu_action_open_file=Arvi schedaio
# LOCALIZATION NOTE (menu_action_copy_download_link, menu_action_go_to_download_page):
# "Download" here, in both cases, is not a verb, it is a noun. As in, "Copy the
@ -93,6 +93,7 @@ prefs_home_header=Pagina iniçiâ de Firefox
prefs_home_description=Çerni i contegnui che ti veu vedde inta pagina iniçiâ de Firefox.
prefs_content_discovery_header=Pagina iniçiâ de Firefox
prefs_content_discovery_button=Dizabilita a descoverta de neuvi contegnui
# LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
# plural forms used in a drop down of multiple row options (1 row, 2 rows).

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

@ -132,14 +132,14 @@ topsites_form_title_label=Título
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_url_placeholder=Digite ou cole uma URL
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
topsites_form_save_button=Salvar
topsites_form_cancel_button=Cancelar
topsites_form_url_validation=É necessário um URL válido
topsites_form_url_validation=É necessário uma URL válida
topsites_form_image_validation=Não foi possível carregar a imagem. Tente uma URL diferente.
# LOCALIZATION NOTE (pocket_read_more): This is shown at the bottom of the

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

@ -41,8 +41,8 @@ window.gActivityStreamStrings = {
"prefs_home_header": "Konten Beranda Firefox",
"prefs_home_description": "Pilih konten yang ingin Anda tampilkan dalam Beranda Firefox.",
"prefs_content_discovery_header": "Beranda Firefox",
"prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
"prefs_content_discovery_button": "Turn Off Content Discovery",
"prefs_content_discovery_description": "Penemuan Konten dalam Firefox Home memungkinkan Anda untuk menemukan artikel bermutu tinggi dan relevan dari seluruh web.",
"prefs_content_discovery_button": "Matikan Penemuan Konten",
"prefs_section_rows_option": "{num} baris",
"prefs_search_header": "Pencarian Web",
"prefs_topsites_description": "Situs yang sering Anda kunjungi",

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

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
<title>Neuvo Feuggio</title>
<title>Neuvo feuggio</title>
<link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />

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

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
<title>Neuvo Feuggio</title>
<title>Neuvo feuggio</title>
<link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />

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

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
<title>Neuvo Feuggio</title>
<title>Neuvo feuggio</title>
<link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />

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

@ -1,6 +1,6 @@
// Note - this is a generated lij file.
window.gActivityStreamStrings = {
"newtab_page_title": "Neuvo Feuggio",
"newtab_page_title": "Neuvo feuggio",
"header_top_sites": "I megio sciti",
"header_highlights": "In evidensa",
"header_recommended_by": "Consegiou da {provider}",
@ -27,8 +27,8 @@ window.gActivityStreamStrings = {
"menu_action_show_file_mac_os": "Fanni vedde in Finder",
"menu_action_show_file_windows": "Arvi cartella",
"menu_action_show_file_linux": "Arvi cartella",
"menu_action_show_file_default": "Fanni vedde file",
"menu_action_open_file": "Arvi file",
"menu_action_show_file_default": "Mostra o schedaio",
"menu_action_open_file": "Arvi schedaio",
"menu_action_copy_download_link": "Còpia indirisso òrigine",
"menu_action_go_to_download_page": "Vanni a-a pagina de descaregamento",
"menu_action_remove_download": "Scancella da-a stöia",
@ -42,7 +42,7 @@ window.gActivityStreamStrings = {
"prefs_home_description": "Çerni i contegnui che ti veu vedde inta pagina iniçiâ de Firefox.",
"prefs_content_discovery_header": "Pagina iniçiâ de Firefox",
"prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
"prefs_content_discovery_button": "Turn Off Content Discovery",
"prefs_content_discovery_button": "Dizabilita a descoverta de neuvi contegnui",
"prefs_section_rows_option": "{num} riga;{num} righe",
"prefs_search_header": "Çerca into Web",
"prefs_topsites_description": "I sciti che ti vixiti de ciù",

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

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
<title>Neuvo Feuggio</title>
<title>Neuvo feuggio</title>
<link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />

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

@ -67,13 +67,13 @@ window.gActivityStreamStrings = {
"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_url_placeholder": "Digite ou cole uma URL",
"topsites_form_use_image_link": "Usar uma imagem personalizada…",
"topsites_form_preview_button": "Visualizar",
"topsites_form_add_button": "Adicionar",
"topsites_form_save_button": "Salvar",
"topsites_form_cancel_button": "Cancelar",
"topsites_form_url_validation": "É necessário um URL válido",
"topsites_form_url_validation": "É necessário uma URL válida",
"topsites_form_image_validation": "Não foi possível carregar a imagem. Tente uma URL diferente.",
"pocket_read_more": "Tópicos populares:",
"pocket_read_even_more": "Ver mais histórias",

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

@ -5,6 +5,7 @@ support-files =
head.js
prefs =
browser.newtabpage.activity-stream.debug=false
browser.newtabpage.activity-stream.discoverystream.endpoints=data:
[browser_activity_stream_strings.js]
[browser_as_load_location.js]

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

@ -31,6 +31,7 @@ describe("CFRPageActions", () => {
"cfr-notification-footer-learn-more-link",
"cfr-notification-footer-pintab-animation-container",
"cfr-notification-footer-animation-button",
"cfr-notification-footer-animation-label",
];
const elementClassNames = [
"popup-notification-body-container",

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

@ -702,6 +702,104 @@ describe("Reducers", () => {
const state = DiscoveryStream(undefined, {type: at.DISCOVERY_STREAM_SPOCS_UPDATE, data});
assert.deepEqual(state.spocs, INITIAL_STATE.DiscoveryStream.spocs);
});
it("should not update state for empty action.data on PLACES_LINK_BLOCKED", () => {
const newState = DiscoveryStream(undefined, {type: at.PLACES_LINK_BLOCKED});
assert.equal(newState, INITIAL_STATE.DiscoveryStream);
});
it("should not update state if feeds are not loaded", () => {
const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "foo.com"}};
const newState = DiscoveryStream(undefined, deleteAction);
assert.equal(newState, INITIAL_STATE.DiscoveryStream);
});
it("should not update state if spocs and feeds data is undefined", () => {
const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "foo.com"}};
const oldState = {
spocs: {
data: {},
loaded: true,
},
feeds: {
data: {},
loaded: true,
},
};
const newState = DiscoveryStream(oldState, deleteAction);
assert.deepEqual(newState, oldState);
});
it("should remove the site on PLACES_LINK_BLOCKED from spocs if feeds data is empty", () => {
const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "https://foo.com"}};
const oldState = {
spocs: {
data: {
spocs: [
{url: "https://foo.com"},
{url: "test-spoc.com"},
],
},
loaded: true,
},
feeds: {
data: {},
loaded: true,
},
};
const newState = DiscoveryStream(oldState, deleteAction);
assert.deepEqual(newState.spocs.data.spocs, [{url: "test-spoc.com"}]);
});
it("should remove the site on PLACES_LINK_BLOCKED from feeds if spocs data is empty", () => {
const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "https://foo.com"}};
const oldState = {
spocs: {
data: {},
loaded: true,
},
feeds: {
data: {
"https://foo.com/feed1": {
data: {
recommendations: [
{url: "https://foo.com"},
{url: "test.com"},
],
},
},
},
loaded: true,
},
};
const newState = DiscoveryStream(oldState, deleteAction);
assert.deepEqual(newState.feeds.data["https://foo.com/feed1"].data.recommendations, [{url: "test.com"}]);
});
it("should remove the site on PLACES_LINK_BLOCKED from both feeds and spocs", () => {
const oldState = {
feeds: {
data: {
"https://foo.com/feed1": {
data: {
recommendations: [
{url: "https://foo.com"},
{url: "test.com"},
],
},
},
},
loaded: true,
},
spocs: {
data: {
spocs: [
{url: "https://foo.com"},
{url: "test-spoc.com"},
],
},
loaded: true,
},
};
const deleteAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "https://foo.com"}};
const newState = DiscoveryStream(oldState, deleteAction);
assert.deepEqual(newState.spocs.data.spocs, [{url: "test-spoc.com"}]);
assert.deepEqual(newState.feeds.data["https://foo.com/feed1"].data.recommendations, [{url: "test.com"}]);
});
});
describe("Search", () => {
it("should return INITIAL_STATE by default", () => {

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

@ -24,16 +24,16 @@ describe("<DSLinkMenu>", () => {
assert.equal(wrapper.find(LinkMenu).length, 1);
});
it("should pass dispatch, onUpdate, onShow, site, options, source and index to LinkMenu", () => {
it("should pass dispatch, onUpdate, onShow, site, options, shouldSendImpressionStats, source and index to LinkMenu", () => {
wrapper.find(".context-menu-button").simulate("click", {preventDefault: () => {}});
const linkMenuProps = wrapper.find(LinkMenu).props();
["dispatch", "onUpdate", "onShow", "site", "index", "options", "source"].forEach(prop => assert.property(linkMenuProps, prop));
["dispatch", "onUpdate", "onShow", "site", "index", "options", "source", "shouldSendImpressionStats"].forEach(prop => assert.property(linkMenuProps, prop));
});
it("should pass through the correct menu options to LinkMenu", () => {
wrapper.find(".context-menu-button").simulate("click", {preventDefault: () => {}});
const linkMenuProps = wrapper.find(LinkMenu).props();
assert.deepEqual(linkMenuProps.options,
["OpenInNewWindow", "OpenInPrivateWindow"]);
["OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"]);
});
});

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

@ -1,9 +1,12 @@
import {actionCreators as ac, actionTypes as at, actionUtils as au} from "common/Actions.jsm";
import {combineReducers, createStore} from "redux";
import {DiscoveryStreamFeed} from "lib/DiscoveryStreamFeed.jsm";
import {GlobalOverrider} from "test/unit/utils";
import {reducers} from "common/Reducers.jsm";
const CONFIG_PREF_NAME = "discoverystream.config";
const DUMMY_ENDPOINT = "https://getpocket.cdn.mozilla.net/dummy";
const ENDPOINTS_PREF_NAME = "discoverystream.endpoints";
const SPOC_IMPRESSION_TRACKING_PREF = "discoverystream.spoc.impressions";
const REC_IMPRESSION_TRACKING_PREF = "discoverystream.rec.impressions";
const THIRTY_MINUTES = 30 * 60 * 1000;
@ -14,6 +17,9 @@ describe("DiscoveryStreamFeed", () => {
let sandbox;
let fetchStub;
let clock;
let fakeNewTabUtils;
let globals;
const setPref = (name, value) => {
const action = {
type: at.PREF_CHANGED,
@ -40,17 +46,72 @@ describe("DiscoveryStreamFeed", () => {
feed.store = createStore(combineReducers(reducers), {
Prefs: {
values: {
[CONFIG_PREF_NAME]: JSON.stringify({enabled: false, show_spocs: false, layout_endpoint: "foo"}),
[CONFIG_PREF_NAME]: JSON.stringify({enabled: false, show_spocs: false, layout_endpoint: DUMMY_ENDPOINT}),
[ENDPOINTS_PREF_NAME]: DUMMY_ENDPOINT,
},
},
});
sandbox.stub(feed, "_maybeUpdateCachedData").resolves();
globals = new GlobalOverrider();
fakeNewTabUtils = {
blockedLinks: {
links: [],
isBlocked: () => false,
},
};
globals.set("NewTabUtils", fakeNewTabUtils);
});
afterEach(() => {
clock.restore();
sandbox.restore();
globals.restore();
});
describe("#fetchFromEndpoint", () => {
beforeEach(() => {
fetchStub.resolves({
json: () => Promise.resolve("hi"),
ok: true,
});
});
it("should get a response", async () => {
const response = await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
assert.equal(response, "hi");
});
it("should not send cookies", async () => {
await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
assert.propertyVal(fetchStub.firstCall.args[1], "credentials", "omit");
});
it("should allow unexpected response", async () => {
fetchStub.resolves({ok: false});
const response = await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
assert.equal(response, null);
});
it("should disallow unexpected endpoints", async () => {
feed.store.getState = () => ({
Prefs: {values: {[ENDPOINTS_PREF_NAME]: "https://other.site"}},
});
const response = await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
assert.equal(response, null);
});
it("should allow multiple endpoints", async () => {
feed.store.getState = () => ({
Prefs: {values: {[ENDPOINTS_PREF_NAME]: `https://other.site,${DUMMY_ENDPOINT}`}},
});
const response = await feed.fetchFromEndpoint(DUMMY_ENDPOINT);
assert.equal(response, "hi");
});
});
describe("#loadLayout", () => {
@ -534,7 +595,80 @@ describe("DiscoveryStreamFeed", () => {
});
});
describe("#filterSpocs", () => {
describe("#filterBlocked", () => {
it("should return initial data if spocs are empty", () => {
const result = feed.filterBlocked({spocs: []});
assert.equal(result.spocs.length, 0);
});
it("should return initial spocs data if links are not blocked", () => {
const result = feed.filterBlocked({
spocs: [
{url: "https://foo.com"},
{url: "test.com"},
],
}, "spocs");
assert.equal(result.spocs.length, 2);
});
it("should return filtered out spocs based on blockedlist", () => {
fakeNewTabUtils.blockedLinks.links = [{url: "https://foo.com"}];
fakeNewTabUtils.blockedLinks.isBlocked = site => (fakeNewTabUtils.blockedLinks.links[0].url === site.url);
const result = feed.filterBlocked({
spocs: [
{url: "https://foo.com"},
{url: "test.com"},
],
}, "spocs");
assert.lengthOf(result.spocs, 1);
assert.equal(result.spocs[0].url, "test.com");
assert.notInclude(result.spocs, fakeNewTabUtils.blockedLinks.links[0]);
});
it("should return initial recommendations data if links are not blocked", () => {
const result = feed.filterBlocked({
recommendations: [
{url: "https://foo.com"},
{url: "test.com"},
],
}, "recommendations");
assert.equal(result.recommendations.length, 2);
});
it("should return filtered out recommendations based on blockedlist", () => {
fakeNewTabUtils.blockedLinks.links = [{url: "https://foo.com"}];
fakeNewTabUtils.blockedLinks.isBlocked = site => (fakeNewTabUtils.blockedLinks.links[0].url === site.url);
const result = feed.filterBlocked({
recommendations: [
{url: "https://foo.com"},
{url: "test.com"},
],
}, "recommendations");
assert.lengthOf(result.recommendations, 1);
assert.equal(result.recommendations[0].url, "test.com");
assert.notInclude(result.recommendations, fakeNewTabUtils.blockedLinks.links[0]);
});
it("filterRecommendations based on blockedlist by passing feed data", () => {
fakeNewTabUtils.blockedLinks.links = [{url: "https://foo.com"}];
fakeNewTabUtils.blockedLinks.isBlocked = site => (fakeNewTabUtils.blockedLinks.links[0].url === site.url);
const result = feed.filterRecommendations({
data: {
recommendations: [
{url: "https://foo.com"},
{url: "test.com"},
],
},
});
assert.lengthOf(result.data.recommendations, 1);
assert.equal(result.data.recommendations[0].url, "test.com");
assert.notInclude(result.data.recommendations, fakeNewTabUtils.blockedLinks.links[0]);
});
});
describe("#frequencyCapSpocs", () => {
it("should return filtered out spocs based on frequency caps", () => {
const fakeSpocs = {
spocs: [
@ -565,7 +699,7 @@ describe("DiscoveryStreamFeed", () => {
};
sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
const result = feed.filterSpocs(fakeSpocs);
const result = feed.frequencyCapSpocs(fakeSpocs);
assert.equal(result.spocs.length, 1);
assert.equal(result.spocs[0].campaign_id, "not-seen");