diff --git a/common/Reducers.jsm b/common/Reducers.jsm
index a2e5374ca4..8ed2c46fa9 100644
--- a/common/Reducers.jsm
+++ b/common/Reducers.jsm
@@ -63,6 +63,7 @@ const INITIAL_STATE = {
spocs: {
spocs_endpoint: "",
lastUpdated: null,
+ showSpocs: false,
data: {}, // {spocs: []}
loaded: false,
frequency_caps: [],
@@ -606,6 +607,7 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
...prevState,
spocs: {
...prevState.spocs,
+ showSpocs: action.data.showSpocs,
lastUpdated: action.data.lastUpdated,
data: action.data.spocs,
loaded: true,
diff --git a/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx b/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
index 6c8c1103ec..4bf93fe96f 100644
--- a/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
+++ b/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
@@ -6,6 +6,7 @@ import { actionCreators as ac } from "common/Actions.jsm";
import { CardGrid } from "content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid";
import { CollapsibleSection } from "content-src/components/CollapsibleSection/CollapsibleSection";
import { connect } from "react-redux";
+import { DSDismiss } from "content-src/components/DiscoveryStreamComponents/DSDismiss/DSDismiss";
import { DSMessage } from "content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage";
import { Hero } from "content-src/components/DiscoveryStreamComponents/Hero/Hero";
import { Highlights } from "content-src/components/DiscoveryStreamComponents/Highlights/Highlights";
@@ -301,11 +302,8 @@ export class _DiscoveryStreamBase extends React.PureComponent {
const styles = [];
return (
- {layoutRender.map((row, rowIndex) => (
-
+ {layoutRender.map((row, rowIndex) => {
+ const contents = (
{row.components.map((component, componentIndex) => {
if (!component) {
@@ -315,6 +313,14 @@ export class _DiscoveryStreamBase extends React.PureComponent {
...(styles[rowIndex] || []),
component.styles,
];
+ // TODO make this dry
+ if (component.campaign_id) {
+ return (
+
+ {this.renderComponent(component, row.width)}
+
+ );
+ }
return (
{this.renderComponent(component, row.width)}
@@ -322,8 +328,29 @@ export class _DiscoveryStreamBase extends React.PureComponent {
);
})}
-
- ))}
+ );
+ // TODO Dry this out too.
+ if (row.campaign_id) {
+ return (
+
+
+ {contents}
+
+
+ );
+ }
+ return (
+
+ {contents}
+
+ );
+ })}
{this.renderStyles(styles)}
);
diff --git a/content-src/components/DiscoveryStreamComponents/DSDismiss/DSDismiss.jsx b/content-src/components/DiscoveryStreamComponents/DSDismiss/DSDismiss.jsx
new file mode 100644
index 0000000000..30ae72c2ee
--- /dev/null
+++ b/content-src/components/DiscoveryStreamComponents/DSDismiss/DSDismiss.jsx
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import React from "react";
+
+export class DSDismiss extends React.PureComponent {
+ render() {
+ // TODO:
+ // This needs an x button that dismisses the campaign_id passed to it.
+ // It also needs a hover state for it and its children.
+ // Somewhere else, we need to filter out anything with a campaign_id that we've blocked.
+ // Consider calling this collection_id.
+ // Right now it is this.props.campaignId
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
diff --git a/content-src/components/DiscoveryStreamComponents/DSDismiss/_DSDismiss.scss b/content-src/components/DiscoveryStreamComponents/DSDismiss/_DSDismiss.scss
new file mode 100644
index 0000000000..5ef67839f1
--- /dev/null
+++ b/content-src/components/DiscoveryStreamComponents/DSDismiss/_DSDismiss.scss
@@ -0,0 +1,4 @@
+.ds-dismiss {
+ background: lightgreen;;
+ border-radius: 5px;
+}
diff --git a/content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage.jsx b/content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage.jsx
index 91a46d4c08..fb591b297a 100644
--- a/content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage.jsx
+++ b/content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage.jsx
@@ -25,6 +25,7 @@ export class DSMessage extends React.PureComponent {
)}
+ {this.props.subtitle && (
{this.props.subtitle}
)}
);
}
diff --git a/content-src/components/DiscoveryStreamComponents/DSMessage/_DSMessage.scss b/content-src/components/DiscoveryStreamComponents/DSMessage/_DSMessage.scss
index 41b4c3863b..2524731f52 100644
--- a/content-src/components/DiscoveryStreamComponents/DSMessage/_DSMessage.scss
+++ b/content-src/components/DiscoveryStreamComponents/DSMessage/_DSMessage.scss
@@ -42,4 +42,10 @@
}
}
}
+
+ .subtitle {
+ font-size: 13px;
+ line-height: 24px;
+ color: $grey-50;
+ }
}
diff --git a/content-src/lib/selectLayoutRender.js b/content-src/lib/selectLayoutRender.js
index a83b71d047..0cc9abfa6e 100644
--- a/content-src/lib/selectLayoutRender.js
+++ b/content-src/lib/selectLayoutRender.js
@@ -139,19 +139,29 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
return { ...component, data };
};
+ const filterComponent = (c) => {
+ return (
+ !filterArray.includes(c.type) &&
+ (!c.sponsored || spocs.showSpocs)
+ );
+ };
+
+ const filterRow = (r) => {
+ return (
+ r.components.filter(filterComponent).length &&
+ (!r.sponsored || spocs.showSpocs)
+ );
+ };
+
const renderLayout = () => {
const renderedLayoutArray = [];
- for (const row of layout.filter(
- r => r.components.filter(c => !filterArray.includes(c.type)).length
- )) {
+ for (const row of layout.filter(filterRow)) {
let components = [];
renderedLayoutArray.push({
...row,
components,
});
- for (const component of row.components.filter(
- c => !filterArray.includes(c.type)
- )) {
+ for (const component of row.components.filter(filterComponent)) {
if (component.feed) {
const spocsConfig = component.spocs;
// Are we still waiting on a feed/spocs, render what we have,
diff --git a/content-src/styles/_activity-stream.scss b/content-src/styles/_activity-stream.scss
index 821b1c3e6a..6636b53859 100644
--- a/content-src/styles/_activity-stream.scss
+++ b/content-src/styles/_activity-stream.scss
@@ -158,6 +158,7 @@ input {
@import '../components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu';
@import '../components/DiscoveryStreamComponents/DSCard/DSCard';
@import '../components/DiscoveryStreamComponents/DSImage/DSImage';
+@import '../components/DiscoveryStreamComponents/DSDismiss/DSDismiss';
@import '../components/DiscoveryStreamComponents/DSMessage/DSMessage';
@import '../components/DiscoveryStreamImpressionStats/ImpressionStats';
@import '../components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState';
diff --git a/lib/DiscoveryStreamFeed.jsm b/lib/DiscoveryStreamFeed.jsm
index 78dc9b4ca1..bcbb942761 100644
--- a/lib/DiscoveryStreamFeed.jsm
+++ b/lib/DiscoveryStreamFeed.jsm
@@ -527,6 +527,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
sendUpdate({
type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
data: {
+ showSpocs: this.showSpocs,
lastUpdated: spocs.lastUpdated,
spocs: newSpocs,
},
@@ -1286,10 +1287,90 @@ defaultLayoutResp = {
{
width: 12,
components: [
+ {
+ type: "Message",
+ header: {
+ title: "Not Sponsored by Collection Title",
+ subtitle: "Not Sponsored by Brand Name",
+ },
+ properties: {},
+ },
+ {
+ type: "Message",
+ header: {
+ title: "Sponsored by Collection Title",
+ subtitle: "Sponsored by Brand Name",
+ },
+ campaign_id: "123456",
+ sponsored: true,
+ properties: {},
+ },
+ ],
+ },
+ {
+ width: 12,
+ campaign_id: "123456",
+ components: [
+ {
+ type: "Message",
+ header: {
+ title: "campaign but not paid",
+ subtitle: "campaign but not paid",
+ },
+ properties: {},
+ },
+ {
+ type: "Message",
+ header: {
+ title: "campaign but not paid",
+ subtitle: "campaign but not paid",
+ },
+ properties: {},
+ },
+ ],
+ },
+ {
+ width: 12,
+ components: [
+ {
+ type: "Message",
+ campaign_id: "123456",
+ header: {
+ title: "campaign but not paid",
+ subtitle: "campaign but not paid",
+ },
+ properties: {},
+ },
+ {
+ type: "Message",
+ header: {
+ title: "not campaign but not paid",
+ subtitle: "not campaign but not paid",
+ },
+ properties: {},
+ },
+ ],
+ },
+ {
+ width: 12,
+ campaign_id: "123456",
+ sponsored: true,
+ components: [
+ {
+ type: "Message",
+ header: {
+ title: "Sponsored by Collection Title",
+ subtitle: "Sponsored by Brand Name",
+ },
+ properties: {},
+ styles: {
+ ".ds-message": "margin-bottom: -28px",
+ },
+ },
{
type: "CardGrid",
properties: {
- items: 21,
+ items: 3,
},
header: {
title: "",
@@ -1299,22 +1380,20 @@ defaultLayoutResp = {
url:
"https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=3&consumer_key=$apiKey&locale_lang=en-US&count=30",
},
- spocs: {
- probability: 1,
- positions: [
- {
- index: 2,
- },
- {
- index: 4,
- },
- {
- index: 11,
- },
- {
- index: 20,
- },
- ],
+ },
+ {
+ type: "Hero",
+ properties: {
+ items: 5,
+ offset: 3,
+ },
+ header: {
+ title: "",
+ },
+ feed: {
+ embed_reference: null,
+ url:
+ "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=3&consumer_key=$apiKey&locale_lang=en-US&count=30",
},
},
{