Bug 1566202 - Add dismissed thumbnail, autohide whatsnew and bug fixes to New Tab Page r=k88hudson,fluent-reviewers,flod

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Ed Lee 2019-07-16 13:52:26 +00:00
parent fe8800dbaa
commit 95d2bd35d3
17 changed files with 358 additions and 377 deletions

View File

@ -48,7 +48,8 @@ module.exports = {
"content-src/asrouter/templates/StartupOverlay/StartupOverlay.jsx",
"content-src/components/TopSites/**",
"content-src/components/MoreRecommendations/MoreRecommendations.jsx",
"content-src/components/CollapsibleSection/CollapsibleSection.jsx"
"content-src/components/CollapsibleSection/CollapsibleSection.jsx",
"content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx"
],
"rules": {
"jsx-a11y/anchor-has-content": 0,

View File

@ -67,7 +67,7 @@ function templateHTML(options) {
}
</body>
</html>
`.trimStart();
`.trimLeft();
}
/**

View File

@ -18,7 +18,7 @@ export class CardGrid extends React.PureComponent {
<PlaceholderDSCard key={`dscard-${index}`} />
) : (
<DSCard
key={`dscard-${index}`}
key={`dscard-${rec.id}`}
pos={rec.pos}
campaignId={rec.campaign_id}
image_src={rec.image_src}

View File

@ -52,13 +52,20 @@ export class DSEmptyState extends React.PureComponent {
renderButton() {
if (this.props.status === "waiting" || this.state.waiting) {
return <button className="try-again-button waiting">Loading...</button>;
return (
<button
className="try-again-button waiting"
data-l10n-id="newtab-discovery-empty-section-topstories-loading"
/>
);
}
return (
<button className="try-again-button" onClick={this.onReset}>
Try Again
</button>
<button
className="try-again-button"
onClick={this.onReset}
data-l10n-id="newtab-discovery-empty-section-topstories-try-again-button"
/>
);
}
@ -66,7 +73,7 @@ export class DSEmptyState extends React.PureComponent {
if (this.props.status === "waiting" || this.props.status === "failed") {
return (
<React.Fragment>
<h2>Oops! We almost loaded this section, but not quite.</h2>
<h2 data-l10n-id="newtab-discovery-empty-section-topstories-timed-out" />
{this.renderButton()}
</React.Fragment>
);
@ -74,8 +81,8 @@ export class DSEmptyState extends React.PureComponent {
return (
<React.Fragment>
<h2>You are caught up!</h2>
<p>Check back later for more stories.</p>
<h2 data-l10n-id="newtab-discovery-empty-section-topstories-header" />
<p data-l10n-id="newtab-discovery-empty-section-topstories-content" />
</React.Fragment>
);
}

View File

@ -62,7 +62,7 @@ export class Hero extends React.PureComponent {
) : (
<DSCard
campaignId={rec.campaign_id}
key={`dscard-${index}`}
key={`dscard-${rec.id}`}
image_src={rec.image_src}
raw_image_src={rec.raw_image_src}
title={rec.title}
@ -87,7 +87,7 @@ export class Hero extends React.PureComponent {
heroCard = <PlaceholderDSCard />;
} else {
heroCard = (
<div className="ds-hero-item">
<div className="ds-hero-item" key={`dscard-${heroRec.id}`}>
<SafeAnchor
className="wrapper"
dispatch={this.props.dispatch}

View File

@ -145,7 +145,7 @@ export function _List(props) {
<PlaceholderListItem key={`ds-list-item-${index}`} />
) : (
<ListItem
key={`ds-list-item-${index}`}
key={`ds-list-item-${rec.id}`}
dispatch={props.dispatch}
campaignId={rec.campaign_id}
domain={rec.domain}

View File

@ -1,3 +1,4 @@
/*! THIS FILE IS AUTO-GENERATED: webpack.system-addon.config.js */
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
@ -7577,22 +7578,30 @@ class DSEmptyState_DSEmptyState extends external_React_default.a.PureComponent {
renderButton() {
if (this.props.status === "waiting" || this.state.waiting) {
return external_React_default.a.createElement("button", {
className: "try-again-button waiting"
}, "Loading...");
className: "try-again-button waiting",
"data-l10n-id": "newtab-discovery-empty-section-topstories-loading"
});
}
return external_React_default.a.createElement("button", {
className: "try-again-button",
onClick: this.onReset
}, "Try Again");
onClick: this.onReset,
"data-l10n-id": "newtab-discovery-empty-section-topstories-try-again-button"
});
}
renderState() {
if (this.props.status === "waiting" || this.props.status === "failed") {
return external_React_default.a.createElement(external_React_default.a.Fragment, null, external_React_default.a.createElement("h2", null, "Oops! We almost loaded this section, but not quite."), this.renderButton());
return external_React_default.a.createElement(external_React_default.a.Fragment, null, external_React_default.a.createElement("h2", {
"data-l10n-id": "newtab-discovery-empty-section-topstories-timed-out"
}), this.renderButton());
}
return external_React_default.a.createElement(external_React_default.a.Fragment, null, external_React_default.a.createElement("h2", null, "You are caught up!"), external_React_default.a.createElement("p", null, "Check back later for more stories."));
return external_React_default.a.createElement(external_React_default.a.Fragment, null, external_React_default.a.createElement("h2", {
"data-l10n-id": "newtab-discovery-empty-section-topstories-header"
}), external_React_default.a.createElement("p", {
"data-l10n-id": "newtab-discovery-empty-section-topstories-content"
}));
}
render() {
@ -7621,7 +7630,7 @@ class CardGrid_CardGrid extends external_React_default.a.PureComponent {
cards.push(!rec || rec.placeholder ? external_React_default.a.createElement(PlaceholderDSCard, {
key: `dscard-${index}`
}) : external_React_default.a.createElement(DSCard_DSCard, {
key: `dscard-${index}`,
key: `dscard-${rec.id}`,
pos: rec.pos,
campaignId: rec.campaign_id,
image_src: rec.image_src,
@ -7823,7 +7832,7 @@ function _List(props) {
recMarkup.push(!rec || rec.placeholder ? external_React_default.a.createElement(PlaceholderListItem, {
key: `ds-list-item-${index}`
}) : external_React_default.a.createElement(List_ListItem, {
key: `ds-list-item-${index}`,
key: `ds-list-item-${rec.id}`,
dispatch: props.dispatch,
campaignId: rec.campaign_id,
domain: rec.domain,
@ -7936,7 +7945,7 @@ class Hero_Hero extends external_React_default.a.PureComponent {
key: `dscard-${index}`
}) : external_React_default.a.createElement(DSCard_DSCard, {
campaignId: rec.campaign_id,
key: `dscard-${index}`,
key: `dscard-${rec.id}`,
image_src: rec.image_src,
raw_image_src: rec.raw_image_src,
title: rec.title,
@ -7959,7 +7968,8 @@ class Hero_Hero extends external_React_default.a.PureComponent {
heroCard = external_React_default.a.createElement(PlaceholderDSCard, null);
} else {
heroCard = external_React_default.a.createElement("div", {
className: "ds-hero-item"
className: "ds-hero-item",
key: `dscard-${heroRec.id}`
}, external_React_default.a.createElement(SafeAnchor_SafeAnchor, {
className: "wrapper",
dispatch: this.props.dispatch,

View File

@ -102,12 +102,14 @@ class _ToolbarBadgeHub {
}
removeToolbarNotification(toolbarButton) {
// Remove it from the element that displays the badge
toolbarButton
.querySelector(".toolbarbutton-badge")
.classList.remove("feature-callout");
// Remove it from the toolbar icon
toolbarButton
.querySelector(".toolbarbutton-icon")
.classList.add("feature-callout");
.classList.remove("feature-callout");
toolbarButton.removeAttribute("badged");
}
@ -122,6 +124,8 @@ class _ToolbarBadgeHub {
toolbarbutton
.querySelector(".toolbarbutton-badge")
.classList.add("feature-callout");
// This creates the cut-out effect for the icon where the notification
// fits in
toolbarbutton
.querySelector(".toolbarbutton-icon")
.classList.add("feature-callout");

View File

@ -68,6 +68,24 @@ class _ToolbarPanelHub {
);
}
// When the panel is hidden we want to run some cleanup
_onPanelHidden(win) {
const panelContainer = win.document.getElementById(
"customizationui-widget-panel"
);
// When the panel is hidden we want to remove any toolbar buttons that
// might have been added as an entry point to the panel
const removeToolbarButton = () => {
EveryWindow.unregisterCallback(TOOLBAR_BUTTON_ID);
};
if (!panelContainer) {
return;
}
panelContainer.addEventListener("popuphidden", removeToolbarButton, {
once: true,
});
}
// Render what's new messages into the panel.
async renderMessages(win, doc, containerId) {
const messages = (await this._getMessages({
@ -97,6 +115,7 @@ class _ToolbarPanelHub {
}
// TODO: TELEMETRY
this._onPanelHidden(win);
}
_createMessageElements(win, doc, content, previousDate) {

View File

@ -148,6 +148,16 @@ newtab-empty-section-highlights = Start browsing, and well show some of the g
# $provider (String): Name of the content provider for this section, e.g "Pocket".
newtab-empty-section-topstories = Youve caught up. Check back later for more top stories from { $provider }. Cant wait? Select a popular topic to find more great stories from around the web.
## Empty Section (Content Discovery Experience). These show when there are no more stories or when some stories fail to load.
newtab-discovery-empty-section-topstories-header = You are caught up!
newtab-discovery-empty-section-topstories-content = Check back later for more stories.
newtab-discovery-empty-section-topstories-try-again-button = Try Again
newtab-discovery-empty-section-topstories-loading = Loading…
# Displays when a layout in a section took too long to fetch articles.
newtab-discovery-empty-section-topstories-timed-out = Oops! We almost loaded this section, but not quite.
## Pocket Content Section.
# This is shown at the bottom of the trending stories section and precedes a list of links to popular topics.

File diff suppressed because it is too large Load Diff

View File

@ -51,6 +51,7 @@
"karma-sourcemap-loader": "0.3.7",
"karma-webpack": "3.0.5",
"loader-utils": "1.2.3",
"lodash": "4.17.14",
"minimist": "1.2.0",
"mocha": "6.1.4",
"mock-raf": "1.0.1",

View File

@ -16,18 +16,33 @@ describe("<DSEmptyState>", () => {
it("should render defaultempty state message", () => {
assert.ok(wrapper.find(".empty-state-message").exists());
assert.ok(wrapper.find("h2").exists());
assert.ok(wrapper.find("p").exists());
const header = wrapper.find(
"h2[data-l10n-id='newtab-discovery-empty-section-topstories-header']"
);
const paragraph = wrapper.find(
"p[data-l10n-id='newtab-discovery-empty-section-topstories-content']"
);
assert.ok(header.exists());
assert.ok(paragraph.exists());
});
it("should render failed state message", () => {
wrapper = shallow(<DSEmptyState status="failed" />);
assert.ok(wrapper.find("button.try-again-button").exists());
const button = wrapper.find(
"button[data-l10n-id='newtab-discovery-empty-section-topstories-try-again-button']"
);
assert.ok(button.exists());
});
it("should render waiting state message", () => {
wrapper = shallow(<DSEmptyState status="waiting" />);
assert.ok(wrapper.find("button.try-again-button.waiting").exists());
const button = wrapper.find(
"button[data-l10n-id='newtab-discovery-empty-section-topstories-loading']"
);
assert.ok(button.exists());
});
it("should dispatch DISCOVERY_STREAM_RETRY_FEED on failed state button click", () => {

View File

@ -23,6 +23,10 @@ describe("ToolbarBadgeHub", () => {
fxaMessage = msgs.find(({ id }) => id === "FXA_ACCOUNTS_BADGE");
whatsnewMessage = msgs.find(({ id }) => id.includes("WHATS_NEW_BADGE_"));
fakeElement = {
classList: {
add: sandbox.stub(),
remove: sandbox.stub(),
},
setAttribute: sandbox.stub(),
removeAttribute: sandbox.stub(),
querySelector: sandbox.stub(),
@ -128,9 +132,9 @@ describe("ToolbarBadgeHub", () => {
it("should show a notification", () => {
instance.addToolbarNotification(target, fxaMessage);
assert.calledTwice(fakeElement.setAttribute);
assert.calledOnce(fakeElement.setAttribute);
assert.calledWithExactly(fakeElement.setAttribute, "badged", true);
assert.calledWithExactly(fakeElement.setAttribute, "value", "x");
assert.calledWithExactly(fakeElement.classList.add, "feature-callout");
});
it("should attach a cb on the notification", () => {
instance.addToolbarNotification(target, fxaMessage);
@ -231,8 +235,10 @@ describe("ToolbarBadgeHub", () => {
it("should remove the notification", () => {
instance.removeToolbarNotification(fakeElement);
assert.calledTwice(fakeElement.removeAttribute);
assert.calledOnce(fakeElement.removeAttribute);
assert.calledWithExactly(fakeElement.removeAttribute, "badged");
assert.calledTwice(fakeElement.classList.remove);
assert.calledWithExactly(fakeElement.classList.remove, "feature-callout");
});
});
describe("removeAllNotifications", () => {

View File

@ -22,6 +22,7 @@ describe("ToolbarPanelHub", () => {
removeAttribute: sandbox.stub(),
querySelector: sandbox.stub().returns(null),
appendChild: sandbox.stub(),
addEventListener: sandbox.stub(),
};
fakeDocument = {
l10n: {
@ -45,6 +46,7 @@ describe("ToolbarPanelHub", () => {
},
};
fakeWindow = {
document: fakeDocument,
browser: {
ownerDocument: fakeDocument,
},
@ -147,4 +149,44 @@ describe("ToolbarPanelHub", () => {
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.callCount(instance._createDateElement, uniqueDates.length);
});
it("should listen for panelhidden and remove the toolbar button", async () => {
instance.init({
getMessages: sandbox.stub().returns([]),
});
fakeDocument.getElementById
.withArgs("customizationui-widget-panel")
.returns(null);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.notCalled(fakeElementById.addEventListener);
});
it("should listen for panelhidden and remove the toolbar button", async () => {
instance.init({
getMessages: sandbox.stub().returns([]),
});
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledOnce(fakeElementById.addEventListener);
assert.calledWithExactly(
fakeElementById.addEventListener,
"popuphidden",
sinon.match.func,
{
once: true,
}
);
const [, cb] = fakeElementById.addEventListener.firstCall.args;
assert.notCalled(everyWindowStub.unregisterCallback);
cb();
assert.calledOnce(everyWindowStub.unregisterCallback);
assert.calledWithExactly(
everyWindowStub.unregisterCallback,
"whats-new-menu-button"
);
});
});

View File

@ -18,7 +18,12 @@ module.exports = (env = {}) => ({
},
// TODO: switch to eval-source-map for faster builds. Requires CSP changes
devtool: env.development ? "inline-source-map" : false,
plugins: [new webpack.optimize.ModuleConcatenationPlugin()],
plugins: [
new webpack.BannerPlugin(
`THIS FILE IS AUTO-GENERATED: ${path.basename(__filename)}`
),
new webpack.optimize.ModuleConcatenationPlugin(),
],
module: {
rules: [
{

View File

@ -148,6 +148,16 @@ newtab-empty-section-highlights = Start browsing, and well show some of the g
# $provider (String): Name of the content provider for this section, e.g "Pocket".
newtab-empty-section-topstories = Youve caught up. Check back later for more top stories from { $provider }. Cant wait? Select a popular topic to find more great stories from around the web.
## Empty Section (Content Discovery Experience). These show when there are no more stories or when some stories fail to load.
newtab-discovery-empty-section-topstories-header = You are caught up!
newtab-discovery-empty-section-topstories-content = Check back later for more stories.
newtab-discovery-empty-section-topstories-try-again-button = Try Again
newtab-discovery-empty-section-topstories-loading = Loading…
# Displays when a layout in a section took too long to fetch articles.
newtab-discovery-empty-section-topstories-timed-out = Oops! We almost loaded this section, but not quite.
## Pocket Content Section.
# This is shown at the bottom of the trending stories section and precedes a list of links to popular topics.