use comment component for livestream comments

This commit is contained in:
Sean Yesmunt 2021-03-23 22:53:33 -04:00 committed by jessopb
parent d8a5ca082b
commit 64e8c8e095
7 changed files with 100 additions and 76 deletions

View file

@ -50,6 +50,7 @@ type Props = {
activeChannelClaim: ?ChannelClaim,
playingUri: ?PlayingUri,
stakedLevel: number,
livestream?: boolean,
};
const LENGTH_TO_COLLAPSE = 300;
@ -77,6 +78,7 @@ function Comment(props: Props) {
othersReacts,
playingUri,
stakedLevel,
livestream,
} = props;
const {
push,
@ -162,6 +164,7 @@ function Comment(props: Props) {
className={classnames('comment', {
'comment--top-level': isTopLevel,
'comment--reply': !isTopLevel,
'comment--livestream': livestream,
})}
id={commentId}
onMouseOver={() => setMouseHover(true)}
@ -173,13 +176,20 @@ function Comment(props: Props) {
'comment--slimed': slimedToDeath && !displayDeadComment,
})}
>
<div className="comment__thumbnail-wrapper">
{authorUri ? (
<ChannelThumbnail uri={authorUri} obscure={channelIsBlocked} small className="comment__author-thumbnail" />
) : (
<ChannelThumbnail small className="comment__author-thumbnail" />
)}
</div>
{!livestream && (
<div className="comment__thumbnail-wrapper">
{authorUri ? (
<ChannelThumbnail
uri={authorUri}
obscure={channelIsBlocked}
small
className="comment__author-thumbnail"
/>
) : (
<ChannelThumbnail small className="comment__author-thumbnail" />
)}
</div>
)}
<div className="comment__body_container">
<div className="comment__meta">
@ -187,13 +197,15 @@ function Comment(props: Props) {
{!author ? (
<span className="comment__author">{__('Anonymous')}</span>
) : (
<UriIndicator link uri={authorUri} />
<UriIndicator link external={livestream} uri={authorUri} />
)}
{!livestream && (
<Button
className="comment__time"
onClick={handleTimeClick}
label={<DateTime date={timePosted} timeAgo />}
/>
)}
<Button
className="comment__time"
onClick={handleTimeClick}
label={<DateTime date={timePosted} timeAgo />}
/>
{isPinned && (
<span className="comment__pin">
@ -273,18 +285,20 @@ function Comment(props: Props) {
)}
</div>
<div className="comment__actions">
{threadDepth !== 0 && (
<Button
requiresAuth={IS_WEB}
label={commentingEnabled ? __('Reply') : __('Log in to reply')}
className="comment__action"
onClick={handleCommentReply}
icon={ICONS.REPLY}
/>
)}
{ENABLE_COMMENT_REACTIONS && <CommentReactions uri={uri} commentId={commentId} />}
</div>
{!livestream && (
<div className="comment__actions">
{threadDepth !== 0 && (
<Button
requiresAuth={IS_WEB}
label={commentingEnabled ? __('Reply') : __('Log in to reply')}
className="comment__action"
onClick={handleCommentReply}
icon={ICONS.REPLY}
/>
)}
{ENABLE_COMMENT_REACTIONS && <CommentReactions uri={uri} commentId={commentId} />}
</div>
)}
{isReplying && (
<CommentCreate

View file

@ -4,8 +4,7 @@ import classnames from 'classnames';
import Card from 'component/common/card';
import Spinner from 'component/spinner';
import CommentCreate from 'component/commentCreate';
import Button from 'component/button';
import MarkdownPreview from 'component/common/markdown-preview';
import CommentView from 'component/comment';
type Props = {
uri: string,
@ -114,20 +113,17 @@ export default function LivestreamFeed(props: Props) {
<div className="livestream__comments">
{comments.map((comment) => (
<div key={comment.comment_id} className={classnames('livestream__comment')}>
{comment.channel_url ? (
<Button
target="_blank"
className={classnames('livestream__comment-author', {
'livestream__comment-author--streamer':
claim.signing_channel && claim.signing_channel.claim_id === comment.channel_id,
})}
navigate={comment.channel_url}
label={comment.channel_name}
/>
) : (
<div className="livestream__comment-author">{comment.channel_name}</div>
)}
<MarkdownPreview content={comment.comment} simpleLinks />
<CommentView
livestream
isTopLevel
uri={uri}
authorUri={comment.channel_url}
author={comment.channel_name}
claimId={comment.claim_id}
commentId={comment.comment_id}
message={comment.comment}
timePosted={comment.timestamp * 1000}
/>
</div>
))}
</div>

View file

@ -17,6 +17,7 @@ type Props = {
// to allow for other elements to be nested within the UriIndicator
children: ?Node,
inline: boolean,
external?: boolean,
};
class UriIndicator extends React.PureComponent<Props> {
@ -37,7 +38,7 @@ class UriIndicator extends React.PureComponent<Props> {
};
render() {
const { link, isResolvingUri, claim, children, inline, hideAnonymous = false } = this.props;
const { link, isResolvingUri, claim, children, inline, hideAnonymous = false, external = false } = this.props;
if (!claim) {
return <span className="empty">{isResolvingUri ? 'Validating...' : 'Unused'}</span>;
@ -74,10 +75,14 @@ class UriIndicator extends React.PureComponent<Props> {
}
if (children) {
return <Button navigate={channelLink}>{children}</Button>;
return (
<Button target={external ? '_blank' : undefined} navigate={channelLink}>
{children}
</Button>
);
} else {
return (
<Button className="button--uri-indicator" navigate={channelLink}>
<Button className="button--uri-indicator" navigate={channelLink} target={external ? '_blank' : undefined}>
{inner}
</Button>
);

View file

@ -13,13 +13,13 @@ const NO_WALLET_ERROR = 'no wallet found for this user';
const BAD_PASSWORD_ERROR_NAME = 'InvalidPasswordError';
export function doSetDefaultAccount(success, failure) {
return dispatch => {
return (dispatch) => {
dispatch({
type: ACTIONS.SET_DEFAULT_ACCOUNT,
});
Lbry.account_list()
.then(accountList => {
.then((accountList) => {
const { lbc_mainnet: accounts } = accountList;
let defaultId;
for (let i = 0; i < accounts.length; ++i) {
@ -43,7 +43,7 @@ export function doSetDefaultAccount(success, failure) {
success();
}
})
.catch(err => {
.catch((err) => {
if (failure) {
failure(err);
}
@ -53,7 +53,7 @@ export function doSetDefaultAccount(success, failure) {
failure('Could not set a default account'); // fail
}
})
.catch(err => {
.catch((err) => {
if (failure) {
failure(err);
}
@ -62,13 +62,13 @@ export function doSetDefaultAccount(success, failure) {
}
export function doSetSync(oldHash, newHash, data) {
return dispatch => {
return (dispatch) => {
dispatch({
type: ACTIONS.SET_SYNC_STARTED,
});
return Lbryio.call('sync', 'set', { old_hash: oldHash, new_hash: newHash, data }, 'post')
.then(response => {
.then((response) => {
if (!response.hash) {
throw Error('No hash returned for sync/set.');
}
@ -78,7 +78,7 @@ export function doSetSync(oldHash, newHash, data) {
data: { syncHash: response.hash },
});
})
.catch(error => {
.catch((error) => {
dispatch({
type: ACTIONS.SET_SYNC_FAILED,
data: { error },
@ -94,7 +94,7 @@ export const doGetSyncDesktop = (cb?, password) => (dispatch, getState) => {
const setSyncPending = selectSetSyncIsPending(state);
const syncLocked = selectSyncIsLocked(state);
return getSavedPassword().then(savedPassword => {
return getSavedPassword().then((savedPassword) => {
const passwordArgument = password || password === '' ? password : savedPassword === null ? '' : savedPassword;
if (syncEnabled && !getSyncPending && !setSyncPending && !syncLocked) {
@ -128,7 +128,7 @@ export function doSyncLoop(noInterval) {
}
export function doSyncUnsubscribe() {
return dispatch => {
return (dispatch) => {
if (syncTimer) {
clearInterval(syncTimer);
}
@ -148,7 +148,7 @@ export function doGetSync(passedPassword, callback) {
}
}
return dispatch => {
return (dispatch) => {
dispatch({
type: ACTIONS.GET_SYNC_STARTED,
});
@ -156,7 +156,7 @@ export function doGetSync(passedPassword, callback) {
const data = {};
Lbry.wallet_status()
.then(status => {
.then((status) => {
if (status.is_locked) {
return Lbry.wallet_unlock({ password });
}
@ -164,15 +164,15 @@ export function doGetSync(passedPassword, callback) {
// Wallet is already unlocked
return true;
})
.then(isUnlocked => {
.then((isUnlocked) => {
if (isUnlocked) {
return Lbry.sync_hash();
}
data.unlockFailed = true;
throw new Error();
})
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post'))
.then(response => {
.then((hash) => Lbryio.call('sync', 'get', { hash }, 'post'))
.then((response) => {
const syncHash = response.hash;
data.syncHash = syncHash;
data.syncData = response.data;
@ -183,7 +183,7 @@ export function doGetSync(passedPassword, callback) {
return Lbry.sync_apply({ password, data: response.data, blocking: true });
}
})
.then(response => {
.then((response) => {
if (!response) {
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
handleCallback(null, data.changed);
@ -200,7 +200,7 @@ export function doGetSync(passedPassword, callback) {
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
handleCallback(null, data.changed);
})
.catch(syncAttemptError => {
.catch((syncAttemptError) => {
const badPasswordError =
syncAttemptError && syncAttemptError.data && syncAttemptError.data.name === BAD_PASSWORD_ERROR_NAME;
@ -248,7 +248,7 @@ export function doGetSync(passedPassword, callback) {
dispatch(doSetSync('', walletHash, syncApplyData, password));
handleCallback();
})
.catch(syncApplyError => {
.catch((syncApplyError) => {
handleCallback(syncApplyError);
});
}
@ -258,7 +258,7 @@ export function doGetSync(passedPassword, callback) {
}
export function doSyncApply(syncHash, syncData, password) {
return dispatch => {
return (dispatch) => {
dispatch({
type: ACTIONS.SYNC_APPLY_STARTED,
});
@ -286,14 +286,14 @@ export function doSyncApply(syncHash, syncData, password) {
}
export function doCheckSync() {
return dispatch => {
return (dispatch) => {
dispatch({
type: ACTIONS.GET_SYNC_STARTED,
});
Lbry.sync_hash().then(hash => {
Lbry.sync_hash().then((hash) => {
Lbryio.call('sync', 'get', { hash }, 'post')
.then(response => {
.then((response) => {
const data = {
hasSyncedWallet: true,
syncHash: response.hash,
@ -314,19 +314,19 @@ export function doCheckSync() {
}
export function doResetSync() {
return dispatch =>
new Promise(resolve => {
return (dispatch) =>
new Promise((resolve) => {
dispatch({ type: ACTIONS.SYNC_RESET });
resolve();
});
}
export function doSyncEncryptAndDecrypt(oldPassword, newPassword, encrypt) {
return dispatch => {
return (dispatch) => {
const data = {};
return Lbry.sync_hash()
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post'))
.then(syncGetResponse => {
.then((hash) => Lbryio.call('sync', 'get', { hash }, 'post'))
.then((syncGetResponse) => {
data.oldHash = syncGetResponse.hash;
return Lbry.sync_apply({ password: oldPassword, data: syncGetResponse.data });
@ -339,7 +339,7 @@ export function doSyncEncryptAndDecrypt(oldPassword, newPassword, encrypt) {
}
})
.then(() => Lbry.sync_apply({ password: newPassword }))
.then(syncApplyResponse => {
.then((syncApplyResponse) => {
if (syncApplyResponse.hash !== data.oldHash) {
return dispatch(doSetSync(data.oldHash, syncApplyResponse.hash, syncApplyResponse.data));
}

View file

@ -105,12 +105,20 @@ export const makeSelectCommentIdsForUri = (uri: string) =>
export const makeSelectMyReactionsForComment = (commentId: string) =>
createSelector(selectState, (state) => {
if (!state.myReactsByCommentId) {
return [];
}
return state.myReactsByCommentId[commentId] || [];
});
export const makeSelectOthersReactionsForComment = (commentId: string) =>
createSelector(selectState, (state) => {
return state.othersReactsByCommentId[commentId];
if (!state.othersReactsByCommentId) {
return {};
}
return state.othersReactsByCommentId[commentId] || {};
});
export const selectPendingCommentReacts = createSelector(selectState, (state) => state.pendingCommentReactions);

View file

@ -34,6 +34,7 @@ $thumbnailWidthSmall: 1rem;
}
.comment {
width: 100%;
display: flex;
flex-direction: column;
font-size: var(--font-small);
@ -101,6 +102,10 @@ $thumbnailWidthSmall: 1rem;
}
}
.comment--livestream {
margin-right: 0;
}
.comment--slimed {
opacity: 0.6;
}

View file

@ -48,10 +48,6 @@
margin-top: var(--spacing-s);
display: flex;
flex-wrap: wrap;
> :first-child {
margin-right: var(--spacing-s);
}
}
.livestream__comment-author {