Skip to content

Commit

Permalink
Brave Today: promoted content
Browse files Browse the repository at this point in the history
  • Loading branch information
petemill committed Dec 1, 2020
1 parent 299ea7c commit 5dc8f42
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default async function getBraveTodayData (
enabledPublishers: BraveToday.Publishers
): Promise<BraveToday.Feed | undefined> {
// No sponsor content yet, wait until we have content spec
// const sponsors: (BraveToday.Article)[] = []
const promotedArticles: (BraveToday.PromotedArticle)[] = []
const deals: (BraveToday.Deal)[] = []
let articles: (BraveToday.Article)[] = []

Expand All @@ -55,9 +55,9 @@ export default async function getBraveTodayData (
for (let feedItem of feedContent) {
feedItem = convertFeedItem(feedItem)
switch (feedItem.content_type) {
// case 'offer':
// sponsors.push(feedItem as BraveToday.Article)
// break
case 'brave_partner':
promotedArticles.push(feedItem)
break
case 'product':
deals.push(feedItem)
break
Expand Down Expand Up @@ -103,9 +103,6 @@ export default async function getBraveTodayData (
const dealsCategoriesByPriority = Array.from(dealsCategoryCounts.keys())
.sort((a, b) => dealsCategoryCounts[a] - dealsCategoryCounts[b])

// TODO(petemill): Sponsor
// const firstSponsors = sponsors.splice(0, 1) // Featured sponsor is the first sponsor

const firstHeadlines = take(articles, 1, isArticleTopNews)
const firstDeals = deals.splice(0, 3)

Expand All @@ -119,7 +116,7 @@ export default async function getBraveTodayData (
while (canGenerateAnotherPage && curPage++ < maxPages) {
const category = categoriesByPriority.shift()
const dealsCategory = dealsCategoriesByPriority.shift()
const nextPage = generateNextPage(articles, deals, category, dealsCategory)
const nextPage = generateNextPage(articles, deals, promotedArticles, category, dealsCategory)
if (!nextPage) {
canGenerateAnotherPage = false
continue
Expand All @@ -128,7 +125,6 @@ export default async function getBraveTodayData (
}

return {
// featuredSponsor: firstSponsors.length ? firstSponsors[0] : undefined,
hash,
featuredArticle: firstHeadlines.length ? firstHeadlines[0] : undefined,
featuredDeals: firstDeals,
Expand All @@ -153,6 +149,7 @@ function isArticleWithin48Hours (article: BraveToday.Article) {
function generateNextPage (
articles: BraveToday.Article[],
allDeals: BraveToday.Deal[],
promotedArticles: BraveToday.PromotedArticle[],
featuredCategory?: string,
dealsCategory?: string): BraveToday.Page | null {

Expand All @@ -177,6 +174,8 @@ function generateNextPage (
deals = deals.concat(allDeals.splice(0, 3 - deals.length))
}

const pagePromotedArticles = take(promotedArticles, 1)

const publisherInfo = generateArticleSourceGroup(articles)

const randomArticles = take(articles, 4, isArticleWithin48Hours, true)
Expand All @@ -185,6 +184,7 @@ function generateNextPage (
articles: headlines,
randomArticles,
deals,
promotedArticle: pagePromotedArticles.length ? pagePromotedArticles[0] : undefined,
itemsByCategory: (featuredCategory && articlesWithCategory.length) ? { categoryName: featuredCategory, items: articlesWithCategory } : undefined,
itemsByPublisher: publisherInfo ? { name: publisherInfo[0], items: publisherInfo[1] } : undefined
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import styled from 'brave-ui/theme'
import {
Block as StandardBlock,
Heading as StandardHeading,
Image as StandardImage,
Text as StandardText,
Time as StandardTime
} from './default'
Expand Down Expand Up @@ -58,7 +57,11 @@ export const ListImageFrame = styled(ImageFrame)`
padding-top: 0;
`

export const Image = styled(StandardImage)`
type ImageProps = {
isPromoted?: boolean
}
export const Image = styled<ImageProps, 'img'>('img')`
box-sizing: border-box;
display: block;
position: absolute;
border: none;
Expand All @@ -69,7 +72,7 @@ export const Image = styled(StandardImage)`
right: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-fit: ${p => p.isPromoted ? 'contain' : 'cover'};
background-color: rgba(188,188,188,0.2);
`

Expand Down Expand Up @@ -105,14 +108,43 @@ export const Time = styled(StandardTime)`
}
`

export const Source = styled('div')`
margin: 10px 0 0 0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
`

export const Publisher = styled('div')`
box-sizing: border-box;
margin: 10px 0 0 0;
padding: 0;
font: 500 14px ${p => p.theme.fontFamily.heading};
color: #fff;
`

export const PromotedLabel = styled('a')`
border-radius: 4px;
background: #495057;
color: rgba(255, 255, 255, .8);
padding: 5px;
font: 400 12px ${p => p.theme.fontFamily.heading};
display: flex;
flex-direction: row;
gap: 5px;
`

export const PromotedIcon = styled('div')`
width: 16px;
height: 9px;
color: inherit;
svg {
width: 100%;
height: 100%;
color: inherit;
fill: currentColor;
}
`

export const ContainerForTwo = styled<{}, 'div'>('div')`
width: 680px;
display: grid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as Background from '../../../../../common/Background'
type Props = {
imageUrl: string
list?: boolean
isPromoted?: boolean
onLoaded?: () => any
}

Expand Down Expand Up @@ -61,7 +62,7 @@ export default function CardImage (props: Props) {
const Frame = props.list ? Card.ListImageFrame : Card.ImageFrame
return (
<Frame isImageLoaded={isImageLoaded}>
<Card.Image src={unpaddedUrl} />
<Card.Image isPromoted={props.isPromoted} src={unpaddedUrl} />
</Frame>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,49 @@ import { OnReadFeedItem, OnSetPublisherPref } from '../../'
// TODO(petemill): Large and Medium article should be combined to 1 component.

interface Props {
content: (BraveToday.Article | undefined)[]
content: (BraveToday.Article | BraveToday.PromotedArticle | undefined)[]
publishers: BraveToday.Publishers
articleToScrollTo?: BraveToday.FeedItem
onReadFeedItem: OnReadFeedItem
onSetPublisherPref: OnSetPublisherPref
isPromoted?: boolean
}

type ArticleProps = {
item: BraveToday.Article
item: BraveToday.Article | BraveToday.PromotedArticle
publisher?: BraveToday.Publisher
shouldScrollIntoView?: boolean
onReadFeedItem: OnReadFeedItem
onSetPublisherPref: OnSetPublisherPref
isPromoted?: boolean
}

const promotedInfoUrl = 'https://brave.com/brave-today'

function onClickPromoted (e: React.MouseEvent) {
const openInNewTab = e.ctrlKey || e.metaKey
if (openInNewTab) {
document.open(promotedInfoUrl, '__blank')
} else {
window.location.href = promotedInfoUrl
}
e.preventDefault()
}

const LargeArticle = React.forwardRef<HTMLElement, ArticleProps>(function (props: ArticleProps, ref) {
const { publisher, item } = props
const [cardRef] = useScrollIntoView(props.shouldScrollIntoView || false)

const onClick = useReadArticleClickHandler(props.onReadFeedItem, item)

// TODO(petemill): Avoid nested links
// `ref as any` due to https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28884
return (
<Card.Large innerRef={ref as any}>
<a onClick={onClick} href={item.url} ref={cardRef}>
<CardImage
imageUrl={item.img}
isPromoted={props.isPromoted}
/>
<Card.Content>
<Card.Heading>
Expand All @@ -46,12 +64,29 @@ const LargeArticle = React.forwardRef<HTMLElement, ArticleProps>(function (props
<Card.Time>{item.relative_time}</Card.Time>
{
publisher &&
<Card.Source>
<Card.Publisher>
<PublisherMeta
publisher={publisher}
onSetPublisherPref={props.onSetPublisherPref}
/>
</Card.Publisher>
{props.isPromoted &&
<Card.PromotedLabel onClick={onClickPromoted} href={promotedInfoUrl}>
<Card.PromotedIcon>
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 9'>
<path
fill='#fff'
fillRule='evenodd'
d='M14.56 4.77a.9.9 0 01-.9-.9v-.83L8.73 7.27a.9.9 0 01-1.23-.05L5.36 5.08 1.47 8.19A.9.9 0 01.2 8.05a.9.9 0 01.14-1.27l4.52-3.62a.9.9 0 011.2.07L8.2 5.35l3.84-3.3h-.18a.9.9 0 110-1.8h2.71c.4 0 .67.26.77.62v.05c.02.08.05.15.05.23v2.72c0 .5-.32.9-.82.9z'
clipRule='evenodd'
/>
</svg>
</Card.PromotedIcon>
Promoted
</Card.PromotedLabel>
}
</Card.Source>
}
</Card.Content>
</a>
Expand Down Expand Up @@ -86,6 +121,7 @@ const CardSingleArticleLarge = React.forwardRef<HTMLElement, Props>(function (pr
shouldScrollIntoView={shouldScrollIntoView}
onReadFeedItem={props.onReadFeedItem}
onSetPublisherPref={props.onSetPublisherPref}
isPromoted={props.isPromoted}
/>
)
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum CardType {
HeadlinePaired,
CategoryGroup,
Deals,
PromotedArticle,
PublisherGroup
}

Expand All @@ -30,6 +31,7 @@ const PageContentOrder = [
CardType.Headline,
CardType.HeadlinePaired,
CardType.HeadlinePaired,
CardType.PromotedArticle,
CardType.CategoryGroup,
CardType.Headline,
CardType.Deals,
Expand Down Expand Up @@ -80,6 +82,18 @@ function Card (props: CardProps) {
onReadFeedItem={props.onReadFeedItem}
onSetPublisherPref={props.onSetPublisherPref}
/>
case CardType.PromotedArticle:
if (!props.content.promotedArticle) {
return null
}
return <CardLarge
isPromoted={true}
content={[props.content.promotedArticle]}
publishers={props.publishers}
articleToScrollTo={props.articleToScrollTo}
onReadFeedItem={props.onReadFeedItem}
onSetPublisherPref={props.onSetPublisherPref}
/>
case CardType.CategoryGroup:
if (!props.content.itemsByCategory) {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,22 @@ export default function getTodayState (): BraveTodayState {
],
pages: [
{
promotedArticle: {
category: `Brave Partners`,
publish_time: `2020-10-21 21:46:00 UTC`,
url: `https://www.autoblog.com/2020/10/21/tesla-quarterly-earnings-net-profit/`,
img: `https://pcdn.brave.software/brave-today/cache/bc35efae36f1a6e590f68a30858c27c7e0adfb5f508bc1267798a2fe000eaea6.jpg.pad`,
title: `Tesla posts net profit for fifth straight quarter`,
description: `Filed under:\n\t\t\t\t\t Earnings/Financials,Green,Tesla,Electric\n\t\t\t\t\t Tesla charged through a summertime auto industry sales slump in the U.S. to post stronger-than-expected net earnings for the third quarter. The electric car and solar panel maker says Wednesday that it made $331 million, or 27 cents per share, for its fifth-straight profitable quarter. Excluding special items such as stock-based compensation, Tesla made 76 cents per share, beating Wall Street estimates of 57 cents.Continue`,
content_type: `brave_partner`,
publisher_id: `5eece347713f329f156cd0204cf9b12629f1dc8f4ea3c1b67984cfbfd66cdca5`,
publisher_name: `Autoblog`,
url_hash: `963e1dbf6eccd9cdf0d273a89f3bc25c727f871104bf9e91d98db796fd09b696`,
padded_img: `https://pcdn.brave.software/brave-today/cache/bc35efae36f1a6e590f68a30858c27c7e0adfb5f508bc1267798a2fe000eaea6.jpg.pad`,
score: 19.716764507900656,
points: 14.716764507900656,
relative_time: `about 6 hours ago`
},
articles: [
{
category: `Cars`,
Expand Down
10 changes: 7 additions & 3 deletions components/definitions/today.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ declare namespace BraveToday {
}
}

export type FeedItem = (Article | Deal)
export type FeedItem = (Article | Deal | PromotedArticle)

export interface Feed {
hash: string
Expand All @@ -62,7 +62,7 @@ declare namespace BraveToday {
items: Article[] // 3
}
deals: Deal[] // 3

promotedArticle?: PromotedArticle
}

export interface ScrollingList {
Expand All @@ -72,7 +72,7 @@ declare namespace BraveToday {
}

type BaseFeedItem = {
content_type: 'article' | 'product'
content_type: 'article' | 'product' | 'brave_partner'
category: string // 'Tech', 'Business', 'Top News', 'Crypto', 'Cars', 'Culture', 'Fashion', 'Sports', 'Entertainment'
publish_time: string // UTC "2020-04-17 19:21:10"
title: string // "14 Truly Incredible Catfish Makeup Transformations From TikTok"
Expand All @@ -93,6 +93,10 @@ declare namespace BraveToday {
content_type: 'article'
}

export type PromotedArticle = BaseFeedItem & {
content_type: 'brave_partner'
}

export type Deal = BaseFeedItem & {
content_type: 'product'
offers_category: string // 'Companion Products
Expand Down

0 comments on commit 5dc8f42

Please sign in to comment.