new homepage

This commit is contained in:
Sean Yesmunt 2020-01-20 11:47:03 -05:00
parent 8bfd10b5f4
commit ba675f48c9
20 changed files with 663 additions and 39 deletions

View file

@ -40,11 +40,6 @@ async function redirectMiddleware(ctx, next) {
return; return;
} }
if (path === '/') {
ctx.redirect(`/$/${PAGES.CHANNELS_FOLLOWING}`);
return;
}
// No redirects needed // No redirects needed
await next(); await next();
} }

View file

@ -905,5 +905,6 @@
"This Month": "This Month", "This Month": "This Month",
"This Year": "This Year", "This Year": "This Year",
"Khmer": "Khmer", "Khmer": "Khmer",
"Invites": "Invites" "Invites": "Invites",
"This is an experiment, and may be removed in the future. Publish something with the #homepagecagematch tag to battle for the top spot on the home page!": "This is an experiment, and may be removed in the future. Publish something with the #homepagecagematch tag to battle for the top spot on the home page!"
} }

View file

@ -0,0 +1,32 @@
import { connect } from 'react-redux';
import {
doResolveUri,
makeSelectClaimForUri,
makeSelectIsUriResolving,
makeSelectThumbnailForUri,
makeSelectTitleForUri,
doFileGet,
makeSelectChannelForClaimUri,
} from 'lbry-redux';
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
import ClaimPreviewTile from './view';
const select = (state, props) => ({
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
channel: props.uri && makeSelectChannelForClaimUri(props.uri)(state),
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
thumbnail: props.uri && makeSelectThumbnailForUri(props.uri)(state),
title: props.uri && makeSelectTitleForUri(props.uri)(state),
blackListedOutpoints: selectBlackListedOutpoints(state),
filteredOutpoints: selectFilteredOutpoints(state),
});
const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
getFile: uri => dispatch(doFileGet(uri, false)),
});
export default connect(
select,
perform
)(ClaimPreviewTile);

View file

@ -0,0 +1,167 @@
// @flow
import React from 'react';
import classnames from 'classnames';
import { NavLink, withRouter } from 'react-router-dom';
import FileThumbnail from 'component/fileThumbnail';
import UriIndicator from 'component/uriIndicator';
import TruncatedText from 'component/common/truncated-text';
import DateTime from 'component/dateTime';
import ChannelThumbnail from 'component/channelThumbnail';
import SubscribeButton from 'component/subscribeButton';
import useGetThumbnail from 'effects/use-get-thumbnail';
import { formatLbryUrlForWeb } from 'util/url';
import { parseURI } from 'lbry-redux';
type Props = {
uri: string,
claim: ?Claim,
resolveUri: string => void,
isResolvingUri: boolean,
history: { push: string => void },
thumbnail: string,
title: string,
placeholder: boolean,
blackListedOutpoints: Array<{
txid: string,
nout: number,
}>,
filteredOutpoints: Array<{
txid: string,
nout: number,
}>,
blockedChannelUris: Array<string>,
getFile: string => void,
placeholder: boolean,
streamingUrl: string,
};
function ClaimPreviewTile(props: Props) {
const {
history,
uri,
isResolvingUri,
thumbnail,
title,
resolveUri,
claim,
placeholder,
blackListedOutpoints,
filteredOutpoints,
getFile,
streamingUrl,
} = props;
const shouldFetch = claim === undefined;
const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail;
const navigateUrl = uri ? formatLbryUrlForWeb(uri) : undefined;
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
let isChannel;
let isValid = false;
if (uri) {
try {
({ isChannel } = parseURI(uri));
isValid = true;
} catch (e) {
isValid = false;
}
}
const signingChannel = claim && claim.signing_channel;
let channelThumbnail;
if (signingChannel) {
channelThumbnail =
// I should be able to just pass the the uri to <ChannelThumbnail /> but it wasn't working
// Come back to me
(signingChannel.value && signingChannel.value.thumbnail && signingChannel.value.thumbnail.url) || undefined;
}
function handleClick(e) {
if (navigateUrl) {
history.push(navigateUrl);
}
}
React.useEffect(() => {
if (isValid && !isResolvingUri && shouldFetch && uri) {
resolveUri(uri);
}
}, [isValid, isResolvingUri, uri, resolveUri, shouldFetch]);
let shouldHide = false;
// This will be replaced once blocking is done at the wallet server level
if (claim && !shouldHide && blackListedOutpoints) {
shouldHide = blackListedOutpoints.some(
outpoint =>
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
);
}
// We're checking to see if the stream outpoint
// or signing channel outpoint is in the filter list
if (claim && !shouldHide && filteredOutpoints) {
shouldHide = filteredOutpoints.some(
outpoint =>
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
);
}
if (shouldHide) {
return null;
}
if (placeholder || isResolvingUri) {
return (
<li className={classnames('claim-preview--tile', {})}>
<div className="placeholder media__thumb" />
<div className="placeholder__wrapper">
<div className="placeholder claim-tile__title" />
<div className="placeholder claim-tile__info" />
</div>
</li>
);
}
return (
<li
role="link"
onClick={handleClick}
className={classnames('card claim-preview--tile', {
'claim-preview--channel': isChannel,
})}
>
<NavLink to={navigateUrl}>
<FileThumbnail thumbnail={thumbnailUrl} />
</NavLink>
<NavLink to={navigateUrl}>
<h2 className="claim-tile__title">
<TruncatedText text={title} lines={2} />
</h2>
</NavLink>
<div className="claim-tile__info">
{isChannel ? (
<div className="claim-tile__about--channel">
<SubscribeButton uri={uri} />
<span>
{claimsInChannel === 1
? __('%claimsInChannel% publish', { claimsInChannel })
: __('%claimsInChannel% publishes', { claimsInChannel })}
</span>
</div>
) : (
<React.Fragment>
<ChannelThumbnail thumbnailPreview={channelThumbnail} />
<div className="claim-tile__about">
<UriIndicator uri={uri} link />
<DateTime timeAgo uri={uri} />
</div>
</React.Fragment>
)}
</div>
</li>
);
}
export default withRouter(ClaimPreviewTile);

View file

@ -0,0 +1,28 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import {
doClaimSearch,
selectClaimSearchByQuery,
selectFetchingClaimSearch,
doToggleTagFollow,
selectBlockedChannels,
} from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import ClaimListDiscover from './view';
const select = state => ({
claimSearchByQuery: selectClaimSearchByQuery(state),
loading: selectFetchingClaimSearch(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
hiddenUris: selectBlockedChannels(state),
});
const perform = {
doClaimSearch,
doToggleTagFollow,
};
export default connect(
select,
perform
)(ClaimListDiscover);

View file

@ -0,0 +1,99 @@
// @flow
import React from 'react';
import { createNormalizedClaimSearchKey, MATURE_TAGS } from 'lbry-redux';
import ClaimPreviewTile from 'component/claimPreviewTile';
type Props = {
uris: Array<string>,
doClaimSearch: ({}) => void,
loading: boolean,
showNsfw: boolean,
history: { action: string, push: string => void, replace: string => void },
claimSearchByQuery: {
[string]: Array<string>,
},
// claim search options are below
tags: Array<string>,
hiddenUris: Array<string>,
channelIds?: Array<string>,
pageSize: number,
orderBy?: Array<string>,
releaseTime?: string,
claimType?: string,
timestamp?: string,
};
function ClaimTilesDiscover(props: Props) {
const {
doClaimSearch,
claimSearchByQuery,
loading,
showNsfw,
hiddenUris,
// Below are options to pass that are forwarded to claim_search
tags,
channelIds,
orderBy,
pageSize = 8,
releaseTime,
claimType,
timestamp,
} = props;
const options: {
page_size: number,
no_totals: boolean,
any_tags: Array<string>,
channel_ids: Array<string>,
channel_ids: Array<string>,
not_channel_ids: Array<string>,
not_tags: Array<string>,
order_by: Array<string>,
release_time?: string,
claim_type?: string,
timestamp?: string,
} = {
page_size: pageSize,
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
// it's faster, but we will need to remove it if we start using total_pages
no_totals: true,
any_tags: tags || [],
not_tags: !showNsfw ? MATURE_TAGS : [],
channel_ids: channelIds || [],
not_channel_ids: hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : [],
order_by: orderBy || ['trending_group', 'trending_mixed'],
};
if (releaseTime) {
options.release_time = releaseTime;
}
if (claimType) {
options.claim_type = claimType;
}
if (timestamp) {
options.timestamp = timestamp;
}
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
const uris = claimSearchByQuery[claimSearchCacheQuery] || [];
const shouldPerformSearch = uris.length === 0 || (!loading && uris.length < pageSize);
// Don't use the query from createNormalizedClaimSearchKey for the effect since that doesn't include page & release_time
const optionsStringForEffect = JSON.stringify(options);
React.useEffect(() => {
if (shouldPerformSearch) {
const searchOptions = JSON.parse(optionsStringForEffect);
doClaimSearch(searchOptions);
}
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect]);
return (
<ul className="claim-grid">
{uris && uris.length
? uris.map(uri => <ClaimPreviewTile key={uri} uri={uri} />)
: new Array(pageSize).fill(1).map((x, i) => <ClaimPreviewTile key={i} placeholder />)}
</ul>
);
}
export default ClaimTilesDiscover;

View file

@ -48,18 +48,12 @@ export const icons = {
), ),
[ICONS.ARROW_LEFT]: buildIcon( [ICONS.ARROW_LEFT]: buildIcon(
<g fill="none" fillRule="evenodd" strokeLinecap="round"> <g fill="none" fillRule="evenodd" strokeLinecap="round">
<path d="M4, 12 L21, 12" /> <polyline points="15 18 9 12 15 6" />
<polyline
strokeLinejoin="round"
transform="translate(7.000000, 12.000000) scale(-1, 1) translate(-7.000000, -12.000000)"
points="3 4 11 12 3 20"
/>
</g> </g>
), ),
[ICONS.ARROW_RIGHT]: buildIcon( [ICONS.ARROW_RIGHT]: buildIcon(
<g fill="none" fillRule="evenodd" strokeLinecap="round"> <g fill="none" fillRule="evenodd" strokeLinecap="round">
<path d="M3, 12 L20, 12" /> <polyline points="9 18 15 12 9 6" />
<polyline strokeLinejoin="round" points="13 4 21 12 13 20" />
</g> </g>
), ),
[ICONS.HOME]: buildIcon( [ICONS.HOME]: buildIcon(
@ -364,4 +358,13 @@ export const icons = {
<path d="M16 3.13a4 4 0 0 1 0 7.75" /> <path d="M16 3.13a4 4 0 0 1 0 7.75" />
</g> </g>
), ),
[ICONS.SHUFFLE]: buildIcon(
<g>
<polyline points="16 3 21 3 21 8" />
<line x1="4" y1="20" x2="21" y2="3" />
<polyline points="21 16 21 21 16 21" />
<line x1="15" y1="15" x2="21" y2="21" />
<line x1="4" y1="4" x2="9" y2="9" />
</g>
),
}; };

