diff --git a/.env.defaults b/.env.defaults index d72ab3fe6..4a7a04d8d 100644 --- a/.env.defaults +++ b/.env.defaults @@ -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 diff --git a/.flowconfig b/.flowconfig index 768762cdc..4fe268126 100644 --- a/.flowconfig +++ b/.flowconfig @@ -33,6 +33,7 @@ module.name_mapper='^analytics\(.*\)$' -> '/ui/analytics\1' module.name_mapper='^rewards\(.*\)$' -> '/ui/rewards\1' module.name_mapper='^i18n\(.*\)$' -> '/ui/i18n\1' module.name_mapper='^effects\(.*\)$' -> '/ui/effects\1' +module.name_mapper='^comments\(.*\)$' -> '/ui/comments\1' module.name_mapper='^config\(.*\)$' -> '/config\1' module.name_mapper='^web\/component\(.*\)$' -> '/web/component\1' module.name_mapper='^web\/effects\(.*\)$' -> '/web/effects\1' diff --git a/config.js b/config.js index 07467c7e6..69ea0e060 100644 --- a/config.js +++ b/config.js @@ -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, diff --git a/flow-typed/comments.js b/flow-typed/comments.js new file mode 100644 index 000000000..ee033cd62 --- /dev/null +++ b/flow-typed/comments.js @@ -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 = {}; diff --git a/package.json b/package.json index ddeb5531c..e9c27f917 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/ui/comments.js b/ui/comments.js new file mode 100644 index 000000000..0bd342256 --- /dev/null +++ b/ui/comments.js @@ -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; diff --git a/ui/component/app/index.js b/ui/component/app/index.js index e135e30f2..ba179d3fd 100644 --- a/ui/component/app/index.js +++ b/ui/component/app/index.js @@ -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), }); diff --git a/ui/component/app/view.jsx b/ui/component/app/view.jsx index 419720ea1..2324e58c6 100644 --- a/ui/component/app/view.jsx +++ b/ui/component/app/view.jsx @@ -80,9 +80,9 @@ type Props = { syncEnabled: boolean, currentModal: any, syncFatalError: boolean, - activeChannelId: ?string, + activeChannelClaim: ?ChannelClaim, myChannelUrls: ?Array, - 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)) { diff --git a/ui/component/comment/index.js b/ui/component/comment/index.js index b951ab64e..7f94e4a96 100644 --- a/ui/component/comment/index.js +++ b/ui/component/comment/index.js @@ -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); diff --git a/ui/component/comment/view.jsx b/ui/component/comment/view.jsx index 1324e405d..61b042b8a 100644 --- a/ui/component/comment/view.jsx +++ b/ui/component/comment/view.jsx @@ -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, commentingEnabled: boolean, @@ -45,10 +45,7 @@ type Props = { like: number, dislike: number, }, - pinComment: (string, boolean) => Promise, - 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} /> - - {commentIsMine ? ( - <> - - - {__('Edit')} - - - - {__('Delete')} - - - ) : ( - blockChannel(authorUri)}> - - {__('Block Channel')} - - )} - {activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl && isTopLevel && ( - handlePinComment(commentId, true) : () => handlePinComment(commentId, false) - } - > - - - {isPinned ? __('Unpin') : __('Pin')} - - - )} - + diff --git a/ui/component/commentMenuList/index.js b/ui/component/commentMenuList/index.js new file mode 100644 index 000000000..4ade95996 --- /dev/null +++ b/ui/component/commentMenuList/index.js @@ -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); diff --git a/ui/component/commentMenuList/view.jsx b/ui/component/commentMenuList/view.jsx new file mode 100644 index 000000000..e0729b843 --- /dev/null +++ b/ui/component/commentMenuList/view.jsx @@ -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, + 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 ( + + {/* {commentIsMine && activeChannelClaim && activeChannelClaim.permanent_url !== authorUri && ( + +
+ + {__('Use this channel')} +
+ + {__('Switch to %channel_name% to interact with this comment', { channel_name: authorChannel })}. + +
+ )} */} + + {activeChannelIsCreator &&
{__('Creator tools')}
} + + {activeChannelIsCreator && isTopLevel && ( + handlePinComment(commentId, true) : () => handlePinComment(commentId, false)} + > + + + {isPinned ? __('Unpin') : __('Pin')} + + + )} + + {activeChannelClaim && + (activeChannelClaim.permanent_url === authorUri || + activeChannelClaim.permanent_url === contentChannelPermanentUrl) && ( + +
+ + {__('Remove')} +
+
+ )} + + {commentIsMine && activeChannelClaim && activeChannelClaim.permanent_url === authorUri && ( + + + {__('Edit')} + + )} + + {/* Disabled until we deal with current app blocklist parity */} + {!commentIsMine && ( + +
+ + {__('Block')} +
+ {/* {activeChannelIsCreator && ( + Hide this channel's comments and block them from commenting. + )} */} +
+ )} + + {activeChannelClaim && ( +
+ +
Interacting as {activeChannelClaim.name}
+
+ )} +
+ ); +} + +export default CommentMenuList; diff --git a/ui/page/publish/index.js b/ui/page/publish/index.js index 1de5ad0a8..dd5b60f09 100644 --- a/ui/page/publish/index.js +++ b/ui/page/publish/index.js @@ -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); diff --git a/ui/page/publish/view.jsx b/ui/page/publish/view.jsx index e04bf4172..4cde90552 100644 --- a/ui/page/publish/view.jsx +++ b/ui/page/publish/view.jsx @@ -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 && } - + {balance !== 0 && fetchingChannels ? ( +
+ +
+ ) : ( + + )} ); } diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js index 67e558c3b..64dbc8778 100644 --- a/ui/redux/actions/comments.js +++ b/ui/redux/actions/comments.js @@ -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, + }); + }; +} diff --git a/ui/redux/reducers/app.js b/ui/redux/reducers/app.js index aaae9dbe7..69f958f83 100644 --- a/ui/redux/reducers/app.js +++ b/ui/redux/reducers/app.js @@ -310,6 +310,7 @@ reducers[ACTIONS.SET_ACTIVE_CHANNEL] = (state, action) => { activeChannel: action.data.claimId, }; }; + reducers[ACTIONS.SET_INCOGNITO] = (state, action) => { return { ...state, diff --git a/ui/scss/component/_comments.scss b/ui/scss/component/_comments.scss index 7fc7acd7c..dd9fe8985 100644 --- a/ui/scss/component/_comments.scss +++ b/ui/scss/component/_comments.scss @@ -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); +} diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index 8d5a194bf..ffbe29d27 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -446,6 +446,8 @@ fieldset-section { } .select--slim { + margin-bottom: var(--spacing-xxs); + @media (min-width: $breakpoint-small) { max-width: none; } diff --git a/ui/scss/component/menu-button.scss b/ui/scss/component/menu-button.scss index 18c0fc869..45ec6418b 100644 --- a/ui/scss/component/menu-button.scss +++ b/ui/scss/component/menu-button.scss @@ -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 { diff --git a/ui/scss/themes/light.scss b/ui/scss/themes/light.scss index d1567e22e..f6893d5b3 100644 --- a/ui/scss/themes/light.scss +++ b/ui/scss/themes/light.scss @@ -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); diff --git a/ui/util/hex.js b/ui/util/hex.js new file mode 100644 index 000000000..e8a58b20a --- /dev/null +++ b/ui/util/hex.js @@ -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; +} diff --git a/yarn.lock b/yarn.lock index a54793da0..3cdea5f4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"