add initial support for comment API outside of SDK
This commit is contained in:
parent
db87125dc8
commit
1f117e43bd
22 changed files with 421 additions and 118 deletions
|
@ -9,6 +9,7 @@ WEB_SERVER_PORT=1337
|
|||
LBRY_WEB_API=https://api.lbry.tv
|
||||
LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
|
||||
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
|
||||
COMMENT_SERVER_API=https://comments.lbry.com/api/v2
|
||||
WELCOME_VERSION=1.0
|
||||
|
||||
# Custom Site info
|
||||
|
|
|
@ -33,6 +33,7 @@ module.name_mapper='^analytics\(.*\)$' -> '<PROJECT_ROOT>/ui/analytics\1'
|
|||
module.name_mapper='^rewards\(.*\)$' -> '<PROJECT_ROOT>/ui/rewards\1'
|
||||
module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/ui/i18n\1'
|
||||
module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/ui/effects\1'
|
||||
module.name_mapper='^comments\(.*\)$' -> '<PROJECT_ROOT>/ui/comments\1'
|
||||
module.name_mapper='^config\(.*\)$' -> '<PROJECT_ROOT>/config\1'
|
||||
module.name_mapper='^web\/component\(.*\)$' -> '<PROJECT_ROOT>/web/component\1'
|
||||
module.name_mapper='^web\/effects\(.*\)$' -> '<PROJECT_ROOT>/web/effects\1'
|
||||
|
|
|
@ -12,6 +12,7 @@ const config = {
|
|||
LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com',
|
||||
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //cdn.lbryplayer.xyz',
|
||||
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
|
||||
COMMENT_SERVER_API: process.env.COMMENT_SERVER_API,
|
||||
WELCOME_VERSION: process.env.WELCOME_VERSION,
|
||||
DOMAIN: process.env.DOMAIN,
|
||||
SHARE_DOMAIN_URL: process.env.SHARE_DOMAIN_URL,
|
||||
|
|
16
flow-typed/comments.js
vendored
Normal file
16
flow-typed/comments.js
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
// @flow
|
||||
declare type CommentListParams = {
|
||||
page: number,
|
||||
page_size: number,
|
||||
claim_id: string,
|
||||
};
|
||||
|
||||
declare type CommentAbandonParams = {
|
||||
comment_id: string,
|
||||
creator_channel_id?: string,
|
||||
creator_channel_name?: string,
|
||||
channel_id?: string,
|
||||
hexdata?: string,
|
||||
};
|
||||
|
||||
declare type ModerationBlockParams = {};
|
|
@ -142,7 +142,7 @@
|
|||
"imagesloaded": "^4.1.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#f0849b4ce19e5e9600b74d61c6db82f0b853b9e8",
|
||||
"lbry-redux": "lbryio/lbry-redux#d90cbd18925788b01fa9c4055c81300cb39e0b3a",
|
||||
"lbryinc": "lbryio/lbryinc#eee2cb730ecec95a1344a755035755b0d4dad5cf",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
|
|
37
ui/comments.js
Normal file
37
ui/comments.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
import { COMMENT_SERVER_API } from 'config';
|
||||
|
||||
const Comments = {
|
||||
url: COMMENT_SERVER_API,
|
||||
enabled: Boolean(COMMENT_SERVER_API),
|
||||
|
||||
moderation_block: (params: ModerationBlockParams) => fetchCommentsApi('moderation.Block', params),
|
||||
comment_list: (params: CommentListParams) => fetchCommentsApi('comment.List', params),
|
||||
comment_abandon: (params: CommentAbandonParams) => fetchCommentsApi('comment.Abandon', params),
|
||||
};
|
||||
|
||||
function fetchCommentsApi(method: string, params: {}) {
|
||||
if (!Comments.enabled) {
|
||||
return Promise.reject('Comments are not currently enabled'); // eslint-disable-line
|
||||
}
|
||||
|
||||
const url = `${Comments.url}?m=${method}`;
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method,
|
||||
params,
|
||||
}),
|
||||
};
|
||||
|
||||
return fetch(url, options)
|
||||
.then(res => res.json())
|
||||
.then(res => res.result);
|
||||
}
|
||||
|
||||
export default Comments;
|
|
@ -16,7 +16,7 @@ import {
|
|||
selectIsUpgradeAvailable,
|
||||
selectAutoUpdateDownloaded,
|
||||
selectModal,
|
||||
selectActiveChannelId,
|
||||
selectActiveChannelClaim,
|
||||
} from 'redux/selectors/app';
|
||||
import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings';
|
||||
import { doSyncLoop } from 'redux/actions/sync';
|
||||
|
@ -44,7 +44,7 @@ const select = state => ({
|
|||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
currentModal: selectModal(state),
|
||||
syncFatalError: selectSyncFatalError(state),
|
||||
activeChannelId: selectActiveChannelId(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
myChannelUrls: selectMyChannelUrls(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -80,9 +80,9 @@ type Props = {
|
|||
syncEnabled: boolean,
|
||||
currentModal: any,
|
||||
syncFatalError: boolean,
|
||||
activeChannelId: ?string,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
myChannelUrls: ?Array<string>,
|
||||
setActiveChannelIfNotSet: (?string) => void,
|
||||
setActiveChannelIfNotSet: () => void,
|
||||
setIncognito: boolean => void,
|
||||
};
|
||||
|
||||
|
@ -110,8 +110,8 @@ function App(props: Props) {
|
|||
syncLoop,
|
||||
currentModal,
|
||||
syncFatalError,
|
||||
activeChannelId,
|
||||
myChannelUrls,
|
||||
activeChannelClaim,
|
||||
setActiveChannelIfNotSet,
|
||||
setIncognito,
|
||||
} = props;
|
||||
|
@ -144,6 +144,7 @@ function App(props: Props) {
|
|||
const hasMyChannels = myChannelUrls && myChannelUrls.length > 0;
|
||||
const hasNoChannels = myChannelUrls && myChannelUrls.length === 0;
|
||||
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
|
||||
const hasActiveChannelClaim = activeChannelClaim !== undefined;
|
||||
|
||||
let uri;
|
||||
try {
|
||||
|
@ -238,12 +239,12 @@ function App(props: Props) {
|
|||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMyChannels && !activeChannelId) {
|
||||
if (hasMyChannels && !hasActiveChannelClaim) {
|
||||
setActiveChannelIfNotSet();
|
||||
} else if (hasNoChannels) {
|
||||
setIncognito(true);
|
||||
}
|
||||
}, [hasMyChannels, activeChannelId, setActiveChannelIfNotSet]);
|
||||
}, [hasMyChannels, hasNoChannels, hasActiveChannelClaim, setActiveChannelIfNotSet, setIncognito]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!languages.includes(language)) {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectThumbnailForUri, selectMyChannelClaims, makeSelectChannelPermUrlForClaimUri } from 'lbry-redux';
|
||||
import { doCommentAbandon, doCommentUpdate, doCommentPin, doCommentList } from 'redux/actions/comments';
|
||||
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
||||
import { makeSelectThumbnailForUri } from 'lbry-redux';
|
||||
import { doCommentUpdate } from 'redux/actions/comments';
|
||||
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectIsFetchingComments, makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
|
||||
import { makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import Comment from './view';
|
||||
|
||||
|
@ -14,21 +13,14 @@ const select = (state, props) => ({
|
|||
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
||||
channelIsBlocked: props.authorUri && selectChannelIsBlocked(props.authorUri)(state),
|
||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||
isFetchingComments: selectIsFetchingComments(state),
|
||||
myChannels: selectMyChannelClaims(state),
|
||||
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
||||
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||
updateComment: (commentId, comment) => dispatch(doCommentUpdate(commentId, comment)),
|
||||
deleteComment: commentId => dispatch(doCommentAbandon(commentId)),
|
||||
blockChannel: channelUri => dispatch(doToggleBlockChannel(channelUri)),
|
||||
doToast: options => dispatch(doToast(options)),
|
||||
pinComment: (commentId, remove) => dispatch(doCommentPin(commentId, remove)),
|
||||
fetchComments: uri => dispatch(doCommentList(uri)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(Comment);
|
||||
|
|
|
@ -10,7 +10,7 @@ import Button from 'component/button';
|
|||
import Expandable from 'component/expandable';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||
import { Menu, MenuButton } from '@reach/menu-button';
|
||||
import Icon from 'component/common/icon';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import classnames from 'classnames';
|
||||
|
@ -19,6 +19,7 @@ import CommentReactions from 'component/commentReactions';
|
|||
import CommentsReplies from 'component/commentsReplies';
|
||||
import { useHistory } from 'react-router';
|
||||
import CommentCreate from 'component/commentCreate';
|
||||
import CommentMenuList from 'component/commentMenuList';
|
||||
|
||||
type Props = {
|
||||
closeInlinePlayer: () => void,
|
||||
|
@ -32,8 +33,7 @@ type Props = {
|
|||
claimIsMine: boolean, // if you control the claim which this comment was posted on
|
||||
commentIsMine: boolean, // if this comment was signed by an owned channel
|
||||
updateComment: (string, string) => void,
|
||||
deleteComment: string => void,
|
||||
blockChannel: string => void,
|
||||
commentModBlock: string => void,
|
||||
linkedComment?: any,
|
||||
myChannels: ?Array<ChannelClaim>,
|
||||
commentingEnabled: boolean,
|
||||
|
@ -45,10 +45,7 @@ type Props = {
|
|||
like: number,
|
||||
dislike: number,
|
||||
},
|
||||
pinComment: (string, boolean) => Promise<any>,
|
||||
fetchComments: string => void,
|
||||
commentIdentityChannel: any,
|
||||
contentChannelPermanentUrl: any,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
};
|
||||
|
||||
|
@ -67,8 +64,6 @@ function Comment(props: Props) {
|
|||
commentIsMine,
|
||||
commentId,
|
||||
updateComment,
|
||||
deleteComment,
|
||||
blockChannel,
|
||||
linkedComment,
|
||||
commentingEnabled,
|
||||
myChannels,
|
||||
|
@ -76,11 +71,7 @@ function Comment(props: Props) {
|
|||
isTopLevel,
|
||||
threadDepth,
|
||||
isPinned,
|
||||
pinComment,
|
||||
fetchComments,
|
||||
othersReacts,
|
||||
contentChannelPermanentUrl,
|
||||
activeChannelClaim,
|
||||
} = props;
|
||||
const {
|
||||
push,
|
||||
|
@ -133,20 +124,11 @@ function Comment(props: Props) {
|
|||
setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value);
|
||||
}
|
||||
|
||||
function handlePinComment(commentId, remove) {
|
||||
pinComment(commentId, remove).then(() => fetchComments(uri));
|
||||
}
|
||||
|
||||
function handleEditComment() {
|
||||
closeInlinePlayer();
|
||||
setEditing(true);
|
||||
}
|
||||
|
||||
function handleDeleteComment() {
|
||||
closeInlinePlayer();
|
||||
deleteComment(commentId);
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
updateComment(commentId, editedMessage);
|
||||
setEditing(false);
|
||||
|
@ -228,38 +210,15 @@ function Comment(props: Props) {
|
|||
icon={ICONS.MORE_VERTICAL}
|
||||
/>
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--comments">
|
||||
{commentIsMine ? (
|
||||
<>
|
||||
<MenuItem className="comment__menu-option menu__link" onSelect={handleEditComment}>
|
||||
<Icon aria-hidden icon={ICONS.EDIT} />
|
||||
{__('Edit')}
|
||||
</MenuItem>
|
||||
<MenuItem className="comment__menu-option menu__link" onSelect={handleDeleteComment}>
|
||||
<Icon aria-hidden icon={ICONS.DELETE} />
|
||||
{__('Delete')}
|
||||
</MenuItem>
|
||||
</>
|
||||
) : (
|
||||
<MenuItem className="comment__menu-option menu__link" onSelect={() => blockChannel(authorUri)}>
|
||||
<Icon aria-hidden icon={ICONS.NO} />
|
||||
{__('Block Channel')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl && isTopLevel && (
|
||||
<MenuItem
|
||||
className="comment__menu-option menu__link"
|
||||
onSelect={
|
||||
isPinned ? () => handlePinComment(commentId, true) : () => handlePinComment(commentId, false)
|
||||
}
|
||||
>
|
||||
<span className={'button__content'}>
|
||||
<Icon aria-hidden icon={ICONS.PIN} className={'icon'} />
|
||||
{isPinned ? __('Unpin') : __('Pin')}
|
||||
</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
<CommentMenuList
|
||||
uri={uri}
|
||||
isTopLevel={isTopLevel}
|
||||
isPinned={isPinned}
|
||||
commentId={commentId}
|
||||
authorUri={authorUri}
|
||||
commentIsMine={commentIsMine}
|
||||
handleEditComment={handleEditComment}
|
||||
/>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
|
|
32
ui/component/commentMenuList/index.js
Normal file
32
ui/component/commentMenuList/index.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectChannelPermUrlForClaimUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import {
|
||||
doCommentAbandon,
|
||||
doCommentPin,
|
||||
doCommentList,
|
||||
// doCommentModBlock,
|
||||
} from 'redux/actions/comments';
|
||||
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
||||
// import { doSetActiveChannel } from 'redux/actions/app';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import CommentMenuList from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||
deleteComment: (commentId, creatorChannelUrl) => dispatch(doCommentAbandon(commentId, creatorChannelUrl)),
|
||||
blockChannel: channelUri => dispatch(doToggleBlockChannel(channelUri)),
|
||||
pinComment: (commentId, remove) => dispatch(doCommentPin(commentId, remove)),
|
||||
fetchComments: uri => dispatch(doCommentList(uri)),
|
||||
// setActiveChannel: channelId => dispatch(doSetActiveChannel(channelId)),
|
||||
// commentModBlock: commentAuthor => dispatch(doCommentModBlock(commentAuthor)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(CommentMenuList);
|
149
ui/component/commentMenuList/view.jsx
Normal file
149
ui/component/commentMenuList/view.jsx
Normal file
|
@ -0,0 +1,149 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import { MenuList, MenuItem } from '@reach/menu-button';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import Icon from 'component/common/icon';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
closeInlinePlayer: () => void,
|
||||
authorUri: string, // full LBRY Channel URI: lbry://@channel#123...
|
||||
commentId: string, // sha256 digest identifying the comment
|
||||
claimIsMine: boolean, // if you control the claim which this comment was posted on
|
||||
commentIsMine: boolean, // if this comment was signed by an owned channel
|
||||
deleteComment: (string, ?string) => void,
|
||||
linkedComment?: any,
|
||||
isPinned: boolean,
|
||||
pinComment: (string, boolean) => Promise<any>,
|
||||
blockChannel: string => void,
|
||||
fetchComments: string => void,
|
||||
handleEditComment: () => void,
|
||||
contentChannelPermanentUrl: any,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
claimIsMine: boolean,
|
||||
isTopLevel: boolean,
|
||||
// commentModBlock: string => void,
|
||||
};
|
||||
|
||||
function CommentMenuList(props: Props) {
|
||||
const {
|
||||
uri,
|
||||
authorUri,
|
||||
commentIsMine,
|
||||
commentId,
|
||||
deleteComment,
|
||||
blockChannel,
|
||||
pinComment,
|
||||
claimIsMine,
|
||||
closeInlinePlayer,
|
||||
activeChannelClaim,
|
||||
contentChannelPermanentUrl,
|
||||
isTopLevel,
|
||||
isPinned,
|
||||
handleEditComment,
|
||||
fetchComments,
|
||||
// commentModBlock,
|
||||
// setActiveChannel,
|
||||
} = props;
|
||||
const activeChannelIsCreator = activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl;
|
||||
|
||||
// let authorChannel;
|
||||
// try {
|
||||
// const { claimName } = parseURI(authorUri);
|
||||
// authorChannel = claimName;
|
||||
// } catch (e) {}
|
||||
|
||||
function handlePinComment(commentId, remove) {
|
||||
pinComment(commentId, remove).then(() => fetchComments(uri));
|
||||
}
|
||||
|
||||
function handleDeleteComment() {
|
||||
closeInlinePlayer();
|
||||
deleteComment(commentId, commentIsMine ? undefined : contentChannelPermanentUrl);
|
||||
}
|
||||
|
||||
function handleCommentBlock() {
|
||||
if (claimIsMine) {
|
||||
// Block them from commenting on future content
|
||||
// commentModBlock(authorUri);
|
||||
}
|
||||
|
||||
blockChannel(authorUri);
|
||||
}
|
||||
|
||||
// function handleChooseChannel() {
|
||||
// const { channelClaimId } = parseURI(authorUri);
|
||||
// setActiveChannel(channelClaimId);
|
||||
// }
|
||||
|
||||
return (
|
||||
<MenuList className="menu__list--comments">
|
||||
{/* {commentIsMine && activeChannelClaim && activeChannelClaim.permanent_url !== authorUri && (
|
||||
<MenuItem className="comment__menu-option" onSelect={handleChooseChannel}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||
{__('Use this channel')}
|
||||
</div>
|
||||
<span className="comment__menu-help">
|
||||
{__('Switch to %channel_name% to interact with this comment', { channel_name: authorChannel })}.
|
||||
</span>
|
||||
</MenuItem>
|
||||
)} */}
|
||||
|
||||
{activeChannelIsCreator && <div className="comment__menu-title">{__('Creator tools')}</div>}
|
||||
|
||||
{activeChannelIsCreator && isTopLevel && (
|
||||
<MenuItem
|
||||
className="comment__menu-option menu__link"
|
||||
onSelect={isPinned ? () => handlePinComment(commentId, true) : () => handlePinComment(commentId, false)}
|
||||
>
|
||||
<span className={'button__content'}>
|
||||
<Icon aria-hidden icon={ICONS.PIN} className={'icon'} />
|
||||
{isPinned ? __('Unpin') : __('Pin')}
|
||||
</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{activeChannelClaim &&
|
||||
(activeChannelClaim.permanent_url === authorUri ||
|
||||
activeChannelClaim.permanent_url === contentChannelPermanentUrl) && (
|
||||
<MenuItem className="comment__menu-option" onSelect={handleDeleteComment}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.DELETE} />
|
||||
{__('Remove')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{commentIsMine && activeChannelClaim && activeChannelClaim.permanent_url === authorUri && (
|
||||
<MenuItem className="comment__menu-option menu__link" onSelect={handleEditComment}>
|
||||
<Icon aria-hidden icon={ICONS.EDIT} />
|
||||
{__('Edit')}
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{/* Disabled until we deal with current app blocklist parity */}
|
||||
{!commentIsMine && (
|
||||
<MenuItem className="comment__menu-option" onSelect={handleCommentBlock}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.BLOCK} />
|
||||
{__('Block')}
|
||||
</div>
|
||||
{/* {activeChannelIsCreator && (
|
||||
<span className="comment__menu-help">Hide this channel's comments and block them from commenting.</span>
|
||||
)} */}
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{activeChannelClaim && (
|
||||
<div className="comment__menu-active">
|
||||
<ChannelThumbnail uri={activeChannelClaim.permanent_url} />
|
||||
<div className="comment__menu-channel">Interacting as {activeChannelClaim.name}</div>
|
||||
</div>
|
||||
)}
|
||||
</MenuList>
|
||||
);
|
||||
}
|
||||
|
||||
export default CommentMenuList;
|
|
@ -1,11 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectBalance } from 'lbry-redux';
|
||||
import { selectBalance, selectFetchingMyChannels } from 'lbry-redux';
|
||||
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
|
||||
import PublishPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
balance: selectBalance(state),
|
||||
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||
fetchingChannels: selectFetchingMyChannels(state),
|
||||
});
|
||||
|
||||
export default connect(select, null)(PublishPage);
|
||||
|
|
|
@ -3,13 +3,15 @@ import React from 'react';
|
|||
import PublishForm from 'component/publishForm';
|
||||
import Page from 'component/page';
|
||||
import YrblWalletEmpty from 'component/yrblWalletEmpty';
|
||||
import Spinner from 'component/spinner';
|
||||
|
||||
type Props = {
|
||||
balance: number,
|
||||
fetchingChannels: boolean,
|
||||
};
|
||||
|
||||
function PublishPage(props: Props) {
|
||||
const { balance } = props;
|
||||
const { balance, fetchingChannels } = props;
|
||||
|
||||
function scrollToTop() {
|
||||
const mainContent = document.querySelector('main');
|
||||
|
@ -32,7 +34,13 @@ function PublishPage(props: Props) {
|
|||
}}
|
||||
>
|
||||
{balance === 0 && <YrblWalletEmpty />}
|
||||
<PublishForm scrollToTop={scrollToTop} disabled={balance === 0} />
|
||||
{balance !== 0 && fetchingChannels ? (
|
||||
<div className="main--empty">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<PublishForm scrollToTop={scrollToTop} disabled={balance === 0} />
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
} from 'redux/selectors/comments';
|
||||
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { toHex } from 'util/hex';
|
||||
import Comments from 'comments';
|
||||
|
||||
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
|
@ -18,15 +20,23 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number =
|
|||
const claim = selectClaimsByUri(state)[uri];
|
||||
const claimId = claim ? claim.claim_id : null;
|
||||
|
||||
if (!claimId) {
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_LIST_FAILED,
|
||||
data: 'unable to find claim for uri',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_LIST_STARTED,
|
||||
});
|
||||
return Lbry.comment_list({
|
||||
claim_id: claimId,
|
||||
|
||||
return Comments.comment_list({
|
||||
page,
|
||||
claim_id: claimId,
|
||||
page_size: pageSize,
|
||||
include_replies: true,
|
||||
skip_validation: true,
|
||||
})
|
||||
.then((result: CommentListResponse) => {
|
||||
const { items: comments } = result;
|
||||
|
@ -238,35 +248,6 @@ export function doCommentCreate(comment: string = '', claim_id: string = '', par
|
|||
};
|
||||
}
|
||||
|
||||
export function doCommentHide(comment_id: string) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_HIDE_STARTED,
|
||||
});
|
||||
return Lbry.comment_hide({
|
||||
comment_ids: [comment_id],
|
||||
})
|
||||
.then((result: CommentHideResponse) => {
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_HIDE_COMPLETED,
|
||||
data: result,
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_HIDE_FAILED,
|
||||
data: error,
|
||||
});
|
||||
dispatch(
|
||||
doToast({
|
||||
message: 'Unable to hide this comment, please try again later.',
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doCommentPin(commentId: string, remove: boolean) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
|
@ -308,13 +289,33 @@ export function doCommentPin(commentId: string, remove: boolean) {
|
|||
};
|
||||
}
|
||||
|
||||
export function doCommentAbandon(comment_id: string) {
|
||||
return (dispatch: Dispatch) => {
|
||||
export function doCommentAbandon(commentId: string, creatorChannelUri?: string) {
|
||||
return async (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const claim = creatorChannelUri ? selectClaimsByUri(state)[creatorChannelUri] : undefined;
|
||||
const creatorChannelId = claim ? claim.claim_id : null;
|
||||
const creatorChannelName = claim ? claim.name : null;
|
||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_ABANDON_STARTED,
|
||||
});
|
||||
return Lbry.comment_abandon({
|
||||
comment_id: comment_id,
|
||||
|
||||
let commentIdSignature;
|
||||
if (activeChannelClaim) {
|
||||
try {
|
||||
commentIdSignature = await Lbry.channel_sign({
|
||||
channel_id: activeChannelClaim.claim_id,
|
||||
hexdata: toHex(commentId),
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return Comments.comment_abandon({
|
||||
comment_id: commentId,
|
||||
...(creatorChannelId ? { creator_channel_id: creatorChannelId } : {}),
|
||||
...(creatorChannelName ? { creator_channel_name: creatorChannelName } : {}),
|
||||
...(commentIdSignature || {}),
|
||||
})
|
||||
.then((result: CommentAbandonResponse) => {
|
||||
// Comment may not be deleted if the signing channel can't be signed.
|
||||
|
@ -323,7 +324,7 @@ export function doCommentAbandon(comment_id: string) {
|
|||
dispatch({
|
||||
type: ACTIONS.COMMENT_ABANDON_COMPLETED,
|
||||
data: {
|
||||
comment_id: comment_id,
|
||||
comment_id: commentId,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
@ -343,6 +344,7 @@ export function doCommentAbandon(comment_id: string) {
|
|||
type: ACTIONS.COMMENT_ABANDON_FAILED,
|
||||
data: error,
|
||||
});
|
||||
|
||||
dispatch(
|
||||
doToast({
|
||||
message: 'Unable to delete this comment, please try again later.',
|
||||
|
@ -402,3 +404,40 @@ export function doCommentUpdate(comment_id: string, comment: string) {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Hides a users comments from all creator's claims and prevent them from commenting in the future
|
||||
export function doCommentModBlock(commentAuthor: string) {
|
||||
return async (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const claim = selectClaimsByUri(state)[commentAuthor];
|
||||
|
||||
if (!claim) {
|
||||
console.error("Can't find claim to block"); // eslint-disable-line
|
||||
return;
|
||||
}
|
||||
|
||||
const creatorIdToBan = claim ? claim.claim_id : null;
|
||||
const creatorNameToBan = claim ? claim.name : null;
|
||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||
|
||||
let channelSignature = {};
|
||||
if (activeChannelClaim) {
|
||||
try {
|
||||
channelSignature = await Lbry.channel_sign({
|
||||
channel_id: activeChannelClaim.claim_id,
|
||||
hexdata: toHex(activeChannelClaim.name),
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return Comments.moderation_block({
|
||||
mod_channel_id: activeChannelClaim.claim_id,
|
||||
mod_channel_name: activeChannelClaim.name,
|
||||
signature: channelSignature.signature,
|
||||
signing_ts: channelSignature.signing_ts,
|
||||
banned_channel_id: creatorIdToBan,
|
||||
banned_channel_name: creatorNameToBan,
|
||||
delete_all: true,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -310,6 +310,7 @@ reducers[ACTIONS.SET_ACTIVE_CHANNEL] = (state, action) => {
|
|||
activeChannel: action.data.claimId,
|
||||
};
|
||||
};
|
||||
|
||||
reducers[ACTIONS.SET_INCOGNITO] = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -212,6 +212,7 @@ $thumbnailWidthSmall: 0rem;
|
|||
|
||||
.comment__menu {
|
||||
align-self: flex-end;
|
||||
line-height: 1;
|
||||
|
||||
button {
|
||||
border-radius: var(--border-radius);
|
||||
|
@ -239,7 +240,13 @@ $thumbnailWidthSmall: 0rem;
|
|||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing-s);
|
||||
font-size: var(--font-small);
|
||||
font-size: var(--font-xsmall);
|
||||
|
||||
.menu__link {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.comment__menu-icon--hovering {
|
||||
|
@ -256,6 +263,26 @@ $thumbnailWidthSmall: 0rem;
|
|||
padding: var(--spacing-s);
|
||||
}
|
||||
|
||||
.comment__menu-title {
|
||||
@extend .help;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: var(--font-small);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin-top: 0;
|
||||
padding-left: var(--spacing-s);
|
||||
padding-bottom: var(--spacing-s);
|
||||
padding-right: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.comment__menu-help {
|
||||
@extend .help;
|
||||
margin-top: var(--spacing-xs);
|
||||
padding-left: calc(18px + var(--spacing-s));
|
||||
max-width: 15rem;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.comment__actions {
|
||||
display: flex;
|
||||
margin-top: var(--spacing-s);
|
||||
|
@ -332,3 +359,27 @@ $thumbnailWidthSmall: 0rem;
|
|||
top: 0.4rem;
|
||||
left: 0.4rem;
|
||||
}
|
||||
|
||||
.comment__menu-active {
|
||||
padding-left: var(--spacing-s);
|
||||
padding-top: var(--spacing-s);
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin-top: var(--spacing-s);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.channel-thumbnail {
|
||||
margin-right: var(--spacing-xs);
|
||||
height: 1.8rem;
|
||||
width: 1.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.comment__menu-channel {
|
||||
@extend .help;
|
||||
font-size: var(--font-xsmall);
|
||||
margin-top: 0;
|
||||
max-width: 10rem;
|
||||
white-space: pre-line;
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
|
|
@ -446,6 +446,8 @@ fieldset-section {
|
|||
}
|
||||
|
||||
.select--slim {
|
||||
margin-bottom: var(--spacing-xxs);
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
max-width: none;
|
||||
}
|
||||
|
|
|
@ -66,7 +66,11 @@
|
|||
@extend .menu__list;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--spacing-xxs);
|
||||
padding: var(--spacing-s) 0;
|
||||
|
||||
[data-reach-menu-item] {
|
||||
margin: 0 var(--spacing-xs);
|
||||
}
|
||||
}
|
||||
|
||||
.menu__link {
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
--color-placeholder-background: #f0f0f0;
|
||||
--color-header-background: #ffffff;
|
||||
--color-card-background: #ffffff;
|
||||
--color-card-background-highlighted: #f0f7ff;
|
||||
--color-card-background-highlighted: #f1f7fe;
|
||||
--color-list-header: #fff;
|
||||
--color-file-viewer-background: var(--color-card-background);
|
||||
--color-tabs-background: var(--color-card-background);
|
||||
|
|
8
ui/util/hex.js
Normal file
8
ui/util/hex.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
// @flow
|
||||
export function toHex(str: string): string {
|
||||
var result = '';
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
result += str.charCodeAt(i).toString(16);
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -6919,9 +6919,9 @@ lazy-val@^1.0.4:
|
|||
yargs "^13.2.2"
|
||||
zstd-codec "^0.1.1"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#f0849b4ce19e5e9600b74d61c6db82f0b853b9e8:
|
||||
lbry-redux@lbryio/lbry-redux#d90cbd18925788b01fa9c4055c81300cb39e0b3a:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/f0849b4ce19e5e9600b74d61c6db82f0b853b9e8"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/d90cbd18925788b01fa9c4055c81300cb39e0b3a"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue