Livestream: implement Pinned Comments
This commit is contained in:
parent
c2b51127ac
commit
4731786a3f
9 changed files with 116 additions and 2 deletions
1
flow-typed/Comment.js
vendored
1
flow-typed/Comment.js
vendored
|
@ -38,6 +38,7 @@ declare type CommentsState = {
|
|||
topLevelTotalCommentsById: { [string]: number }, // ClaimID -> total top level comments in commentron.
|
||||
commentById: { [string]: Comment },
|
||||
linkedCommentAncestors: { [string]: Array<string> }, // {"linkedCommentId": ["parentId", "grandParentId", ...]}
|
||||
pinnedCommentsById: {}, // ClaimId -> array of pinned comment IDs
|
||||
isLoading: boolean,
|
||||
isLoadingByParentId: { [string]: boolean },
|
||||
myComments: ?Set<string>,
|
||||
|
|
|
@ -21,10 +21,22 @@ type Props = {
|
|||
stakedLevel: number,
|
||||
supportAmount: number,
|
||||
isFiat: boolean,
|
||||
isPinned: boolean,
|
||||
};
|
||||
|
||||
function LivestreamComment(props: Props) {
|
||||
const { claim, uri, authorUri, message, commentIsMine, commentId, stakedLevel, supportAmount, isFiat } = props;
|
||||
const {
|
||||
claim,
|
||||
uri,
|
||||
authorUri,
|
||||
message,
|
||||
commentIsMine,
|
||||
commentId,
|
||||
stakedLevel,
|
||||
supportAmount,
|
||||
isFiat,
|
||||
isPinned,
|
||||
} = props;
|
||||
const [mouseIsHovering, setMouseHover] = React.useState(false);
|
||||
const commentByOwnerOfContent = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri;
|
||||
const { claimName } = parseURI(authorUri);
|
||||
|
@ -57,6 +69,13 @@ function LivestreamComment(props: Props) {
|
|||
{claimName}
|
||||
</Button>
|
||||
|
||||
{isPinned && (
|
||||
<span className="comment__pin">
|
||||
<Icon icon={ICONS.PIN} size={14} />
|
||||
{__('Pinned')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="livestream-comment__text">
|
||||
<MarkdownPreview content={message} promptLinks stakedLevel={stakedLevel} />
|
||||
</div>
|
||||
|
@ -78,6 +97,8 @@ function LivestreamComment(props: Props) {
|
|||
authorUri={authorUri}
|
||||
commentIsMine={commentIsMine}
|
||||
disableEdit
|
||||
isTopLevel
|
||||
isPinned={isPinned}
|
||||
disableRemove={supportAmount > 0}
|
||||
/>
|
||||
</Menu>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { makeSelectClaimForUri, selectMyChannelClaims } from 'lbry-redux';
|
|||
import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket';
|
||||
import { doCommentList, doSuperChatList } from 'redux/actions/comments';
|
||||
import {
|
||||
selectPinnedCommentsById,
|
||||
makeSelectTopLevelCommentsForUri,
|
||||
selectIsFetchingComments,
|
||||
makeSelectSuperChatsForUri,
|
||||
|
@ -17,6 +18,7 @@ const select = (state, props) => ({
|
|||
superChats: makeSelectSuperChatsForUri(props.uri)(state),
|
||||
superChatsTotalAmount: makeSelectSuperChatTotalAmountForUri(props.uri)(state),
|
||||
myChannels: selectMyChannelClaims(state),
|
||||
pinnedCommentsById: selectPinnedCommentsById(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
|
|
|
@ -23,6 +23,7 @@ type Props = {
|
|||
doSuperChatList: (string) => void,
|
||||
superChats: Array<Comment>,
|
||||
myChannels: ?Array<ChannelClaim>,
|
||||
pinnedCommentsById: { [claimId: string]: Array<string> },
|
||||
};
|
||||
|
||||
const VIEW_MODE_CHAT = 'view_chat';
|
||||
|
@ -43,6 +44,7 @@ export default function LivestreamComments(props: Props) {
|
|||
doSuperChatList,
|
||||
myChannels,
|
||||
superChats: superChatsByTipAmount,
|
||||
pinnedCommentsById,
|
||||
} = props;
|
||||
|
||||
let superChatsFiatAmount, superChatsTotalAmount;
|
||||
|
@ -58,6 +60,12 @@ export default function LivestreamComments(props: Props) {
|
|||
const discussionElement = document.querySelector('.livestream__comments');
|
||||
const commentElement = document.querySelector('.livestream-comment');
|
||||
|
||||
let pinnedComment;
|
||||
const pinnedCommentIds = (claimId && pinnedCommentsById[claimId]) || [];
|
||||
if (pinnedCommentIds.length > 0) {
|
||||
pinnedComment = commentsByChronologicalOrder.find((c) => c.comment_id === pinnedCommentIds[0]);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (claimId) {
|
||||
doCommentList(uri, '', 1, 75);
|
||||
|
@ -234,6 +242,22 @@ export default function LivestreamComments(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{pinnedComment && (
|
||||
<div className="livestream-pinned__wrapper">
|
||||
<LivestreamComment
|
||||
key={pinnedComment.comment_id}
|
||||
uri={uri}
|
||||
authorUri={pinnedComment.channel_url}
|
||||
commentId={pinnedComment.comment_id}
|
||||
message={pinnedComment.comment}
|
||||
supportAmount={pinnedComment.support_amount}
|
||||
isFiat={pinnedComment.is_fiat}
|
||||
isPinned={pinnedComment.is_pinned}
|
||||
commentIsMine={pinnedComment.channel_id && isMyComment(pinnedComment.channel_id)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* top to bottom comment display */}
|
||||
{!fetchingComments && commentsByChronologicalOrder.length > 0 ? (
|
||||
<div className="livestream__comments">
|
||||
|
|
|
@ -110,6 +110,17 @@ export const doCommentSocketConnect = (uri, claimId) => (dispatch) => {
|
|||
data: { connected, claimId },
|
||||
});
|
||||
}
|
||||
if (response.type === 'pinned') {
|
||||
const pinnedComment = response.data.comment;
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_PIN_COMPLETED,
|
||||
data: {
|
||||
pinnedComment: pinnedComment,
|
||||
claimId,
|
||||
unpin: !pinnedComment.is_pinned,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ const defaultState: CommentsState = {
|
|||
commentsByUri: {}, // URI -> claimId
|
||||
linkedCommentAncestors: {}, // {"linkedCommentId": ["parentId", "grandParentId", ...]}
|
||||
superChatsByUri: {},
|
||||
pinnedCommentsById: {}, // ClaimId -> array of pinned comment IDs
|
||||
isLoading: false,
|
||||
isLoadingByParentId: {},
|
||||
isCommenting: false,
|
||||
|
@ -285,6 +286,7 @@ export default handleActions(
|
|||
const commentsByUri = Object.assign({}, state.commentsByUri);
|
||||
const repliesByParentId = Object.assign({}, state.repliesByParentId);
|
||||
const totalCommentsById = Object.assign({}, state.totalCommentsById);
|
||||
const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById);
|
||||
const totalRepliesByParentId = Object.assign({}, state.totalRepliesByParentId);
|
||||
const isLoadingByParentId = Object.assign({}, state.isLoadingByParentId);
|
||||
|
||||
|
@ -315,6 +317,9 @@ export default handleActions(
|
|||
const comment = comments[i];
|
||||
commonUpdateAction(comment, commentById, commentIds, i);
|
||||
pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id);
|
||||
if (comment.is_pinned) {
|
||||
pushToArrayInObject(pinnedCommentsById, claimId, comment.comment_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- Replies ---
|
||||
|
@ -337,6 +342,7 @@ export default handleActions(
|
|||
topLevelTotalPagesById,
|
||||
repliesByParentId,
|
||||
totalCommentsById,
|
||||
pinnedCommentsById,
|
||||
totalRepliesByParentId,
|
||||
byId,
|
||||
commentById,
|
||||
|
@ -621,12 +627,20 @@ export default handleActions(
|
|||
const { pinnedComment, claimId, unpin } = action.data;
|
||||
const commentById = Object.assign({}, state.commentById);
|
||||
const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById);
|
||||
const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById);
|
||||
|
||||
if (pinnedComment && topLevelCommentsById[claimId]) {
|
||||
const index = topLevelCommentsById[claimId].indexOf(pinnedComment.comment_id);
|
||||
if (index > -1) {
|
||||
topLevelCommentsById[claimId].splice(index, 1);
|
||||
|
||||
if (pinnedCommentsById[claimId]) {
|
||||
// Remove here so that the 'unshift' below will be a unique entry.
|
||||
pinnedCommentsById[claimId] = pinnedCommentsById[claimId].filter((x) => x !== pinnedComment.comment_id);
|
||||
} else {
|
||||
pinnedCommentsById[claimId] = [];
|
||||
}
|
||||
|
||||
if (unpin) {
|
||||
// Without the sort score, I have no idea where to put it. Just
|
||||
// dump it at the bottom. Users can refresh if they want it back to
|
||||
|
@ -634,16 +648,34 @@ export default handleActions(
|
|||
topLevelCommentsById[claimId].push(pinnedComment.comment_id);
|
||||
} else {
|
||||
topLevelCommentsById[claimId].unshift(pinnedComment.comment_id);
|
||||
pinnedCommentsById[claimId].unshift(pinnedComment.comment_id);
|
||||
}
|
||||
|
||||
if (commentById[pinnedComment.comment_id]) {
|
||||
// Commentron's `comment.Pin` response places the creator's credentials
|
||||
// in the 'channel_*' fields, which doesn't make sense. Maybe it is to
|
||||
// show who signed/pinned it, but even if so, it shouldn't overload
|
||||
// these variables which are already used by existing comment data structure.
|
||||
// Ensure we don't override the existing/correct values, but fallback
|
||||
// to whatever was given.
|
||||
const { channel_id, channel_name, channel_url } = commentById[pinnedComment.comment_id];
|
||||
commentById[pinnedComment.comment_id] = {
|
||||
...pinnedComment,
|
||||
channel_id: channel_id || pinnedComment.channel_id,
|
||||
channel_name: channel_name || pinnedComment.channel_name,
|
||||
channel_url: channel_url || pinnedComment.channel_url,
|
||||
};
|
||||
} else {
|
||||
commentById[pinnedComment.comment_id] = pinnedComment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
commentById,
|
||||
topLevelCommentsById,
|
||||
pinnedCommentsById,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ export const selectCommentsDisabledChannelIds = createSelector(
|
|||
(state) => state.commentsDisabledChannelIds
|
||||
);
|
||||
export const selectOthersReactsById = createSelector(selectState, (state) => state.othersReactsByCommentId);
|
||||
export const selectPinnedCommentsById = createSelector(selectState, (state) => state.pinnedCommentsById);
|
||||
|
||||
export const selectModerationBlockList = createSelector(selectState, (state) =>
|
||||
state.moderationBlockList ? state.moderationBlockList.reverse() : []
|
||||
|
|
|
@ -217,6 +217,7 @@ $thumbnailWidthSmall: 1rem;
|
|||
|
||||
.comment__pin {
|
||||
margin-left: var(--spacing-s);
|
||||
font-size: var(--font-xsmall);
|
||||
|
||||
.icon {
|
||||
padding-top: 1px;
|
||||
|
|
|
@ -223,6 +223,27 @@ $discussion-header__height: 3rem;
|
|||
}
|
||||
}
|
||||
|
||||
.livestream-pinned__wrapper {
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
padding: var(--spacing-s) var(--spacing-xs);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
font-size: var(--font-small);
|
||||
background-color: var(--color-card-background-highlighted);
|
||||
width: 100%;
|
||||
|
||||
.livestream-comment {
|
||||
padding-top: var(--spacing-xs);
|
||||
max-height: 6rem;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
padding: var(--spacing-xs);
|
||||
width: var(--livestream-comments-width);
|
||||
}
|
||||
}
|
||||
|
||||
.livestream-superchat__amount-large {
|
||||
.credit-amount {
|
||||
display: flex;
|
||||
|
|
Loading…
Reference in a new issue