Skip to content
This repository has been archived by the owner on Feb 29, 2020. It is now read-only.

Commit

Permalink
feat(content): #870 Implement recommendation highlight
Browse files Browse the repository at this point in the history
  • Loading branch information
sarracini committed Jul 8, 2016
1 parent c756bb5 commit 408817d
Show file tree
Hide file tree
Showing 18 changed files with 440 additions and 50 deletions.
28 changes: 19 additions & 9 deletions common/action-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const am = new ActionManager([
"EXPERIMENTS_RESPONSE",
"NOTIFY_BLOCK_URL",
"NOTIFY_UNBLOCK_URL",
"NOTIFY_UNBLOCK_ALL",
"NOTIFY_BOOKMARK_ADD",
"NOTIFY_BOOKMARK_DELETE",
"NOTIFY_HISTORY_DELETE",
Expand All @@ -38,7 +37,10 @@ const am = new ActionManager([
"NOTIFY_PERFORMANCE",
"NOTIFY_USER_EVENT",
"NOTIFY_OPEN_WINDOW",
"NOTIFY_UPDATE_SEARCH_STRING"
"NOTIFY_UPDATE_SEARCH_STRING",
"NOTIFY_BLOCK_RECOMMENDATION",
"NOTIFY_TOGGLE_RECOMMENDATIONS",
"RECEIVE_RECOMMENDATION_TOGGLE"
]);

// This is a a set of actions that have sites in them,
Expand Down Expand Up @@ -86,6 +88,9 @@ function RequestExpect(type, expect, options = {}) {
if (options.append) {
action.meta.append = true;
}
if (options.meta) {
action.meta = Object.assign({}, options.meta, action.meta);
}
return action;
}

Expand All @@ -101,7 +106,7 @@ function RequestMoreBookmarks(beforeDate) {
return RequestBookmarks({
data: {beforeDate},
append: true,
skipPreviewRequest: true
meta: {skipPreviewRequest: true}
});
}

Expand All @@ -113,12 +118,12 @@ function RequestMoreRecentLinks(beforeDate) {
return RequestRecentLinks({
data: {beforeDate},
append: true,
skipPreviewRequest: true
meta: {skipPreviewRequest: true}
});
}

function RequestHighlightsLinks() {
return RequestExpect("HIGHLIGHTS_LINKS_REQUEST", "HIGHLIGHTS_LINKS_RESPONSE");
return RequestExpect("HIGHLIGHTS_LINKS_REQUEST", "HIGHLIGHTS_LINKS_RESPONSE", {meta: {getRecommendation: true}});
}

function RequestSearchState() {
Expand Down Expand Up @@ -177,8 +182,12 @@ function NotifyUnblockURL(url) {
return Notify("NOTIFY_UNBLOCK_URL", url);
}

function NotifyUnblockAll() {
return Notify("NOTIFY_UNBLOCK_ALL");
function NotifyBlockRecommendation(url) {
return Notify("NOTIFY_BLOCK_RECOMMENDATION", url);
}

function NotifyToggleRecommendations() {
return Notify("NOTIFY_TOGGLE_RECOMMENDATIONS");
}

function NotifyPerformSearch(data) {
Expand Down Expand Up @@ -226,7 +235,6 @@ am.defineActions({
RequestExperiments,
NotifyBlockURL,
NotifyUnblockURL,
NotifyUnblockAll,
NotifyBookmarkAdd,
NotifyBookmarkDelete,
NotifyHistoryDelete,
Expand All @@ -238,7 +246,9 @@ am.defineActions({
NotifyUpdateSearchString,
NotifyManageEngines,
NotifyRemoveFormHistory,
NotifyCycleEngine
NotifyCycleEngine,
NotifyBlockRecommendation,
NotifyToggleRecommendations
});

module.exports = am;
2 changes: 1 addition & 1 deletion common/event-constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const constants = {
"SEARCH",
"SHARE",
"UNBLOCK",
"UNBLOCK_ALL"
"BLOCK_RECOMMENDATION",
]),
sources: new Set([
"TOP_SITES",
Expand Down
24 changes: 21 additions & 3 deletions content-src/components/LinkMenu/LinkMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const LinkMenu = React.createClass({

// Don't add delete options for default links
// that show up if your history is empty
if (isNotDefault && page !== "TIMELINE_BOOKMARKS") {
if ((isNotDefault && page !== "TIMELINE_BOOKMARKS") && !site.recommended) {
deleteOptions = [
allowBlock && {
ref: "dismiss",
Expand All @@ -56,6 +56,23 @@ const LinkMenu = React.createClass({
deleteOptions.unshift({type: "separator"});
}

// Only show this option if the highlights card is a recommendation
let recommendationOptions;
if (site.recommended) {
recommendationOptions = [
{type: "separator"},
{
ref: "hideRecommendation",
label: "Dismiss",
icon: "dismiss",
userEvent: "BLOCK_RECOMMENDATION",
onClick: () => {
dispatch(actions.NotifyBlockRecommendation(site.url));
dispatch(actions.RequestHighlightsLinks());
}
}];
}

return [
(site.bookmarkGuid ? {
ref: "removeBookmark",
Expand Down Expand Up @@ -85,7 +102,7 @@ const LinkMenu = React.createClass({
userEvent: "OPEN_PRIVATE_WINDOW",
onClick: () => dispatch(actions.NotifyOpenWindow({url: site.url, isPrivate: true}))
}]
.concat(deleteOptions).filter(o => o);
.concat(deleteOptions).concat(recommendationOptions).filter(o => o);
},
render() {
return (<ContextMenu
Expand All @@ -102,7 +119,8 @@ LinkMenu.propTypes = {
allowBlock: React.PropTypes.bool,
site: React.PropTypes.shape({
url: React.PropTypes.string.isRequired,
bookmarkGuid: React.PropTypes.string
bookmarkGuid: React.PropTypes.string,
recommended: React.PropTypes.bool
}).isRequired,

// This is for events
Expand Down
13 changes: 6 additions & 7 deletions content-src/components/NewTabPage/NewTabPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,9 @@ const NewTabPage = React.createClass({
showSettingsMenu: false
};
},
resetBlockList() {
this.props.dispatch(actions.NotifyEvent({
event: "UNBLOCK_ALL",
page: PAGE_NAME
}));
this.props.dispatch(actions.NotifyUnblockAll());
toggleRecommendation() {
this.props.dispatch(actions.NotifyToggleRecommendations());
this.props.dispatch(actions.RequestHighlightsLinks());
},
componentDidMount() {
document.title = "New Tab";
Expand All @@ -38,6 +35,8 @@ const NewTabPage = React.createClass({
},
render() {
const props = this.props;
let recommendationLabel = "Show Trending Highlights";
let recommendationIcon = this.props.Spotlight.recommendationShown ? "check" : " ";
return (<main className="new-tab">
<div className="new-tab-wrapper">
<section>
Expand Down Expand Up @@ -79,7 +78,7 @@ const NewTabPage = React.createClass({
visible={this.state.showSettingsMenu}
onUpdate={showSettingsMenu => this.setState({showSettingsMenu})}
options={[
{label: "Reset Block List", onClick: this.resetBlockList}
{icon: `${recommendationIcon}`, label: `${recommendationLabel}`, onClick: this.toggleRecommendation}
]} />
</span>
</section>
Expand Down
40 changes: 36 additions & 4 deletions content-src/components/Spotlight/Spotlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const DEFAULT_LENGTH = 3;
const SpotlightItem = React.createClass({
getInitialState() {
return {
showContextMenu: false
showContextMenu: false,
hover: false
};
},
getDefaultProps() {
Expand All @@ -22,12 +23,23 @@ const SpotlightItem = React.createClass({
bestImage: {}
};
},
onMouseIn(site) {
if (site.recommended) {
this.setState({hover: true});
}
},
onMouseOut(site) {
if (site.recommended) {
this.setState({hover: false});
}
},
render() {
const site = this.props;
const image = site.bestImage;
const imageUrl = image.url;
const description = site.description || site.url;
const isPortrait = image.height > image.width;
const timestamp = site.timestamp;

let contextMessage;
if (site.context_message) {
Expand All @@ -38,6 +50,8 @@ const SpotlightItem = React.createClass({
contextMessage = `Visited ${moment(site.lastVisitDate).fromNow()}`;
} else if (site.type === "bookmark") {
contextMessage = "Bookmarked recently";
} else if (site.recommended === true) {
contextMessage = "Trending";
} else {
contextMessage = "Visited recently";
}
Expand All @@ -50,6 +64,10 @@ const SpotlightItem = React.createClass({
style.backgroundColor = site.backgroundColor;
}

const toolTipStyle = {
display: this.state.hover ? "block" : "none"
};

return (<li className={classNames("spotlight-item", {active: this.state.showContextMenu})}>
<a onClick={this.props.onClick} href={site.url} ref="link">
<div className={classNames("spotlight-image", {portrait: isPortrait})} style={style} ref="image">
Expand All @@ -61,11 +79,22 @@ const SpotlightItem = React.createClass({
<h4 ref="title" className="spotlight-title">{site.title}</h4>
<p className="spotlight-description" ref="description">{description}</p>
</div>
<div className="spotlight-context" ref="contextMessage">{contextMessage}</div>
<div className="spotlight-context"
onMouseOver={() => this.onMouseIn(site)}
onMouseOut={() => this.onMouseOut(site)}>
{site.recommended ? <div className="icon-pocket"></div> : null}
<div className={site.recommended ? "recommended-context" : ""}
data-timestamp={site.recommended ? `${timestamp}` : ""}
ref="contextMessage">{contextMessage}</div>
</div>
</div>
</div>
<div className="inner-border" />
</a>
<div className="spotlight-tooltip" style={toolTipStyle}>
This page is trending on Pocket right now.
<div className="anchor"></div>
</div>
<LinkMenuButton onClick={() => this.setState({showContextMenu: true})} />
<LinkMenu
visible={this.state.showContextMenu}
Expand Down Expand Up @@ -98,14 +127,17 @@ const Spotlight = React.createClass({
page: "NEW_TAB"
};
},
onClickFactory(index) {
onClickFactory(index, site) {
return () => {
this.props.dispatch(actions.NotifyEvent({
event: "CLICK",
page: this.props.page,
source: "FEATURED",
action_position: index
}));
if (site.recommended) {
this.props.dispatch(actions.NotifyBlockRecommendation(site.url));
}
};
},
render() {
Expand All @@ -122,7 +154,7 @@ const Spotlight = React.createClass({
key={site.guid || site.cacheKey || i}
page={this.props.page}
source="FEATURED"
onClick={this.onClickFactory(i)}
onClick={this.onClickFactory(i, site)}
{...site} />)}
{blankSites}
</ul>
Expand Down
49 changes: 49 additions & 0 deletions content-src/components/Spotlight/Spotlight.scss
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,59 @@
overflow: hidden;
}

.icon-pocket {
display: inline-block;
width: $icon-size;
height: $icon-size;
background-position: center center;
background-repeat: no-repeat;
vertical-align: middle;
background-image: url('img/glyph-pocket-16.svg');
background-size: $icon-size;
}

.recommended-context {
margin-top: -15px;
margin-left: 20px;

&::after {
content: attr(data-timestamp);
float: right;
}
}

&.spotlight-placeholder {
background: $placeholder-background;
border: $placeholder-border;
box-shadow: none;
height: $spotlight-item-height;
}

.spotlight-tooltip {
width: $spotlight-item-width;
height: $tooltip-height;
background-color: $tooltip-background-color;
position: relative;
margin: $tooltip-margin;
border-radius: $tooltip-border-radius;
border: $tooltip-border-properties;
text-align: center;
font-size: 11px;
padding: $tooltip-padding;
box-shadow: $tooltip-box-shadow;
z-index: 999;
}

.anchor {
width: $tooltip-anchor-width;
height: $tooltip-anchor-height;
background-color: $tooltip-background-color;
display: block;
position: relative;
margin: $tooltip-anchor-margin;
transform: rotate(45deg);
border: $tooltip-border-properties;
border-bottom-style: none;
border-right-style: none;
}
}
6 changes: 5 additions & 1 deletion content-src/reducers/SetRowsOrError.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const DEFAULTS = {
error: false,
init: false,
isLoading: false,
canLoadMore: true
canLoadMore: true,
recommendationShown: true
};

module.exports = function setRowsOrError(requestType, responseType, querySize) {
Expand Down Expand Up @@ -47,6 +48,9 @@ module.exports = function setRowsOrError(requestType, responseType, querySize) {
}
});
break;
case am.type("RECEIVE_RECOMMENDATION_TOGGLE"):
state.recommendationShown = action.data;
break;
case am.type("NOTIFY_BLOCK_URL"):
case am.type("NOTIFY_HISTORY_DELETE"):
state.rows = prevState.rows.filter(val => val.url !== action.data);
Expand Down
5 changes: 5 additions & 0 deletions content-src/selectors/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ module.exports.selectNewTabSites = createSelector(
// Note that we have to limit the length of topsites, spotlight so we
// don't dedupe against stuff that isn't shown
let [topSitesRows, spotlightRows] = dedupe.group([TopSites.rows.slice(0, TOP_SITES_LENGTH), Spotlight.rows]);

// find the recommended item and move it to the third spot in highlights
if (spotlightRows.length > 0) {
spotlightRows.splice(2, 0, spotlightRows.splice(spotlightRows.findIndex(element => element.recommended), 1)[0]);
}
spotlightRows = spotlightRows.slice(0, SPOTLIGHT_LENGTH);
const historyRows = dedupe.group([
topSitesRows,
Expand Down
15 changes: 15 additions & 0 deletions content-src/static/img/glyph-check-16.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 408817d

Please sign in to comment.