View file

@ -14,6 +14,7 @@ const BLUE_COLOR = '#49b2e2';
type Props = { type Props = {
icon: string, icon: string,
tooltip?: boolean, tooltip?: boolean,
customTooltipText?: string,
iconColor?: string, iconColor?: string,
size?: number, size?: number,
className?: string, className?: string,
@ -50,7 +51,7 @@ class IconComponent extends React.PureComponent<Props> {
}; };
render() { render() {
const { icon, tooltip, iconColor, size, className, sectionIcon = false } = this.props; const { icon, tooltip, customTooltipText, iconColor, size, className, sectionIcon = false } = this.props;
const Icon = icons[this.props.icon]; const Icon = icons[this.props.icon];
if (!Icon) { if (!Icon) {
@ -66,7 +67,7 @@ class IconComponent extends React.PureComponent<Props> {
let tooltipText; let tooltipText;
if (tooltip) { if (tooltip) {
tooltipText = this.getTooltip(icon); tooltipText = customTooltipText || this.getTooltip(icon);
} }
const component = ( const component = (

View file

@ -1,6 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import FreezeframeWrapper from './FreezeframeWrapper'; // import FreezeframeWrapper from './FreezeframeWrapper';
import Placeholder from './placeholder.png'; import Placeholder from './placeholder.png';
type Props = { type Props = {
@ -13,9 +13,10 @@ class CardMedia extends React.PureComponent<Props> {
render() { render() {
const { thumbnail } = this.props; const { thumbnail } = this.props;
if (thumbnail && thumbnail.endsWith('gif')) { // Disabling temporarily to see if people complain
return <FreezeframeWrapper src={thumbnail} className={className} />; // if (thumbnail && thumbnail.endsWith('gif')) {
} // return <FreezeframeWrapper src={thumbnail} className={className} />;
// }
let url; let url;
// @if TARGET='web' // @if TARGET='web'

View file

@ -54,12 +54,8 @@ const Header = (props: Props) => {
const isAuthPage = history.location.pathname.includes(PAGES.AUTH); const isAuthPage = history.location.pathname.includes(PAGES.AUTH);
// Sign out if they click the "x" when they are on the password prompt // Sign out if they click the "x" when they are on the password prompt
const authHeaderAction = syncError ? { onClick: signOut } : { navigate: `/$/${PAGES.CHANNELS_FOLLOWING}` }; const authHeaderAction = syncError ? { onClick: signOut } : { navigate: '/' };
const homeButtonNavigationProps = isVerifyPage const homeButtonNavigationProps = isVerifyPage ? {} : authHeader ? authHeaderAction : { navigate: '/' };
? {}
: authHeader
? authHeaderAction
: { navigate: `/$/${PAGES.CHANNELS_FOLLOWING}` };
const closeButtonNavigationProps = authHeader ? authHeaderAction : { onClick: () => history.goBack() }; const closeButtonNavigationProps = authHeader ? authHeaderAction : { onClick: () => history.goBack() };
function handleThemeToggle() { function handleThemeToggle() {

View file

@ -8,8 +8,8 @@ import ReportPage from 'page/report';
import ShowPage from 'page/show'; import ShowPage from 'page/show';
import PublishPage from 'page/publish'; import PublishPage from 'page/publish';
import DiscoverPage from 'page/discover'; import DiscoverPage from 'page/discover';
import HomePage from 'page/home';
import InvitedPage from 'page/invited'; import InvitedPage from 'page/invited';
// import HomePage from 'page/home';
import RewardsPage from 'page/rewards'; import RewardsPage from 'page/rewards';
import FileListDownloaded from 'page/fileListDownloaded'; import FileListDownloaded from 'page/fileListDownloaded';
import FileListPublished from 'page/fileListPublished'; import FileListPublished from 'page/fileListPublished';
@ -75,7 +75,7 @@ function AppRouter(props: Props) {
return ( return (
<Switch> <Switch>
<Route path={`/`} exact component={() => <Redirect to={`/$/${PAGES.CHANNELS_FOLLOWING}`} />} /> <Route path={`/`} exact component={HomePage} />
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} /> <Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} /> <Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} /> <Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />

View file

@ -96,6 +96,9 @@ function SideNavigation(props: Props) {
<nav className="navigation"> <nav className="navigation">
<ul className="navigation-links"> <ul className="navigation-links">
{[ {[
{
...buildLink(null, __('Home'), ICONS.HOME),
},
{ {
...buildLink(PAGES.CHANNELS_FOLLOWING, __('Following'), ICONS.SUBSCRIBE), ...buildLink(PAGES.CHANNELS_FOLLOWING, __('Following'), ICONS.SUBSCRIBE),
}, },

View file

@ -81,3 +81,4 @@ export const SIGN_IN = 'SignIn';
export const TRENDING = 'Trending'; export const TRENDING = 'Trending';
export const TOP = 'Top'; export const TOP = 'Top';
export const NEW = 'New'; export const NEW = 'New';
export const SHUFFLE = 'Shuffle';

View file

@ -7,7 +7,7 @@ import DiscoverPage from './view';
const select = state => ({ const select = state => ({
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
subscribedChannels: selectSubscriptions(state), subscribedChannels: selectSubscriptions(state),
email: selectUserVerifiedEmail(state), authenticated: selectUserVerifiedEmail(state),
}); });
const perform = {}; const perform = {};

View file

@ -1,20 +1,190 @@
// @flow // @flow
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import React from 'react'; import React from 'react';
import moment from 'moment';
import Page from 'component/page'; import Page from 'component/page';
// import Button from 'component/button'; import Button from 'component/button';
import ClaimTilesDiscover from 'component/claimTilesDiscover';
import Icon from 'component/common/icon';
import { parseURI } from 'lbry-redux';
import { toCapitalCase } from 'util/string';
// type Props = {}; type Props = {
authenticated: boolean,
followedTags: Array<Tag>,
subscribedChannels: Array<Subscription>,
};
function HomePage() { type RowDataItem = {
// props: Props title: string,
// const {} = props; link?: string,
help?: any,
options?: {},
};
function HomePage(props: Props) {
const { followedTags, subscribedChannels, authenticated } = props;
let rowData: Array<RowDataItem> = [];
if (!authenticated) {
rowData.push(
{
title: 'Trending On LBRY',
link: `/$/${PAGES.DISCOVER}`,
},
{
title: 'Top Channels On LBRY',
options: {
orderBy: ['effective_amount'],
claimType: 'channel',
},
}
);
}
if (authenticated) {
if (subscribedChannels && subscribedChannels.length > 0) {
rowData.push({
title: 'Recent From Following',
link: `/$/${PAGES.CHANNELS_FOLLOWING}`,
options: {
orderBy: ['release_time'],
pageSize: subscribedChannels.length > 3 ? 8 : 4,
channelIds: subscribedChannels.map(subscription => {
const { channelClaimId } = parseURI(subscription.uri);
return channelClaimId;
}),
},
});
}
if (followedTags.length === 0) {
rowData.push({
title: 'Trending On LBRY',
link: `/$/${PAGES.DISCOVER}`,
options: {
pageSize: subscribedChannels.length > 0 ? 4 : 8,
},
});
}
if (followedTags.length > 0 && followedTags.length < 5) {
const followedRows = followedTags.map((tag: Tag) => ({
title: `Trending for #${toCapitalCase(tag.name)}`,
link: `/$/${PAGES.TAGS}?t=${tag.name}`,
options: {
pageSize: 4,
tags: [tag.name],
},
}));
rowData.push(...followedRows);
}
if (followedTags.length > 4) {
rowData.push({
title: 'Trending For Your Tags',
link: `/$/${PAGES.TAGS_FOLLOWING}`,
options: {
tags: followedTags.map(tag => tag.name),
},
});
}
}
// Everyone
rowData.push(
{
title: 'Top Content Last Week',
link: `/$/${PAGES.DISCOVER}?&type=top&time=week`,
options: {
orderBy: ['effective_amount'],
pageSize: 4,
// claimType: 'stream',
releaseTime: `>${Math.floor(
moment()
.subtract(1, 'week')
.unix()
)}`,
},
},
{
title: '#HomePageCageMatch',
link: `/$/${PAGES.TAGS}?t=homepagecagematch&type=top&time=all`,
help: (
<div className="claim-grid__help">
<Icon
icon={ICONS.HELP}
tooltip
customTooltipText={__(
'This is an experiment, and may be removed in the future. Publish something with the #homepagecagematch tag to battle for the top spot on the home page!'
)}
/>
</div>
),
options: {
tags: ['homepagecagematch'],
orderBy: ['effective_amount'],
timestamp: `>${Math.floor(
moment()
.subtract(1, 'week')
.unix()
)}`,
},
}
);
if (!authenticated) {
rowData.push({
title: '#lbry',
link: `/$/${PAGES.TAGS}?t=lbry&type=top&time=all`,
options: {
tags: ['lbry'],
orderBy: ['effective_amount'],
pageSize: 4,
},
});
}
if (authenticated) {
rowData.push({
title: 'Trending On LBRY',
link: `/$/${PAGES.DISCOVER}`,
});
}
rowData.push({
title: 'Latest From @lbry',
link: `/@lbry:3f`,
options: {
orderBy: ['release_time'],
pageSize: 4,
channelIds: ['3fda836a92faaceedfe398225fb9b2ee2ed1f01a'],
},
});
return ( return (
<Page> <Page>
{/* TODO */} {rowData.map(({ title, link, help, options = {} }) => (
{/* Recent From Following */} <div key={title} className="claim-grid__wrapper">
{/* Trending for your tags */} <h1 className="section__actions">
{/* Trending for everyone */} {link ? (
<Button
className="claim-grid__title"
button="link"
navigate={link}
iconRight={ICONS.ARROW_RIGHT}
label={title}
/>
) : (
<span className="claim-grid__title">{title}</span>
)}
{help}
</h1>
<ClaimTilesDiscover {...options} />
</div>
))}
</Page> </Page>
); );
} }

View file

@ -5,6 +5,7 @@
.button--uri-indicator { .button--uri-indicator {
@extend .button--link; @extend .button--link;
color: var(--color-text-subtitle);
max-width: 100%; max-width: 100%;
height: 1.2em; height: 1.2em;
vertical-align: text-top; vertical-align: text-top;

View file

@ -239,3 +239,117 @@
background: var(--color-primary-alt); background: var(--color-primary-alt);
padding: var(--spacing-miniscule); padding: var(--spacing-miniscule);
} }
//
// The _other_ way to view claims
// A grid of tiles
// It should probably go into it's own file once we add more to it
//
.claim-grid {
display: flex;
flex-wrap: wrap;
list-style: none;
align-items: flex-start;
}
.claim-grid__wrapper {
&:not(:first-of-type) {
margin-top: var(--spacing-large);
}
@media (max-width: $breakpoint-small) {
&:not(:first-of-type) {
margin-top: var(--spacing-medium);
}
}
}
.claim-grid__title {
margin-bottom: var(--spacing-medium);
&:not(:first-of-type) {
margin-top: var(--spacing-xlarge);
}
}
.claim-grid__help {
margin-bottom: 12px;
svg {
stroke: var(--color-text-subtitle);
}
}
.claim-preview--tile {
width: calc((100% - var(--spacing-medium) * 3) / 4);
margin-bottom: var(--spacing-large);
margin-right: 0;
margin-top: 0;
margin-left: var(--spacing-medium);
justify-content: flex-start;
&:first-child,
&:nth-child(5n) {
margin-left: 0;
}
&:hover {
cursor: pointer;
}
.media__thumb {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
@media (max-width: $breakpoint-small) {
width: calc((100% - var(--spacing-medium) * 1) / 2);
margin-bottom: var(--spacing-large);
.channel-thumbnail {
display: none;
}
&:first-child,
&:nth-child(2n + 1) {
margin-left: 0;
}
}
}
.claim-tile__title {
margin: var(--spacing-small);
margin-bottom: 0;
font-weight: 600;
font-size: var(--font-small);
color: var(--color-text);
@media (max-width: $breakpoint-small) {
font-size: var(--font-xsmall);
}
}
.claim-tile__info {
display: flex;
margin-top: var(--spacing-small);
padding: var(--spacing-small);
border-top: 1px solid var(--color-border);
color: var(--color-subtitle);
.channel-thumbnail {
width: 2.1rem;
height: 2.1rem;
}
}
.claim-tile__about {
display: flex;
flex-direction: column;
font-size: var(--font-xsmall);
color: var(--color-text-subtitle);
}
.claim-tile__about--channel {
@extend .claim-tile__about;
font-size: var(--font-body);
}

View file

@ -35,7 +35,7 @@
.comment__author { .comment__author {
text-overflow: ellipsis; text-overflow: ellipsis;
padding-right: var(--spacing-small); padding-right: var(--spacing-xsmall);
} }
.comment__time { .comment__time {

View file

@ -107,6 +107,7 @@
@extend .section__actions; @extend .section__actions;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: flex-start;
margin-top: 0; margin-top: 0;
} }

View file

@ -21,6 +21,17 @@
height: 1.5em; height: 1.5em;
} }
&.claim-tile__title {
width: 100%;
height: 2.5rem;
margin-left: 0;
}
&.claim-tile__info {
width: 40%;
height: 2rem;
}
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
&.media__thumb { &.media__thumb {
display: none; display: none;