New moderation tools: block & mute #5572
75 changed files with 1115 additions and 877 deletions
4
flow-typed/Comment.js
vendored
4
flow-typed/Comment.js
vendored
|
@ -27,6 +27,10 @@ declare type CommentsState = {
|
||||||
myReactsByCommentId: any,
|
myReactsByCommentId: any,
|
||||||
othersReactsByCommentId: any,
|
othersReactsByCommentId: any,
|
||||||
pendingCommentReactions: Array<string>,
|
pendingCommentReactions: Array<string>,
|
||||||
|
moderationBlockList: ?Array<string>,
|
||||||
|
fetchingModerationBlockList: boolean,
|
||||||
|
blockingByUri: {},
|
||||||
|
unBlockingByUri: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
declare type CommentReactParams = {
|
declare type CommentReactParams = {
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
"electron-updater": "^4.2.4",
|
"electron-updater": "^4.2.4",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"if-env": "^1.0.4",
|
"if-env": "^1.0.4",
|
||||||
"remove-markdown": "^0.3.0",
|
"remove-markdown": "^0.3.0",
|
||||||
"tempy": "^0.6.0",
|
"tempy": "^0.6.0",
|
||||||
"videojs-logo": "^2.1.4"
|
"videojs-logo": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
@ -184,7 +184,7 @@
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
"remark": "^9.0.0",
|
"remark": "^9.0.0",
|
||||||
"remark-attr": "^0.8.3",
|
"remark-attr": "^0.8.3",
|
||||||
"remark-breaks": "^1.0.5",
|
"remark-breaks": "^1.0.5",
|
||||||
"remark-emoji": "^2.0.1",
|
"remark-emoji": "^2.0.1",
|
||||||
"remark-frontmatter": "^2.0.0",
|
"remark-frontmatter": "^2.0.0",
|
||||||
"remark-react": "^8.0.0",
|
"remark-react": "^8.0.0",
|
||||||
|
|
|
@ -6,6 +6,8 @@ const Comments = {
|
||||||
enabled: Boolean(COMMENT_SERVER_API),
|
enabled: Boolean(COMMENT_SERVER_API),
|
||||||
|
|
||||||
moderation_block: (params: ModerationBlockParams) => fetchCommentsApi('moderation.Block', params),
|
moderation_block: (params: ModerationBlockParams) => fetchCommentsApi('moderation.Block', params),
|
||||||
|
moderation_unblock: (params: ModerationBlockParams) => fetchCommentsApi('moderation.UnBlock', params),
|
||||||
|
moderation_block_list: (params: ModerationBlockParams) => fetchCommentsApi('moderation.BlockedList', params),
|
||||||
comment_list: (params: CommentListParams) => fetchCommentsApi('comment.List', params),
|
comment_list: (params: CommentListParams) => fetchCommentsApi('comment.List', params),
|
||||||
comment_abandon: (params: CommentAbandonParams) => fetchCommentsApi('comment.Abandon', params),
|
comment_abandon: (params: CommentAbandonParams) => fetchCommentsApi('comment.Abandon', params),
|
||||||
};
|
};
|
||||||
|
@ -30,8 +32,8 @@ function fetchCommentsApi(method: string, params: {}) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return fetch(url, options)
|
return fetch(url, options)
|
||||||
.then(res => res.json())
|
.then((res) => res.json())
|
||||||
.then(res => res.result);
|
.then((res) => res.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Comments;
|
export default Comments;
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
|
||||||
import { doChannelUnsubscribe } from 'redux/actions/subscriptions';
|
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
|
||||||
import AbandonedChannelPreview from './view';
|
import AbandonedChannelPreview from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({});
|
||||||
blockedChannelUris: selectBlockedChannels(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, {
|
export default connect(select)(AbandonedChannelPreview);
|
||||||
doChannelUnsubscribe,
|
|
||||||
doOpenModal,
|
|
||||||
})(AbandonedChannelPreview);
|
|
||||||
|
|
|
@ -2,28 +2,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
import Button from 'component/button';
|
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
import * as ICONS from '../../constants/icons';
|
import ChannelBlockButton from 'component/channelBlockButton';
|
||||||
import * as MODALS from 'constants/modal_types';
|
import ChannelMuteButton from 'component/channelMuteButton';
|
||||||
|
|
||||||
type SubscriptionArgs = {
|
|
||||||
channelName: string,
|
|
||||||
uri: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
doChannelUnsubscribe: SubscriptionArgs => void,
|
|
||||||
type: string,
|
type: string,
|
||||||
blockedChannelUris: Array<string>,
|
|
||||||
doOpenModal: (string, {}) => void,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function AbandonedChannelPreview(props: Props) {
|
function AbandonedChannelPreview(props: Props) {
|
||||||
const { uri, doChannelUnsubscribe, type, blockedChannelUris, doOpenModal } = props;
|
const { uri, type } = props;
|
||||||
const { channelName } = parseURI(uri);
|
const { channelName } = parseURI(uri);
|
||||||
const isBlockedChannel = blockedChannelUris.includes(uri);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={classnames('claim-preview__wrapper', 'claim-preview__wrapper--notice')}>
|
<li className={classnames('claim-preview__wrapper', 'claim-preview__wrapper--notice')}>
|
||||||
|
@ -37,31 +27,10 @@ function AbandonedChannelPreview(props: Props) {
|
||||||
<div className="media__subtitle">{__(`This channel may have been unpublished.`)}</div>
|
<div className="media__subtitle">{__(`This channel may have been unpublished.`)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="claim-preview__actions">
|
<div className="claim-preview__actions">
|
||||||
{isBlockedChannel && (
|
<div className="section__actions">
|
||||||
<Button
|
<ChannelBlockButton uri={uri} />
|
||||||
iconColor="red"
|
<ChannelMuteButton uri={uri} />
|
||||||
icon={ICONS.UNBLOCK}
|
</div>
|
||||||
button={'alt'}
|
|
||||||
label={__('Unblock')}
|
|
||||||
onClick={() => doOpenModal(MODALS.REMOVE_BLOCKED, { blockedUri: uri })}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* SubscribeButton uses resolved permanentUri; modifying it didn't seem worth it. */}
|
|
||||||
{!isBlockedChannel && (
|
|
||||||
<Button
|
|
||||||
iconColor="red"
|
|
||||||
icon={ICONS.UNSUBSCRIBE}
|
|
||||||
button={'alt'}
|
|
||||||
label={__('Unfollow')}
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
doChannelUnsubscribe({
|
|
||||||
channelName: `@${channelName}`,
|
|
||||||
uri,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,9 +27,10 @@ import {
|
||||||
doSetActiveChannel,
|
doSetActiveChannel,
|
||||||
doSetIncognito,
|
doSetIncognito,
|
||||||
} from 'redux/actions/app';
|
} from 'redux/actions/app';
|
||||||
|
import { doFetchModBlockedList } from 'redux/actions/comments';
|
||||||
import App from './view';
|
import App from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state) => ({
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
accessToken: selectAccessToken(state),
|
accessToken: selectAccessToken(state),
|
||||||
theme: selectThemePath(state),
|
theme: selectThemePath(state),
|
||||||
|
@ -48,18 +49,19 @@ const select = state => ({
|
||||||
myChannelUrls: selectMyChannelUrls(state),
|
myChannelUrls: selectMyChannelUrls(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = (dispatch) => ({
|
||||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
||||||
setLanguage: language => dispatch(doSetLanguage(language)),
|
setLanguage: (language) => dispatch(doSetLanguage(language)),
|
||||||
signIn: () => dispatch(doSignIn()),
|
signIn: () => dispatch(doSignIn()),
|
||||||
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
|
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
|
||||||
updatePreferences: () => dispatch(doGetAndPopulatePreferences()),
|
updatePreferences: () => dispatch(doGetAndPopulatePreferences()),
|
||||||
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
|
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
|
||||||
syncLoop: noInterval => dispatch(doSyncLoop(noInterval)),
|
syncLoop: (noInterval) => dispatch(doSyncLoop(noInterval)),
|
||||||
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
||||||
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),
|
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),
|
||||||
setIncognito: () => dispatch(doSetIncognito()),
|
setIncognito: () => dispatch(doSetIncognito()),
|
||||||
|
fetchModBlockedList: () => dispatch(doFetchModBlockedList()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default hot(connect(select, perform)(App));
|
export default hot(connect(select, perform)(App));
|
||||||
|
|
|
@ -56,14 +56,14 @@ type Props = {
|
||||||
goForward: () => void,
|
goForward: () => void,
|
||||||
index: number,
|
index: number,
|
||||||
length: number,
|
length: number,
|
||||||
push: string => void,
|
push: (string) => void,
|
||||||
},
|
},
|
||||||
fetchAccessToken: () => void,
|
fetchAccessToken: () => void,
|
||||||
fetchChannelListMine: () => void,
|
fetchChannelListMine: () => void,
|
||||||
signIn: () => void,
|
signIn: () => void,
|
||||||
requestDownloadUpgrade: () => void,
|
requestDownloadUpgrade: () => void,
|
||||||
onSignedIn: () => void,
|
onSignedIn: () => void,
|
||||||
setLanguage: string => void,
|
setLanguage: (string) => void,
|
||||||
isUpgradeAvailable: boolean,
|
isUpgradeAvailable: boolean,
|
||||||
autoUpdateDownloaded: boolean,
|
autoUpdateDownloaded: boolean,
|
||||||
updatePreferences: () => Promise<any>,
|
updatePreferences: () => Promise<any>,
|
||||||
|
@ -83,7 +83,8 @@ type Props = {
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
myChannelUrls: ?Array<string>,
|
myChannelUrls: ?Array<string>,
|
||||||
setActiveChannelIfNotSet: () => void,
|
setActiveChannelIfNotSet: () => void,
|
||||||
setIncognito: boolean => void,
|
setIncognito: (boolean) => void,
|
||||||
|
fetchModBlockedList: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function App(props: Props) {
|
function App(props: Props) {
|
||||||
|
@ -114,6 +115,7 @@ function App(props: Props) {
|
||||||
activeChannelClaim,
|
activeChannelClaim,
|
||||||
setActiveChannelIfNotSet,
|
setActiveChannelIfNotSet,
|
||||||
setIncognito,
|
setIncognito,
|
||||||
|
fetchModBlockedList,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const appRef = useRef();
|
const appRef = useRef();
|
||||||
|
@ -134,7 +136,7 @@ function App(props: Props) {
|
||||||
const showUpgradeButton =
|
const showUpgradeButton =
|
||||||
(autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable)) && !upgradeNagClosed;
|
(autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable)) && !upgradeNagClosed;
|
||||||
// referral claiming
|
// referral claiming
|
||||||
const referredRewardAvailable = rewards && rewards.some(reward => reward.reward_type === REWARDS.TYPE_REFEREE);
|
const referredRewardAvailable = rewards && rewards.some((reward) => reward.reward_type === REWARDS.TYPE_REFEREE);
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const rawReferrerParam = urlParams.get('r');
|
const rawReferrerParam = urlParams.get('r');
|
||||||
const sanitizedReferrerParam = rawReferrerParam && rawReferrerParam.replace(':', '#');
|
const sanitizedReferrerParam = rawReferrerParam && rawReferrerParam.replace(':', '#');
|
||||||
|
@ -166,7 +168,7 @@ function App(props: Props) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!uploadCount) return;
|
if (!uploadCount) return;
|
||||||
const handleBeforeUnload = event => {
|
const handleBeforeUnload = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.returnValue = 'magic'; // without setting this to something it doesn't work
|
event.returnValue = 'magic'; // without setting this to something it doesn't work
|
||||||
};
|
};
|
||||||
|
@ -176,7 +178,7 @@ function App(props: Props) {
|
||||||
|
|
||||||
// allows user to navigate history using the forward and backward buttons on a mouse
|
// allows user to navigate history using the forward and backward buttons on a mouse
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleForwardAndBackButtons = e => {
|
const handleForwardAndBackButtons = (e) => {
|
||||||
switch (e.button) {
|
switch (e.button) {
|
||||||
case MOUSE_BACK_BTN:
|
case MOUSE_BACK_BTN:
|
||||||
history.index > 0 && history.goBack();
|
history.index > 0 && history.goBack();
|
||||||
|
@ -192,7 +194,7 @@ function App(props: Props) {
|
||||||
|
|
||||||
// allows user to pause miniplayer using the spacebar without the page scrolling down
|
// allows user to pause miniplayer using the spacebar without the page scrolling down
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyPress = e => {
|
const handleKeyPress = (e) => {
|
||||||
if (e.key === ' ' && e.target === document.body) {
|
if (e.key === ' ' && e.target === document.body) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -244,6 +246,10 @@ function App(props: Props) {
|
||||||
} else if (hasNoChannels) {
|
} else if (hasNoChannels) {
|
||||||
setIncognito(true);
|
setIncognito(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasMyChannels) {
|
||||||
|
fetchModBlockedList();
|
||||||
|
}
|
||||||
}, [hasMyChannels, hasNoChannels, hasActiveChannelClaim, setActiveChannelIfNotSet, setIncognito]);
|
}, [hasMyChannels, hasNoChannels, hasActiveChannelClaim, setActiveChannelIfNotSet, setIncognito]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -358,7 +364,7 @@ function App(props: Props) {
|
||||||
[`${MAIN_WRAPPER_CLASS}--scrollbar`]: useCustomScrollbar,
|
[`${MAIN_WRAPPER_CLASS}--scrollbar`]: useCustomScrollbar,
|
||||||
})}
|
})}
|
||||||
ref={appRef}
|
ref={appRef}
|
||||||
onContextMenu={IS_WEB ? undefined : e => openContextMenu(e)}
|
onContextMenu={IS_WEB ? undefined : (e) => openContextMenu(e)}
|
||||||
>
|
>
|
||||||
{IS_WEB && lbryTvApiStatus === STATUS_DOWN ? (
|
{IS_WEB && lbryTvApiStatus === STATUS_DOWN ? (
|
||||||
<Yrbl
|
<Yrbl
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { makeSelectClaimIsMine, makeSelectShortUrlForUri, makeSelectPermanentUrlForUri } from 'lbry-redux';
|
|
||||||
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
|
||||||
import { doToast } from 'redux/actions/notifications';
|
|
||||||
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
|
||||||
import BlockButton from './view';
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
|
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
|
||||||
shortUrl: makeSelectShortUrlForUri(props.uri)(state),
|
|
||||||
permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, {
|
|
||||||
toggleBlockChannel: doToggleBlockChannel,
|
|
||||||
doToast,
|
|
||||||
})(BlockButton);
|
|
|
@ -1,49 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import * as PAGES from 'constants/pages';
|
|
||||||
import React, { useRef } from 'react';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import useHover from 'effects/use-hover';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
permanentUrl: ?string,
|
|
||||||
shortUrl: string,
|
|
||||||
isSubscribed: boolean,
|
|
||||||
toggleBlockChannel: (uri: string) => void,
|
|
||||||
channelIsBlocked: boolean,
|
|
||||||
claimIsMine: boolean,
|
|
||||||
doToast: ({ message: string, linkText: string, linkTarget: string }) => void,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function BlockButton(props: Props) {
|
|
||||||
const { permanentUrl, shortUrl, toggleBlockChannel, channelIsBlocked, claimIsMine, doToast } = props;
|
|
||||||
|
|
||||||
const blockRef = useRef();
|
|
||||||
const isHovering = useHover(blockRef);
|
|
||||||
const blockLabel = channelIsBlocked ? __('Blocked') : __('Block');
|
|
||||||
const blockTitlePrefix = channelIsBlocked ? __('Unblock this channel') : __('Block this channel');
|
|
||||||
const blockedOverride = channelIsBlocked && isHovering && __('Unblock');
|
|
||||||
|
|
||||||
return permanentUrl && (!claimIsMine || channelIsBlocked) ? (
|
|
||||||
<Button
|
|
||||||
ref={blockRef}
|
|
||||||
icon={ICONS.BLOCK}
|
|
||||||
button={'alt'}
|
|
||||||
label={blockedOverride || blockLabel}
|
|
||||||
title={blockTitlePrefix}
|
|
||||||
requiresAuth={IS_WEB}
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (!channelIsBlocked) {
|
|
||||||
doToast({
|
|
||||||
message: __('Blocked %channelUrl%', { channelUrl: shortUrl }),
|
|
||||||
linkText: __('Manage'),
|
|
||||||
linkTarget: `/${PAGES.BLOCKED}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleBlockChannel(permanentUrl);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
}
|
|
14
ui/component/channelBlockButton/index.js
Normal file
14
ui/component/channelBlockButton/index.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doCommentModUnBlock, doCommentModBlock } from 'redux/actions/comments';
|
||||||
|
import { makeSelectChannelIsBlocked, makeSelectUriIsBlockingOrUnBlocking } from 'redux/selectors/comments';
|
||||||
|
import ChannelBlockButton from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
isBlocked: makeSelectChannelIsBlocked(props.uri)(state),
|
||||||
|
isBlockingOrUnBlocking: makeSelectUriIsBlockingOrUnBlocking(props.uri)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, {
|
||||||
|
doCommentModUnBlock,
|
||||||
|
doCommentModBlock,
|
||||||
|
})(ChannelBlockButton);
|
41
ui/component/channelBlockButton/view.jsx
Normal file
41
ui/component/channelBlockButton/view.jsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: string,
|
||||||
|
isBlocked: boolean,
|
||||||
|
isBlockingOrUnBlocking: boolean,
|
||||||
|
doCommentModUnBlock: (string) => void,
|
||||||
|
doCommentModBlock: (string) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ChannelBlockButton(props: Props) {
|
||||||
|
const { uri, doCommentModUnBlock, doCommentModBlock, isBlocked, isBlockingOrUnBlocking } = props;
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
if (isBlocked) {
|
||||||
|
doCommentModUnBlock(uri);
|
||||||
|
} else {
|
||||||
|
doCommentModBlock(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
button={isBlocked ? 'alt' : 'secondary'}
|
||||||
|
label={
|
||||||
|
isBlocked
|
||||||
|
? isBlockingOrUnBlocking
|
||||||
|
? __('Unblocking...')
|
||||||
|
: __('Unblock')
|
||||||
|
: isBlockingOrUnBlocking
|
||||||
|
? __('Blocking...')
|
||||||
|
: __('Block')
|
||||||
|
}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChannelBlockButton;
|
|
@ -8,7 +8,7 @@ import {
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
|
@ -24,7 +24,7 @@ const select = (state, props) => {
|
||||||
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
|
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
|
||||||
totalPages: makeSelectTotalPagesInChannelSearch(props.uri, PAGE_SIZE)(state),
|
totalPages: makeSelectTotalPagesInChannelSearch(props.uri, PAGE_SIZE)(state),
|
||||||
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
|
channelIsBlocked: makeSelectChannelIsMuted(props.uri)(state),
|
||||||
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
|
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
showMature: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
showMature: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
||||||
|
|
|
@ -29,6 +29,7 @@ type Props = {
|
||||||
isAuthenticated: boolean,
|
isAuthenticated: boolean,
|
||||||
showMature: boolean,
|
showMature: boolean,
|
||||||
tileLayout: boolean,
|
tileLayout: boolean,
|
||||||
|
viewBlockedChannel: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChannelContent(props: Props) {
|
function ChannelContent(props: Props) {
|
||||||
|
@ -44,6 +45,7 @@ function ChannelContent(props: Props) {
|
||||||
defaultInfiniteScroll = true,
|
defaultInfiniteScroll = true,
|
||||||
showMature,
|
showMature,
|
||||||
tileLayout,
|
tileLayout,
|
||||||
|
viewBlockedChannel,
|
||||||
} = props;
|
} = props;
|
||||||
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
||||||
const [searchQuery, setSearchQuery] = React.useState('');
|
const [searchQuery, setSearchQuery] = React.useState('');
|
||||||
|
@ -120,6 +122,7 @@ function ChannelContent(props: Props) {
|
||||||
|
|
||||||
{claim && claimsInChannel > 0 ? (
|
{claim && claimsInChannel > 0 ? (
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
|
showHiddenByUser={viewBlockedChannel}
|
||||||
forceShowReposts
|
forceShowReposts
|
||||||
tileLayout={tileLayout}
|
tileLayout={tileLayout}
|
||||||
uris={searchResults}
|
uris={searchResults}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import ChannelCreate from './view';
|
|
||||||
import { selectBalance, doCreateChannel, selectCreatingChannel, selectCreateChannelError } from 'lbry-redux';
|
|
||||||
|
|
||||||
const select = state => ({
|
|
||||||
balance: selectBalance(state),
|
|
||||||
creatingChannel: selectCreatingChannel(state),
|
|
||||||
createChannelError: selectCreateChannelError(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const perform = dispatch => ({
|
|
||||||
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, perform)(ChannelCreate);
|
|
|
@ -1,149 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import { isNameValid } from 'lbry-redux';
|
|
||||||
import { Form, FormField } from 'component/common/form';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import analytics from 'analytics';
|
|
||||||
import LbcSymbol from 'component/common/lbc-symbol';
|
|
||||||
import { MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
|
|
||||||
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
balance: number,
|
|
||||||
createChannel: (string, number) => Promise<any>,
|
|
||||||
onSuccess?: ({}) => void,
|
|
||||||
creatingChannel: boolean,
|
|
||||||
createChannelError: ?string,
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
newChannelName: string,
|
|
||||||
newChannelBid: number,
|
|
||||||
newChannelNameError: string,
|
|
||||||
newChannelBidError: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
class ChannelCreate extends React.PureComponent<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
newChannelName: '',
|
|
||||||
newChannelBid: 0.001,
|
|
||||||
newChannelNameError: '',
|
|
||||||
newChannelBidError: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
(this: any).handleNewChannelNameChange = this.handleNewChannelNameChange.bind(this);
|
|
||||||
(this: any).handleNewChannelBidChange = this.handleNewChannelBidChange.bind(this);
|
|
||||||
(this: any).handleCreateChannel = this.handleCreateChannel.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNewChannelNameChange(event: SyntheticInputEvent<*>) {
|
|
||||||
let newChannelName = event.target.value;
|
|
||||||
|
|
||||||
if (newChannelName.startsWith('@')) {
|
|
||||||
newChannelName = newChannelName.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let newChannelNameError;
|
|
||||||
if (newChannelName.length > 0 && !isNameValid(newChannelName, false)) {
|
|
||||||
newChannelNameError = INVALID_NAME_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
newChannelNameError,
|
|
||||||
newChannelName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNewChannelBidChange(newChannelBid: number) {
|
|
||||||
const { balance } = this.props;
|
|
||||||
let newChannelBidError;
|
|
||||||
if (newChannelBid === 0) {
|
|
||||||
newChannelBidError = __('Your deposit cannot be 0');
|
|
||||||
} else if (newChannelBid === balance) {
|
|
||||||
newChannelBidError = __('Please decrease your deposit to account for transaction fees');
|
|
||||||
} else if (newChannelBid > balance) {
|
|
||||||
newChannelBidError = __('Deposit cannot be higher than your available balance');
|
|
||||||
} else if (newChannelBid < MINIMUM_PUBLISH_BID) {
|
|
||||||
newChannelBidError = __('Your deposit must be higher');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
newChannelBid,
|
|
||||||
newChannelBidError,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCreateChannel() {
|
|
||||||
const { createChannel, onSuccess } = this.props;
|
|
||||||
const { newChannelBid, newChannelName } = this.state;
|
|
||||||
|
|
||||||
const channelName = `@${newChannelName.trim()}`;
|
|
||||||
|
|
||||||
const success = channelClaim => {
|
|
||||||
analytics.apiLogPublish(channelClaim);
|
|
||||||
|
|
||||||
if (onSuccess !== undefined) {
|
|
||||||
onSuccess({ ...this.props, ...this.state });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createChannel(channelName, newChannelBid).then(success);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { newChannelName, newChannelNameError, newChannelBid, newChannelBidError } = this.state;
|
|
||||||
const { creatingChannel, createChannelError } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form onSubmit={this.handleCreateChannel}>
|
|
||||||
{createChannelError && <div className="error__text">{createChannelError}</div>}
|
|
||||||
<div>
|
|
||||||
<FormField
|
|
||||||
label={__('Name')}
|
|
||||||
name="channel-input"
|
|
||||||
type="text"
|
|
||||||
placeholder={__('ChannelName')}
|
|
||||||
error={newChannelNameError}
|
|
||||||
value={newChannelName}
|
|
||||||
onChange={this.handleNewChannelNameChange}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
className="form-field--price-amount"
|
|
||||||
name="channel-deposit"
|
|
||||||
label={<LbcSymbol postfix={__('Deposit')} size={14} />}
|
|
||||||
step="any"
|
|
||||||
min="0"
|
|
||||||
type="number"
|
|
||||||
helper={
|
|
||||||
<>
|
|
||||||
{__(
|
|
||||||
'These LBRY Credits remain yours. It is a deposit to reserve the name and can be undone at any time.'
|
|
||||||
)}
|
|
||||||
<WalletSpendableBalanceHelp inline />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
error={newChannelBidError}
|
|
||||||
value={newChannelBid}
|
|
||||||
onChange={event => this.handleNewChannelBidChange(parseFloat(event.target.value))}
|
|
||||||
onWheel={e => e.stopPropagation()}
|
|
||||||
/>
|
|
||||||
<div className="section__actions">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
button="primary"
|
|
||||||
label={!creatingChannel ? __('Create channel') : __('Creating channel...')}
|
|
||||||
disabled={
|
|
||||||
!newChannelName || !newChannelBid || creatingChannel || newChannelNameError || newChannelBidError
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChannelCreate;
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
doClearChannelErrors,
|
doClearChannelErrors,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
|
import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments';
|
||||||
import ChannelPage from './view';
|
import ChannelPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -38,12 +38,16 @@ const select = (state, props) => ({
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = (dispatch) => ({
|
||||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
updateChannel: params => dispatch(doUpdateChannel(params)),
|
updateChannel: (params) => dispatch(doUpdateChannel(params)),
|
||||||
createChannel: params => {
|
createChannel: (params) => {
|
||||||
const { name, amount, ...optionalParams } = params;
|
const { name, amount, ...optionalParams } = params;
|
||||||
return dispatch(doCreateChannel('@' + name, amount, optionalParams));
|
return dispatch(
|
||||||
|
doCreateChannel('@' + name, amount, optionalParams, (channelClaim) => {
|
||||||
|
dispatch(doUpdateBlockListForPublishedChannel(channelClaim));
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
clearChannelErrors: () => dispatch(doClearChannelErrors()),
|
clearChannelErrors: () => dispatch(doClearChannelErrors()),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import {
|
|
||||||
doResolveUri,
|
|
||||||
selectPublishFormValues,
|
|
||||||
selectIsStillEditing,
|
|
||||||
selectMyClaimForUri,
|
|
||||||
selectIsResolvingPublishUris,
|
|
||||||
selectTakeOverAmount,
|
|
||||||
doResetThumbnailStatus,
|
|
||||||
doClearPublish,
|
|
||||||
doUpdatePublishForm,
|
|
||||||
doPrepareEdit,
|
|
||||||
} from 'lbry-redux';
|
|
||||||
import { doPublishDesktop } from 'redux/actions/publish';
|
|
||||||
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
|
|
||||||
import ChannelForm from './view';
|
|
||||||
|
|
||||||
const select = state => ({
|
|
||||||
...selectPublishFormValues(state),
|
|
||||||
// The winning claim for a short lbry uri
|
|
||||||
amountNeededForTakeover: selectTakeOverAmount(state),
|
|
||||||
// My previously published claims under this short lbry uri
|
|
||||||
myClaimForUri: selectMyClaimForUri(state),
|
|
||||||
// If I clicked the "edit" button, have I changed the uri?
|
|
||||||
// Need this to make it easier to find the source on previously published content
|
|
||||||
isStillEditing: selectIsStillEditing(state),
|
|
||||||
isResolvingUri: selectIsResolvingPublishUris(state),
|
|
||||||
totalRewardValue: selectUnclaimedRewardValue(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const perform = dispatch => ({
|
|
||||||
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
|
||||||
clearPublish: () => dispatch(doClearPublish()),
|
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
|
||||||
publish: filePath => dispatch(doPublishDesktop(filePath)),
|
|
||||||
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
|
||||||
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, perform)(ChannelForm);
|
|
|
@ -1,63 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { useEffect, Fragment } from 'react';
|
|
||||||
import { CHANNEL_NEW } from 'constants/claim';
|
|
||||||
import { buildURI, isURIValid } from 'lbry-redux';
|
|
||||||
import ChannelCreate from 'component/channelCreate';
|
|
||||||
import Card from 'component/common/card';
|
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
name: ?string,
|
|
||||||
channel: string,
|
|
||||||
resolveUri: string => void,
|
|
||||||
updatePublishForm: any => void,
|
|
||||||
onSuccess: () => void,
|
|
||||||
};
|
|
||||||
|
|
||||||
function ChannelForm(props: Props) {
|
|
||||||
const { name, channel, resolveUri, updatePublishForm, onSuccess } = props;
|
|
||||||
|
|
||||||
// Every time the channel or name changes, resolve the uris to find winning bid amounts
|
|
||||||
useEffect(() => {
|
|
||||||
// If they are midway through a channel creation, treat it as anonymous until it completes
|
|
||||||
const channelName = channel === CHANNEL_NEW ? '' : channel;
|
|
||||||
|
|
||||||
// We are only going to store the full uri, but we need to resolve the uri with and without the channel name
|
|
||||||
let uri;
|
|
||||||
try {
|
|
||||||
uri = name && buildURI({ streamName: name, channelName });
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
if (channelName && name) {
|
|
||||||
// resolve without the channel name so we know the winning bid for it
|
|
||||||
try {
|
|
||||||
const uriLessChannel = buildURI({ streamName: name });
|
|
||||||
resolveUri(uriLessChannel);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValid = isURIValid(uri);
|
|
||||||
if (uri && isValid) {
|
|
||||||
resolveUri(uri);
|
|
||||||
updatePublishForm({ uri });
|
|
||||||
}
|
|
||||||
}, [name, channel, resolveUri, updatePublishForm]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Card
|
|
||||||
icon={ICONS.CHANNEL}
|
|
||||||
title="Create a New Channel"
|
|
||||||
subtitle="This is a username or handle that your content can be found under."
|
|
||||||
actions={
|
|
||||||
<React.Fragment>
|
|
||||||
<ChannelCreate onSuccess={onSuccess} onChannelChange={channel => updatePublishForm({ channel })} />
|
|
||||||
</React.Fragment>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChannelForm;
|
|
12
ui/component/channelMuteButton/index.js
Normal file
12
ui/component/channelMuteButton/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doToggleMuteChannel } from 'redux/actions/blocked';
|
||||||
|
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||||
|
import ChannelMuteButton from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
isMuted: makeSelectChannelIsMuted(props.uri)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, {
|
||||||
|
doToggleMuteChannel,
|
||||||
|
})(ChannelMuteButton);
|
24
ui/component/channelMuteButton/view.jsx
Normal file
24
ui/component/channelMuteButton/view.jsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: string,
|
||||||
|
isMuted: boolean,
|
||||||
|
channelClaim: ?ChannelClaim,
|
||||||
|
doToggleMuteChannel: (string) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ChannelBlockButton(props: Props) {
|
||||||
|
const { uri, doToggleMuteChannel, isMuted } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
button={isMuted ? 'alt' : 'secondary'}
|
||||||
|
label={isMuted ? __('Unmute') : __('Mute')}
|
||||||
|
onClick={() => doToggleMuteChannel(uri)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChannelBlockButton;
|
|
@ -11,6 +11,7 @@ import CreditAmount from 'component/common/credit-amount';
|
||||||
type Props = {
|
type Props = {
|
||||||
channelClaim: ChannelClaim,
|
channelClaim: ChannelClaim,
|
||||||
large?: boolean,
|
large?: boolean,
|
||||||
|
inline?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getChannelLevel(amount: number): number {
|
function getChannelLevel(amount: number): number {
|
||||||
|
@ -47,7 +48,7 @@ function getChannelIcon(level: number): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChannelStakedIndicator(props: Props) {
|
function ChannelStakedIndicator(props: Props) {
|
||||||
const { channelClaim, large = false } = props;
|
const { channelClaim, large = false, inline = false } = props;
|
||||||
|
|
||||||
if (!channelClaim || !channelClaim.meta) {
|
if (!channelClaim || !channelClaim.meta) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -79,6 +80,7 @@ function ChannelStakedIndicator(props: Props) {
|
||||||
<div
|
<div
|
||||||
className={classnames('channel-staked__wrapper', {
|
className={classnames('channel-staked__wrapper', {
|
||||||
'channel-staked__wrapper--large': large,
|
'channel-staked__wrapper--large': large,
|
||||||
|
'channel-staked__wrapper--inline': inline,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<LevelIcon icon={icon} large={large} isControlling={isControlling} />
|
<LevelIcon icon={icon} large={large} isControlling={isControlling} />
|
||||||
|
|
|
@ -32,12 +32,12 @@ type Props = {
|
||||||
showUnresolvedClaims?: boolean,
|
showUnresolvedClaims?: boolean,
|
||||||
renderProperties: ?(Claim) => Node,
|
renderProperties: ?(Claim) => Node,
|
||||||
includeSupportAction?: boolean,
|
includeSupportAction?: boolean,
|
||||||
hideBlock: boolean,
|
|
||||||
injectedItem: ?Node,
|
injectedItem: ?Node,
|
||||||
timedOutMessage?: Node,
|
timedOutMessage?: Node,
|
||||||
tileLayout?: boolean,
|
tileLayout?: boolean,
|
||||||
renderActions?: (Claim) => ?Node,
|
renderActions?: (Claim) => ?Node,
|
||||||
searchInLanguage: boolean,
|
searchInLanguage: boolean,
|
||||||
|
hideMenu?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ClaimList(props: Props) {
|
export default function ClaimList(props: Props) {
|
||||||
|
@ -57,12 +57,12 @@ export default function ClaimList(props: Props) {
|
||||||
showUnresolvedClaims,
|
showUnresolvedClaims,
|
||||||
renderProperties,
|
renderProperties,
|
||||||
includeSupportAction,
|
includeSupportAction,
|
||||||
hideBlock,
|
|
||||||
injectedItem,
|
injectedItem,
|
||||||
timedOutMessage,
|
timedOutMessage,
|
||||||
tileLayout = false,
|
tileLayout = false,
|
||||||
renderActions,
|
renderActions,
|
||||||
searchInLanguage,
|
searchInLanguage,
|
||||||
|
hideMenu,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||||
|
@ -100,7 +100,8 @@ export default function ClaimList(props: Props) {
|
||||||
|
|
||||||
return tileLayout && !header ? (
|
return tileLayout && !header ? (
|
||||||
<section className="claim-grid">
|
<section className="claim-grid">
|
||||||
{urisLength > 0 && uris.map((uri) => <ClaimPreviewTile key={uri} uri={uri} />)}
|
{urisLength > 0 &&
|
||||||
|
uris.map((uri) => <ClaimPreviewTile key={uri} uri={uri} showHiddenByUser={showHiddenByUser} />)}
|
||||||
{!timedOut && urisLength === 0 && !loading && <div className="empty main--empty">{empty || noResultMsg}</div>}
|
{!timedOut && urisLength === 0 && !loading && <div className="empty main--empty">{empty || noResultMsg}</div>}
|
||||||
{timedOut && timedOutMessage && <div className="empty main--empty">{timedOutMessage}</div>}
|
{timedOut && timedOutMessage && <div className="empty main--empty">{timedOutMessage}</div>}
|
||||||
</section>
|
</section>
|
||||||
|
@ -149,12 +150,13 @@ export default function ClaimList(props: Props) {
|
||||||
<ClaimPreview
|
<ClaimPreview
|
||||||
uri={uri}
|
uri={uri}
|
||||||
type={type}
|
type={type}
|
||||||
|
hideMenu={hideMenu}
|
||||||
includeSupportAction={includeSupportAction}
|
includeSupportAction={includeSupportAction}
|
||||||
showUnresolvedClaim={showUnresolvedClaims}
|
showUnresolvedClaim={showUnresolvedClaims}
|
||||||
properties={renderProperties || (type !== 'small' ? undefined : false)}
|
properties={renderProperties || (type !== 'small' ? undefined : false)}
|
||||||
renderActions={renderActions}
|
renderActions={renderActions}
|
||||||
showUserBlocked={showHiddenByUser}
|
showUserBlocked={showHiddenByUser}
|
||||||
hideBlock={hideBlock}
|
showHiddenByUser={showHiddenByUser}
|
||||||
customShouldHide={(claim: StreamClaim) => {
|
customShouldHide={(claim: StreamClaim) => {
|
||||||
// Hack to hide spee.ch thumbnail publishes
|
// Hack to hide spee.ch thumbnail publishes
|
||||||
// If it meets these requirements, it was probably uploaded here:
|
// If it meets these requirements, it was probably uploaded here:
|
||||||
|
|
|
@ -7,12 +7,13 @@ import {
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectFollowedTags } from 'redux/selectors/tags';
|
import { selectFollowedTags } from 'redux/selectors/tags';
|
||||||
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||||
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
||||||
|
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||||
import ClaimListDiscover from './view';
|
import ClaimListDiscover from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state) => ({
|
||||||
followedTags: selectFollowedTags(state),
|
followedTags: selectFollowedTags(state),
|
||||||
claimSearchByQuery: selectClaimSearchByQuery(state),
|
claimSearchByQuery: selectClaimSearchByQuery(state),
|
||||||
claimSearchByQueryLastPageReached: selectClaimSearchByQueryLastPageReached(state),
|
claimSearchByQueryLastPageReached: selectClaimSearchByQueryLastPageReached(state),
|
||||||
|
@ -20,7 +21,8 @@ const select = state => ({
|
||||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
||||||
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
|
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
|
||||||
languageSetting: selectLanguage(state),
|
languageSetting: selectLanguage(state),
|
||||||
hiddenUris: selectBlockedChannels(state),
|
mutedUris: selectMutedChannels(state),
|
||||||
|
blockedUris: selectModerationBlockList(state),
|
||||||
searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state),
|
searchInLanguage: makeSelectClientSetting(SETTINGS.SEARCH_IN_LANGUAGE)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,17 +20,18 @@ type Props = {
|
||||||
doClaimSearch: ({}) => void,
|
doClaimSearch: ({}) => void,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
personalView: boolean,
|
personalView: boolean,
|
||||||
doToggleTagFollowDesktop: string => void,
|
doToggleTagFollowDesktop: (string) => void,
|
||||||
meta?: Node,
|
meta?: Node,
|
||||||
showNsfw: boolean,
|
showNsfw: boolean,
|
||||||
hideReposts: boolean,
|
hideReposts: boolean,
|
||||||
history: { action: string, push: string => void, replace: string => void },
|
history: { action: string, push: (string) => void, replace: (string) => void },
|
||||||
location: { search: string, pathname: string },
|
location: { search: string, pathname: string },
|
||||||
claimSearchByQuery: {
|
claimSearchByQuery: {
|
||||||
[string]: Array<string>,
|
[string]: Array<string>,
|
||||||
},
|
},
|
||||||
claimSearchByQueryLastPageReached: { [string]: boolean },
|
claimSearchByQueryLastPageReached: { [string]: boolean },
|
||||||
hiddenUris: Array<string>,
|
mutedUris: Array<string>,
|
||||||
|
blockedUris: Array<string>,
|
||||||
hiddenNsfwMessage?: Node,
|
hiddenNsfwMessage?: Node,
|
||||||
channelIds?: Array<string>,
|
channelIds?: Array<string>,
|
||||||
claimIds?: Array<string>,
|
claimIds?: Array<string>,
|
||||||
|
@ -43,13 +44,12 @@ type Props = {
|
||||||
header?: Node,
|
header?: Node,
|
||||||
headerLabel?: string | Node,
|
headerLabel?: string | Node,
|
||||||
name?: string,
|
name?: string,
|
||||||
hideBlock?: boolean,
|
|
||||||
hideAdvancedFilter?: boolean,
|
hideAdvancedFilter?: boolean,
|
||||||
claimType?: Array<string>,
|
claimType?: Array<string>,
|
||||||
defaultClaimType?: Array<string>,
|
defaultClaimType?: Array<string>,
|
||||||
streamType?: string | Array<string>,
|
streamType?: string | Array<string>,
|
||||||
defaultStreamType?: string | Array<string>,
|
defaultStreamType?: string | Array<string>,
|
||||||
renderProperties?: Claim => Node,
|
renderProperties?: (Claim) => Node,
|
||||||
includeSupportAction?: boolean,
|
includeSupportAction?: boolean,
|
||||||
repostedClaimId?: string,
|
repostedClaimId?: string,
|
||||||
pageSize?: number,
|
pageSize?: number,
|
||||||
|
@ -64,6 +64,7 @@ type Props = {
|
||||||
languageSetting: string,
|
languageSetting: string,
|
||||||
searchInLanguage: boolean,
|
searchInLanguage: boolean,
|
||||||
scrollAnchor?: string,
|
scrollAnchor?: string,
|
||||||
|
showHiddenByUser?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ClaimListDiscover(props: Props) {
|
function ClaimListDiscover(props: Props) {
|
||||||
|
@ -80,7 +81,8 @@ function ClaimListDiscover(props: Props) {
|
||||||
hideReposts,
|
hideReposts,
|
||||||
history,
|
history,
|
||||||
location,
|
location,
|
||||||
hiddenUris,
|
mutedUris,
|
||||||
|
blockedUris,
|
||||||
hiddenNsfwMessage,
|
hiddenNsfwMessage,
|
||||||
defaultOrderBy,
|
defaultOrderBy,
|
||||||
orderBy,
|
orderBy,
|
||||||
|
@ -89,7 +91,6 @@ function ClaimListDiscover(props: Props) {
|
||||||
name,
|
name,
|
||||||
claimType,
|
claimType,
|
||||||
pageSize,
|
pageSize,
|
||||||
hideBlock,
|
|
||||||
defaultClaimType,
|
defaultClaimType,
|
||||||
streamType,
|
streamType,
|
||||||
defaultStreamType,
|
defaultStreamType,
|
||||||
|
@ -112,6 +113,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
languageSetting,
|
languageSetting,
|
||||||
searchInLanguage,
|
searchInLanguage,
|
||||||
scrollAnchor,
|
scrollAnchor,
|
||||||
|
showHiddenByUser = false,
|
||||||
} = props;
|
} = props;
|
||||||
const didNavigateForward = history.action === 'PUSH';
|
const didNavigateForward = history.action === 'PUSH';
|
||||||
const { search } = location;
|
const { search } = location;
|
||||||
|
@ -120,13 +122,14 @@ function ClaimListDiscover(props: Props) {
|
||||||
const isLargeScreen = useIsLargeScreen();
|
const isLargeScreen = useIsLargeScreen();
|
||||||
const [orderParamEntry, setOrderParamEntry] = usePersistedState(`entry-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
const [orderParamEntry, setOrderParamEntry] = usePersistedState(`entry-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
||||||
const [orderParamUser, setOrderParamUser] = usePersistedState(`orderUser-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
const [orderParamUser, setOrderParamUser] = usePersistedState(`orderUser-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
||||||
const followed = (followedTags && followedTags.map(t => t.name)) || [];
|
const followed = (followedTags && followedTags.map((t) => t.name)) || [];
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const tagsParam = // can be 'x,y,z' or 'x' or ['x','y'] or CS.CONSTANT
|
const tagsParam = // can be 'x,y,z' or 'x' or ['x','y'] or CS.CONSTANT
|
||||||
(tags && getParamFromTags(tags)) ||
|
(tags && getParamFromTags(tags)) ||
|
||||||
(urlParams.get(CS.TAGS_KEY) !== null && urlParams.get(CS.TAGS_KEY)) ||
|
(urlParams.get(CS.TAGS_KEY) !== null && urlParams.get(CS.TAGS_KEY)) ||
|
||||||
(defaultTags && getParamFromTags(defaultTags));
|
(defaultTags && getParamFromTags(defaultTags));
|
||||||
const freshnessParam = freshness || urlParams.get(CS.FRESH_KEY) || defaultFreshness;
|
const freshnessParam = freshness || urlParams.get(CS.FRESH_KEY) || defaultFreshness;
|
||||||
|
const mutedAndBlockedChannelIds = Array.from(new Set(mutedUris.concat(blockedUris).map((uri) => uri.split('#')[1])));
|
||||||
|
|
||||||
const langParam = urlParams.get(CS.LANGUAGE_KEY) || null;
|
const langParam = urlParams.get(CS.LANGUAGE_KEY) || null;
|
||||||
const languageParams = searchInLanguage
|
const languageParams = searchInLanguage
|
||||||
|
@ -206,7 +209,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
no_totals: true,
|
no_totals: true,
|
||||||
not_channel_ids:
|
not_channel_ids:
|
||||||
// If channelIdsParam were passed in, we don't need not_channel_ids
|
// If channelIdsParam were passed in, we don't need not_channel_ids
|
||||||
!channelIdsParam && hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : [],
|
!channelIdsParam ? mutedAndBlockedChannelIds : [],
|
||||||
not_tags: !showNsfw ? MATURE_TAGS : [],
|
not_tags: !showNsfw ? MATURE_TAGS : [],
|
||||||
order_by:
|
order_by:
|
||||||
orderParam === CS.ORDER_BY_TRENDING
|
orderParam === CS.ORDER_BY_TRENDING
|
||||||
|
@ -247,12 +250,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
|
|
||||||
if (claimType !== CS.CLAIM_CHANNEL) {
|
if (claimType !== CS.CLAIM_CHANNEL) {
|
||||||
if (orderParam === CS.ORDER_BY_TOP && freshnessParam !== CS.FRESH_ALL) {
|
if (orderParam === CS.ORDER_BY_TOP && freshnessParam !== CS.FRESH_ALL) {
|
||||||
options.release_time = `>${Math.floor(
|
options.release_time = `>${Math.floor(moment().subtract(1, freshnessParam).startOf('hour').unix())}`;
|
||||||
moment()
|
|
||||||
.subtract(1, freshnessParam)
|
|
||||||
.startOf('hour')
|
|
||||||
.unix()
|
|
||||||
)}`;
|
|
||||||
} else if (orderParam === CS.ORDER_BY_NEW || orderParam === CS.ORDER_BY_TRENDING) {
|
} else if (orderParam === CS.ORDER_BY_NEW || orderParam === CS.ORDER_BY_TRENDING) {
|
||||||
// Warning - hack below
|
// Warning - hack below
|
||||||
// If users are following more than 10 channels or tags, limit results to stuff less than a year old
|
// If users are following more than 10 channels or tags, limit results to stuff less than a year old
|
||||||
|
@ -263,29 +261,15 @@ function ClaimListDiscover(props: Props) {
|
||||||
(options.channel_ids && options.channel_ids.length > 20) ||
|
(options.channel_ids && options.channel_ids.length > 20) ||
|
||||||
(options.any_tags && options.any_tags.length > 20)
|
(options.any_tags && options.any_tags.length > 20)
|
||||||
) {
|
) {
|
||||||
options.release_time = `>${Math.floor(
|
options.release_time = `>${Math.floor(moment().subtract(3, CS.FRESH_MONTH).startOf('week').unix())}`;
|
||||||
moment()
|
|
||||||
.subtract(3, CS.FRESH_MONTH)
|
|
||||||
.startOf('week')
|
|
||||||
.unix()
|
|
||||||
)}`;
|
|
||||||
} else if (
|
} else if (
|
||||||
(options.channel_ids && options.channel_ids.length > 10) ||
|
(options.channel_ids && options.channel_ids.length > 10) ||
|
||||||
(options.any_tags && options.any_tags.length > 10)
|
(options.any_tags && options.any_tags.length > 10)
|
||||||
) {
|
) {
|
||||||
options.release_time = `>${Math.floor(
|
options.release_time = `>${Math.floor(moment().subtract(1, CS.FRESH_YEAR).startOf('week').unix())}`;
|
||||||
moment()
|
|
||||||
.subtract(1, CS.FRESH_YEAR)
|
|
||||||
.startOf('week')
|
|
||||||
.unix()
|
|
||||||
)}`;
|
|
||||||
} else {
|
} else {
|
||||||
// Hack for at least the New page until https://github.com/lbryio/lbry-sdk/issues/2591 is fixed
|
// Hack for at least the New page until https://github.com/lbryio/lbry-sdk/issues/2591 is fixed
|
||||||
options.release_time = `<${Math.floor(
|
options.release_time = `<${Math.floor(moment().startOf('minute').unix())}`;
|
||||||
moment()
|
|
||||||
.startOf('minute')
|
|
||||||
.unix()
|
|
||||||
)}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,14 +317,14 @@ function ClaimListDiscover(props: Props) {
|
||||||
if (hideReposts && !options.reposted_claim_id && !forceShowReposts) {
|
if (hideReposts && !options.reposted_claim_id && !forceShowReposts) {
|
||||||
if (Array.isArray(options.claim_type)) {
|
if (Array.isArray(options.claim_type)) {
|
||||||
if (options.claim_type.length > 1) {
|
if (options.claim_type.length > 1) {
|
||||||
options.claim_type = options.claim_type.filter(claimType => claimType !== 'repost');
|
options.claim_type = options.claim_type.filter((claimType) => claimType !== 'repost');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
options.claim_type = ['stream', 'channel'];
|
options.claim_type = ['stream', 'channel'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasMatureTags = tagsParam && tagsParam.split(',').some(t => MATURE_TAGS.includes(t));
|
const hasMatureTags = tagsParam && tagsParam.split(',').some((t) => MATURE_TAGS.includes(t));
|
||||||
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
|
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
|
||||||
const claimSearchResult = claimSearchByQuery[claimSearchCacheQuery];
|
const claimSearchResult = claimSearchByQuery[claimSearchCacheQuery];
|
||||||
const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[claimSearchCacheQuery];
|
const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[claimSearchCacheQuery];
|
||||||
|
@ -499,8 +483,8 @@ function ClaimListDiscover(props: Props) {
|
||||||
timedOutMessage={timedOutMessage}
|
timedOutMessage={timedOutMessage}
|
||||||
renderProperties={renderProperties}
|
renderProperties={renderProperties}
|
||||||
includeSupportAction={includeSupportAction}
|
includeSupportAction={includeSupportAction}
|
||||||
hideBlock={hideBlock}
|
|
||||||
injectedItem={injectedItem}
|
injectedItem={injectedItem}
|
||||||
|
showHiddenByUser={showHiddenByUser}
|
||||||
/>
|
/>
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="claim-grid">
|
<div className="claim-grid">
|
||||||
|
@ -527,8 +511,8 @@ function ClaimListDiscover(props: Props) {
|
||||||
timedOutMessage={timedOutMessage}
|
timedOutMessage={timedOutMessage}
|
||||||
renderProperties={renderProperties}
|
renderProperties={renderProperties}
|
||||||
includeSupportAction={includeSupportAction}
|
includeSupportAction={includeSupportAction}
|
||||||
hideBlock={hideBlock}
|
|
||||||
injectedItem={injectedItem}
|
injectedItem={injectedItem}
|
||||||
|
showHiddenByUser={showHiddenByUser}
|
||||||
/>
|
/>
|
||||||
{loading && new Array(dynamicPageSize).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}
|
{loading && new Array(dynamicPageSize).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}
|
||||||
</div>
|
</div>
|
||||||
|
|
20
ui/component/claimMenuList/index.js
Normal file
20
ui/component/claimMenuList/index.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectClaimForUri, makeSelectClaimIsMine } from 'lbry-redux';
|
||||||
|
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||||
|
import { doToggleMuteChannel } from 'redux/actions/blocked';
|
||||||
|
import { doCommentModBlock, doCommentModUnBlock } from 'redux/actions/comments';
|
||||||
|
import { makeSelectChannelIsBlocked } from 'redux/selectors/comments';
|
||||||
|
import ClaimPreview from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
|
channelIsMuted: makeSelectChannelIsMuted(props.uri)(state),
|
||||||
|
channelIsBlocked: makeSelectChannelIsBlocked(props.uri)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, {
|
||||||
|
doToggleMuteChannel,
|
||||||
|
doCommentModBlock,
|
||||||
|
doCommentModUnBlock,
|
||||||
|
})(ClaimPreview);
|
86
ui/component/claimMenuList/view.jsx
Normal file
86
ui/component/claimMenuList/view.jsx
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// @flow
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { Menu, MenuButton, MenuList, MenuItem } from '@reach/menu-button';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
claim: ?Claim,
|
||||||
|
inline?: boolean,
|
||||||
|
claimIsMine: boolean,
|
||||||
|
channelIsMuted: boolean,
|
||||||
|
channelIsBlocked: boolean,
|
||||||
|
doToggleMuteChannel: (string) => void,
|
||||||
|
doCommentModBlock: (string) => void,
|
||||||
|
doCommentModUnBlock: (string) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ClaimMenuList(props: Props) {
|
||||||
|
const {
|
||||||
|
claim,
|
||||||
|
inline = false,
|
||||||
|
claimIsMine,
|
||||||
|
doToggleMuteChannel,
|
||||||
|
channelIsMuted,
|
||||||
|
channelIsBlocked,
|
||||||
|
doCommentModBlock,
|
||||||
|
doCommentModUnBlock,
|
||||||
|
} = props;
|
||||||
|
const channelUri =
|
||||||
|
claim &&
|
||||||
|
(claim.value_type === 'channel'
|
||||||
|
? claim.permanent_url
|
||||||
|
: claim.signing_channel && claim.signing_channel.permanent_url);
|
||||||
|
|
||||||
|
if (!channelUri || claimIsMine) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToggleMute() {
|
||||||
|
doToggleMuteChannel(channelUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToggleBlock() {
|
||||||
|
if (channelIsBlocked) {
|
||||||
|
doCommentModUnBlock(channelUri);
|
||||||
|
} else {
|
||||||
|
doCommentModBlock(channelUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuButton
|
||||||
|
className={classnames('menu__button', { 'claim__menu-button': !inline, 'claim__menu-button--inline': inline })}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon size={20} icon={ICONS.MORE_VERTICAL} />
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList className="menu__list">
|
||||||
|
{!claimIsMine && (
|
||||||
|
<>
|
||||||
|
<MenuItem className="comment__menu-option" onSelect={handleToggleBlock}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={ICONS.BLOCK} />
|
||||||
|
{channelIsBlocked ? __('Unblock Channel') : __('Block Channel')}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<MenuItem className="comment__menu-option" onSelect={handleToggleMute}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={ICONS.MUTE} />
|
||||||
|
{channelIsMuted ? __('Unmute Channel') : __('Mute Channel')}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClaimMenuList;
|
|
@ -11,11 +11,12 @@ import {
|
||||||
makeSelectClaimWasPurchased,
|
makeSelectClaimWasPurchased,
|
||||||
makeSelectStreamingUrlForUri,
|
makeSelectStreamingUrlForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectBlockedChannels, selectChannelIsBlocked } from 'redux/selectors/blocked';
|
import { selectMutedChannels, makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
||||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
|
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||||
import ClaimPreview from './view';
|
import ClaimPreview from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -30,17 +31,18 @@ const select = (state, props) => ({
|
||||||
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
|
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||||
filteredOutpoints: selectFilteredOutpoints(state),
|
filteredOutpoints: selectFilteredOutpoints(state),
|
||||||
blockedChannelUris: selectBlockedChannels(state),
|
mutedUris: selectMutedChannels(state),
|
||||||
|
blockedUris: selectModerationBlockList(state),
|
||||||
hasVisitedUri: props.uri && makeSelectHasVisitedUri(props.uri)(state),
|
hasVisitedUri: props.uri && makeSelectHasVisitedUri(props.uri)(state),
|
||||||
channelIsBlocked: props.uri && selectChannelIsBlocked(props.uri)(state),
|
channelIsBlocked: props.uri && makeSelectChannelIsMuted(props.uri)(state),
|
||||||
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state),
|
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state),
|
||||||
streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state),
|
streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state),
|
||||||
wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state),
|
wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = (dispatch) => ({
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
resolveUri: (uri) => dispatch(doResolveUri(uri)),
|
||||||
getFile: uri => dispatch(doFileGet(uri, false)),
|
getFile: (uri) => dispatch(doFileGet(uri, false)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(ClaimPreview);
|
export default connect(select, perform)(ClaimPreview);
|
||||||
|
|
|
@ -3,7 +3,6 @@ import type { Node } from 'react';
|
||||||
import React, { useEffect, forwardRef } from 'react';
|
import React, { useEffect, forwardRef } from 'react';
|
||||||
import { NavLink, withRouter } from 'react-router-dom';
|
import { NavLink, withRouter } from 'react-router-dom';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { SIMPLE_SITE } from 'config';
|
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
import { openClaimPreviewMenu } from 'util/context-menu';
|
import { openClaimPreviewMenu } from 'util/context-menu';
|
||||||
|
@ -16,7 +15,6 @@ import FileProperties from 'component/fileProperties';
|
||||||
import ClaimTags from 'component/claimTags';
|
import ClaimTags from 'component/claimTags';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
import BlockButton from 'component/blockButton';
|
|
||||||
import ClaimSupportButton from 'component/claimSupportButton';
|
import ClaimSupportButton from 'component/claimSupportButton';
|
||||||
import useGetThumbnail from 'effects/use-get-thumbnail';
|
import useGetThumbnail from 'effects/use-get-thumbnail';
|
||||||
import ClaimPreviewTitle from 'component/claimPreviewTitle';
|
import ClaimPreviewTitle from 'component/claimPreviewTitle';
|
||||||
|
@ -25,6 +23,7 @@ import ClaimRepostAuthor from 'component/claimRepostAuthor';
|
||||||
import FileDownloadLink from 'component/fileDownloadLink';
|
import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
|
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
|
||||||
import PublishPending from 'component/publishPending';
|
import PublishPending from 'component/publishPending';
|
||||||
|
import ClaimMenuList from 'component/claimMenuList';
|
||||||
import ClaimPreviewLoading from './claim-preview-loading';
|
import ClaimPreviewLoading from './claim-preview-loading';
|
||||||
import ClaimPreviewHidden from './claim-preview-no-mature';
|
import ClaimPreviewHidden from './claim-preview-no-mature';
|
||||||
import ClaimPreviewNoContent from './claim-preview-no-content';
|
import ClaimPreviewNoContent from './claim-preview-no-content';
|
||||||
|
@ -53,14 +52,13 @@ type Props = {
|
||||||
txid: string,
|
txid: string,
|
||||||
nout: number,
|
nout: number,
|
||||||
}>,
|
}>,
|
||||||
blockedChannelUris: Array<string>,
|
mutedUris: Array<string>,
|
||||||
|
blockedUris: Array<string>,
|
||||||
channelIsBlocked: boolean,
|
channelIsBlocked: boolean,
|
||||||
isSubscribed: boolean,
|
|
||||||
actions: boolean | Node | string | number,
|
actions: boolean | Node | string | number,
|
||||||
properties: boolean | Node | string | number | ((Claim) => Node),
|
properties: boolean | Node | string | number | ((Claim) => Node),
|
||||||
empty?: Node,
|
empty?: Node,
|
||||||
onClick?: (any) => any,
|
onClick?: (any) => any,
|
||||||
hideBlock?: boolean,
|
|
||||||
streamingUrl: ?string,
|
streamingUrl: ?string,
|
||||||
getFile: (string) => void,
|
getFile: (string) => void,
|
||||||
customShouldHide?: (Claim) => boolean,
|
customShouldHide?: (Claim) => boolean,
|
||||||
|
@ -72,6 +70,7 @@ type Props = {
|
||||||
wrapperElement?: string,
|
wrapperElement?: string,
|
||||||
hideRepostLabel?: boolean,
|
hideRepostLabel?: boolean,
|
||||||
repostUrl?: string,
|
repostUrl?: string,
|
||||||
|
hideMenu?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
|
@ -87,7 +86,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
// is the claim consider nsfw?
|
// is the claim consider nsfw?
|
||||||
nsfw,
|
nsfw,
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
isSubscribed,
|
|
||||||
streamingUrl,
|
streamingUrl,
|
||||||
// user properties
|
// user properties
|
||||||
channelIsBlocked,
|
channelIsBlocked,
|
||||||
|
@ -113,13 +111,14 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
hideActions = false,
|
hideActions = false,
|
||||||
properties,
|
properties,
|
||||||
onClick,
|
onClick,
|
||||||
hideBlock,
|
|
||||||
actions,
|
actions,
|
||||||
blockedChannelUris,
|
mutedUris,
|
||||||
|
blockedUris,
|
||||||
blackListedOutpoints,
|
blackListedOutpoints,
|
||||||
filteredOutpoints,
|
filteredOutpoints,
|
||||||
includeSupportAction,
|
includeSupportAction,
|
||||||
renderActions,
|
renderActions,
|
||||||
|
hideMenu = false,
|
||||||
// repostUrl,
|
// repostUrl,
|
||||||
} = props;
|
} = props;
|
||||||
const WrapperElement = wrapperElement || 'li';
|
const WrapperElement = wrapperElement || 'li';
|
||||||
|
@ -172,13 +171,11 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// block stream claims
|
// block stream claims
|
||||||
if (claim && !shouldHide && !showUserBlocked && blockedChannelUris.length && signingChannel) {
|
if (claim && !shouldHide && !showUserBlocked && mutedUris.length && signingChannel) {
|
||||||
shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === signingChannel.permanent_url);
|
shouldHide = mutedUris.some((blockedUri) => blockedUri === signingChannel.permanent_url);
|
||||||
}
|
}
|
||||||
// block channel claims if we can't control for them in claim search
|
if (claim && !shouldHide && !showUserBlocked && blockedUris.length && signingChannel) {
|
||||||
// e.g. fetchRecommendedSubscriptions
|
shouldHide = blockedUris.some((blockedUri) => blockedUri === signingChannel.permanent_url);
|
||||||
if (claim && isChannelUri && !shouldHide && !showUserBlocked && blockedChannelUris.length) {
|
|
||||||
shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === claim.permanent_url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldHide && customShouldHide && claim) {
|
if (!shouldHide && customShouldHide && claim) {
|
||||||
|
@ -275,7 +272,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
>
|
>
|
||||||
{isChannelUri && claim ? (
|
{isChannelUri && claim ? (
|
||||||
<UriIndicator uri={contentUri} link>
|
<UriIndicator uri={contentUri} link>
|
||||||
<ChannelThumbnail uri={contentUri} obscure={channelIsBlocked} />
|
<ChannelThumbnail uri={contentUri} />
|
||||||
</UriIndicator>
|
</UriIndicator>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -313,9 +310,9 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{type !== 'small' && !isChannelUri && signingChannel && SIMPLE_SITE && (
|
{/* {type !== 'small' && !isChannelUri && signingChannel && SIMPLE_SITE && (
|
||||||
<ChannelThumbnail uri={signingChannel.permanent_url} />
|
<ChannelThumbnail uri={signingChannel.permanent_url} />
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
<ClaimPreviewSubtitle uri={uri} type={type} />
|
<ClaimPreviewSubtitle uri={uri} type={type} />
|
||||||
{(pending || !!reflectingProgress) && <PublishPending uri={uri} />}
|
{(pending || !!reflectingProgress) && <PublishPending uri={uri} />}
|
||||||
|
@ -329,12 +326,16 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
actions
|
actions
|
||||||
) : (
|
) : (
|
||||||
<div className="claim-preview__primary-actions">
|
<div className="claim-preview__primary-actions">
|
||||||
|
{!isChannelUri && signingChannel && (
|
||||||
|
<div className="claim-preview__channel-staked">
|
||||||
|
<ChannelThumbnail uri={signingChannel.permanent_url} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{isChannelUri && !channelIsBlocked && !claimIsMine && (
|
{isChannelUri && !channelIsBlocked && !claimIsMine && (
|
||||||
<SubscribeButton uri={contentUri.startsWith('lbry://') ? contentUri : `lbry://${contentUri}`} />
|
<SubscribeButton uri={contentUri.startsWith('lbry://') ? contentUri : `lbry://${contentUri}`} />
|
||||||
)}
|
)}
|
||||||
{!hideBlock && isChannelUri && !isSubscribed && (!claimIsMine || channelIsBlocked) && (
|
|
||||||
<BlockButton uri={contentUri.startsWith('lbry://') ? contentUri : `lbry://${contentUri}`} />
|
|
||||||
)}
|
|
||||||
{includeSupportAction && <ClaimSupportButton uri={uri} />}
|
{includeSupportAction && <ClaimSupportButton uri={uri} />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -355,6 +356,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{!hideMenu && <ClaimMenuList uri={uri} />}
|
||||||
</WrapperElement>
|
</WrapperElement>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
makeSelectChannelForClaimUri,
|
makeSelectChannelForClaimUri,
|
||||||
makeSelectClaimIsNsfw,
|
makeSelectClaimIsNsfw,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import ClaimPreviewTile from './view';
|
import ClaimPreviewTile from './view';
|
||||||
|
@ -22,14 +22,14 @@ const select = (state, props) => ({
|
||||||
title: props.uri && makeSelectTitleForUri(props.uri)(state),
|
title: props.uri && makeSelectTitleForUri(props.uri)(state),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||||
filteredOutpoints: selectFilteredOutpoints(state),
|
filteredOutpoints: selectFilteredOutpoints(state),
|
||||||
blockedChannelUris: selectBlockedChannels(state),
|
blockedChannelUris: selectMutedChannels(state),
|
||||||
showMature: selectShowMatureContent(state),
|
showMature: selectShowMatureContent(state),
|
||||||
isMature: makeSelectClaimIsNsfw(props.uri)(state),
|
isMature: makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = (dispatch) => ({
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
resolveUri: (uri) => dispatch(doResolveUri(uri)),
|
||||||
getFile: uri => dispatch(doFileGet(uri, false)),
|
getFile: (uri) => dispatch(doFileGet(uri, false)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(ClaimPreviewTile);
|
export default connect(select, perform)(ClaimPreviewTile);
|
||||||
|
|
|
@ -14,6 +14,8 @@ import { parseURI } from 'lbry-redux';
|
||||||
import FileProperties from 'component/fileProperties';
|
import FileProperties from 'component/fileProperties';
|
||||||
import FileDownloadLink from 'component/fileDownloadLink';
|
import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
import ClaimRepostAuthor from 'component/claimRepostAuthor';
|
import ClaimRepostAuthor from 'component/claimRepostAuthor';
|
||||||
|
import ClaimMenuList from 'component/claimMenuList';
|
||||||
|
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
import { openClaimPreviewMenu } from 'util/context-menu';
|
import { openClaimPreviewMenu } from 'util/context-menu';
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -41,6 +43,7 @@ type Props = {
|
||||||
streamingUrl: string,
|
streamingUrl: string,
|
||||||
isMature: boolean,
|
isMature: boolean,
|
||||||
showMature: boolean,
|
showMature: boolean,
|
||||||
|
showHiddenByUser?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ClaimPreviewTile(props: Props) {
|
function ClaimPreviewTile(props: Props) {
|
||||||
|
@ -60,6 +63,7 @@ function ClaimPreviewTile(props: Props) {
|
||||||
blockedChannelUris,
|
blockedChannelUris,
|
||||||
isMature,
|
isMature,
|
||||||
showMature,
|
showMature,
|
||||||
|
showHiddenByUser,
|
||||||
} = props;
|
} = props;
|
||||||
const isRepost = claim && claim.repost_channel_url;
|
const isRepost = claim && claim.repost_channel_url;
|
||||||
const shouldFetch = claim === undefined;
|
const shouldFetch = claim === undefined;
|
||||||
|
@ -128,12 +132,12 @@ function ClaimPreviewTile(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// block stream claims
|
// block stream claims
|
||||||
if (claim && !shouldHide && blockedChannelUris.length && signingChannel) {
|
if (claim && !shouldHide && !showHiddenByUser && blockedChannelUris.length && signingChannel) {
|
||||||
shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === signingChannel.permanent_url);
|
shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === signingChannel.permanent_url);
|
||||||
}
|
}
|
||||||
// block channel claims if we can't control for them in claim search
|
// block channel claims if we can't control for them in claim search
|
||||||
// e.g. fetchRecommendedSubscriptions
|
// e.g. fetchRecommendedSubscriptions
|
||||||
if (claim && isChannel && !shouldHide && blockedChannelUris.length) {
|
if (claim && isChannel && !shouldHide && !showHiddenByUser && blockedChannelUris.length) {
|
||||||
shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === claim.permanent_url);
|
shouldHide = blockedChannelUris.some((blockedUri) => blockedUri === claim.permanent_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +198,7 @@ function ClaimPreviewTile(props: Props) {
|
||||||
<UriIndicator uri={uri} link />
|
<UriIndicator uri={uri} link />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<ClaimMenuList uri={uri} />
|
||||||
</h2>
|
</h2>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doClaimSearch, selectClaimSearchByQuery, selectFetchingClaimSearchByQuery, SETTINGS } from 'lbry-redux';
|
import { doClaimSearch, selectClaimSearchByQuery, selectFetchingClaimSearchByQuery, SETTINGS } from 'lbry-redux';
|
||||||
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
|
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import ClaimListDiscover from './view';
|
import ClaimListDiscover from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state) => ({
|
||||||
claimSearchByQuery: selectClaimSearchByQuery(state),
|
claimSearchByQuery: selectClaimSearchByQuery(state),
|
||||||
fetchingClaimSearchByQuery: selectFetchingClaimSearchByQuery(state),
|
fetchingClaimSearchByQuery: selectFetchingClaimSearchByQuery(state),
|
||||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
||||||
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
|
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
|
||||||
hiddenUris: selectBlockedChannels(state),
|
mutedUris: selectMutedChannels(state),
|
||||||
|
blockedUris: selectModerationBlockList(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = {
|
const perform = {
|
||||||
|
|
|
@ -10,7 +10,7 @@ type Props = {
|
||||||
doClaimSearch: ({}) => void,
|
doClaimSearch: ({}) => void,
|
||||||
showNsfw: boolean,
|
showNsfw: boolean,
|
||||||
hideReposts: boolean,
|
hideReposts: boolean,
|
||||||
history: { action: string, push: string => void, replace: string => void },
|
history: { action: string, push: (string) => void, replace: (string) => void },
|
||||||
claimSearchByQuery: {
|
claimSearchByQuery: {
|
||||||
[string]: Array<string>,
|
[string]: Array<string>,
|
||||||
},
|
},
|
||||||
|
@ -19,10 +19,10 @@ type Props = {
|
||||||
},
|
},
|
||||||
// claim search options are below
|
// claim search options are below
|
||||||
tags: Array<string>,
|
tags: Array<string>,
|
||||||
hiddenUris: Array<string>,
|
blockedUris: Array<string>,
|
||||||
|
mutedUris: Array<string>,
|
||||||
claimIds?: Array<string>,
|
claimIds?: Array<string>,
|
||||||
channelIds?: Array<string>,
|
channelIds?: Array<string>,
|
||||||
notChannelIds?: Array<string>,
|
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
orderBy?: Array<string>,
|
orderBy?: Array<string>,
|
||||||
releaseTime?: string,
|
releaseTime?: string,
|
||||||
|
@ -39,12 +39,12 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
claimSearchByQuery,
|
claimSearchByQuery,
|
||||||
showNsfw,
|
showNsfw,
|
||||||
hideReposts,
|
hideReposts,
|
||||||
hiddenUris,
|
blockedUris,
|
||||||
|
mutedUris,
|
||||||
// Below are options to pass that are forwarded to claim_search
|
// Below are options to pass that are forwarded to claim_search
|
||||||
tags,
|
tags,
|
||||||
channelIds,
|
channelIds,
|
||||||
claimIds,
|
claimIds,
|
||||||
notChannelIds,
|
|
||||||
orderBy,
|
orderBy,
|
||||||
pageSize = 8,
|
pageSize = 8,
|
||||||
releaseTime,
|
releaseTime,
|
||||||
|
@ -60,6 +60,7 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
const urlParams = new URLSearchParams(location.search);
|
const urlParams = new URLSearchParams(location.search);
|
||||||
const feeAmountInUrl = urlParams.get('fee_amount');
|
const feeAmountInUrl = urlParams.get('fee_amount');
|
||||||
const feeAmountParam = feeAmountInUrl || feeAmount;
|
const feeAmountParam = feeAmountInUrl || feeAmount;
|
||||||
|
const mutedAndBlockedChannelIds = Array.from(new Set(mutedUris.concat(blockedUris).map((uri) => uri.split('#')[1])));
|
||||||
const options: {
|
const options: {
|
||||||
page_size: number,
|
page_size: number,
|
||||||
no_totals: boolean,
|
no_totals: boolean,
|
||||||
|
@ -86,10 +87,7 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
not_tags: !showNsfw ? MATURE_TAGS : [],
|
not_tags: !showNsfw ? MATURE_TAGS : [],
|
||||||
any_languages: languages,
|
any_languages: languages,
|
||||||
channel_ids: channelIds || [],
|
channel_ids: channelIds || [],
|
||||||
not_channel_ids:
|
not_channel_ids: mutedAndBlockedChannelIds || [],
|
||||||
notChannelIds ||
|
|
||||||
// If channelIds were passed in, we don't need not_channel_ids
|
|
||||||
(!channelIds && hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : []),
|
|
||||||
order_by: orderBy || ['trending_group', 'trending_mixed'],
|
order_by: orderBy || ['trending_group', 'trending_mixed'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -108,7 +106,7 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
// https://github.com/lbryio/lbry-desktop/issues/3774
|
// https://github.com/lbryio/lbry-desktop/issues/3774
|
||||||
if (hideReposts) {
|
if (hideReposts) {
|
||||||
if (Array.isArray(options.claim_type)) {
|
if (Array.isArray(options.claim_type)) {
|
||||||
options.claim_type = options.claim_type.filter(claimType => claimType !== 'repost');
|
options.claim_type = options.claim_type.filter((claimType) => claimType !== 'repost');
|
||||||
} else {
|
} else {
|
||||||
options.claim_type = ['stream', 'channel'];
|
options.claim_type = ['stream', 'channel'];
|
||||||
}
|
}
|
||||||
|
@ -143,7 +141,7 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
return (
|
return (
|
||||||
<ul className="claim-grid">
|
<ul className="claim-grid">
|
||||||
{uris && uris.length
|
{uris && uris.length
|
||||||
? uris.map(uri => <ClaimPreviewTile key={uri} uri={uri} />)
|
? uris.map((uri) => <ClaimPreviewTile key={uri} uri={uri} />)
|
||||||
: new Array(pageSize).fill(1).map((x, i) => <ClaimPreviewTile key={i} placeholder />)}
|
: new Array(pageSize).fill(1).map((x, i) => <ClaimPreviewTile key={i} placeholder />)}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectThumbnailForUri, selectMyChannelClaims } from 'lbry-redux';
|
import { makeSelectThumbnailForUri, selectMyChannelClaims } from 'lbry-redux';
|
||||||
import { doCommentUpdate } from 'redux/actions/comments';
|
import { doCommentUpdate } from 'redux/actions/comments';
|
||||||
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
@ -11,7 +11,7 @@ import Comment from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
||||||
channelIsBlocked: props.authorUri && selectChannelIsBlocked(props.authorUri)(state),
|
channelIsBlocked: props.authorUri && makeSelectChannelIsMuted(props.authorUri)(state),
|
||||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||||
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
|
|
|
@ -200,7 +200,7 @@ function Comment(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
<div className="comment__menu">
|
<div className="comment__menu">
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton>
|
<MenuButton className="menu__button">
|
||||||
<Icon
|
<Icon
|
||||||
size={18}
|
size={18}
|
||||||
className={mouseIsHovering ? 'comment__menu-icon--hovering' : 'comment__menu-icon'}
|
className={mouseIsHovering ? 'comment__menu-icon--hovering' : 'comment__menu-icon'}
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectChannelPermUrlForClaimUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
import { makeSelectChannelPermUrlForClaimUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||||
import {
|
import { doCommentAbandon, doCommentPin, doCommentList, doCommentModBlock } from 'redux/actions/comments';
|
||||||
doCommentAbandon,
|
import { doToggleMuteChannel } from 'redux/actions/blocked';
|
||||||
doCommentPin,
|
|
||||||
doCommentList,
|
|
||||||
// doCommentModBlock,
|
|
||||||
} from 'redux/actions/comments';
|
|
||||||
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
|
||||||
// import { doSetActiveChannel } from 'redux/actions/app';
|
// import { doSetActiveChannel } from 'redux/actions/app';
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
@ -19,14 +14,14 @@ const select = (state, props) => ({
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = (dispatch) => ({
|
||||||
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||||
deleteComment: (commentId, creatorChannelUrl) => dispatch(doCommentAbandon(commentId, creatorChannelUrl)),
|
deleteComment: (commentId, creatorChannelUrl) => dispatch(doCommentAbandon(commentId, creatorChannelUrl)),
|
||||||
blockChannel: channelUri => dispatch(doToggleBlockChannel(channelUri)),
|
blockChannel: (channelUri) => dispatch(doToggleMuteChannel(channelUri)),
|
||||||
pinComment: (commentId, remove) => dispatch(doCommentPin(commentId, remove)),
|
pinComment: (commentId, remove) => dispatch(doCommentPin(commentId, remove)),
|
||||||
fetchComments: uri => dispatch(doCommentList(uri)),
|
fetchComments: (uri) => dispatch(doCommentList(uri)),
|
||||||
// setActiveChannel: channelId => dispatch(doSetActiveChannel(channelId)),
|
// setActiveChannel: channelId => dispatch(doSetActiveChannel(channelId)),
|
||||||
// commentModBlock: commentAuthor => dispatch(doCommentModBlock(commentAuthor)),
|
commentModBlock: (commentAuthor) => dispatch(doCommentModBlock(commentAuthor)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(CommentMenuList);
|
export default connect(select, perform)(CommentMenuList);
|
||||||
|
|
|
@ -23,7 +23,7 @@ type Props = {
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
isTopLevel: boolean,
|
isTopLevel: boolean,
|
||||||
// commentModBlock: string => void,
|
commentModBlock: (string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function CommentMenuList(props: Props) {
|
function CommentMenuList(props: Props) {
|
||||||
|
@ -43,17 +43,10 @@ function CommentMenuList(props: Props) {
|
||||||
isPinned,
|
isPinned,
|
||||||
handleEditComment,
|
handleEditComment,
|
||||||
fetchComments,
|
fetchComments,
|
||||||
// commentModBlock,
|
commentModBlock,
|
||||||
// setActiveChannel,
|
|
||||||
} = props;
|
} = props;
|
||||||
const activeChannelIsCreator = activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl;
|
const activeChannelIsCreator = activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl;
|
||||||
|
|
||||||
// let authorChannel;
|
|
||||||
// try {
|
|
||||||
// const { claimName } = parseURI(authorUri);
|
|
||||||
// authorChannel = claimName;
|
|
||||||
// } catch (e) {}
|
|
||||||
|
|
||||||
function handlePinComment(commentId, remove) {
|
function handlePinComment(commentId, remove) {
|
||||||
pinComment(commentId, remove).then(() => fetchComments(uri));
|
pinComment(commentId, remove).then(() => fetchComments(uri));
|
||||||
}
|
}
|
||||||
|
@ -65,32 +58,16 @@ function CommentMenuList(props: Props) {
|
||||||
|
|
||||||
function handleCommentBlock() {
|
function handleCommentBlock() {
|
||||||
if (claimIsMine) {
|
if (claimIsMine) {
|
||||||
// Block them from commenting on future content
|
commentModBlock(authorUri);
|
||||||
// commentModBlock(authorUri);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCommentMute() {
|
||||||
blockChannel(authorUri);
|
blockChannel(authorUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
// function handleChooseChannel() {
|
|
||||||
// const { channelClaimId } = parseURI(authorUri);
|
|
||||||
// setActiveChannel(channelClaimId);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuList className="menu__list--comments">
|
<MenuList className="menu__list">
|
||||||
{/* {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 && <div className="comment__menu-title">{__('Creator tools')}</div>}
|
||||||
|
|
||||||
{activeChannelIsCreator && isTopLevel && (
|
{activeChannelIsCreator && isTopLevel && (
|
||||||
|
@ -123,16 +100,27 @@ function CommentMenuList(props: Props) {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Disabled until we deal with current app blocklist parity */}
|
|
||||||
{!commentIsMine && (
|
{!commentIsMine && (
|
||||||
<MenuItem className="comment__menu-option" onSelect={handleCommentBlock}>
|
<MenuItem className="comment__menu-option" onSelect={handleCommentBlock}>
|
||||||
<div className="menu__link">
|
<div className="menu__link">
|
||||||
<Icon aria-hidden icon={ICONS.BLOCK} />
|
<Icon aria-hidden icon={ICONS.BLOCK} />
|
||||||
{__('Block')}
|
{__('Block')}
|
||||||
</div>
|
</div>
|
||||||
{/* {activeChannelIsCreator && (
|
{activeChannelIsCreator && (
|
||||||
<span className="comment__menu-help">Hide this channel's comments and block them from commenting.</span>
|
<span className="comment__menu-help">{__('Prevent this channel from interacting with you.')}</span>
|
||||||
)} */}
|
)}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!commentIsMine && (
|
||||||
|
<MenuItem className="comment__menu-option" onSelect={handleCommentMute}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={ICONS.MUTE} />
|
||||||
|
{__('Mute')}
|
||||||
|
</div>
|
||||||
|
{activeChannelIsCreator && (
|
||||||
|
<span className="comment__menu-help">{__('Hide this channel for you only.')}</span>
|
||||||
|
)}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,13 @@ export const icons = {
|
||||||
<circle cx="12" cy="12" r="10" />
|
<circle cx="12" cy="12" r="10" />
|
||||||
</g>
|
</g>
|
||||||
),
|
),
|
||||||
|
[ICONS.MUTE]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
|
||||||
|
<line x1="23" y1="9" x2="17" y2="15" />
|
||||||
|
<line x1="17" y1="9" x2="23" y2="15" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
[ICONS.LIGHT]: buildIcon(
|
[ICONS.LIGHT]: buildIcon(
|
||||||
<g>
|
<g>
|
||||||
<circle cx="12" cy="12" r="5" />
|
<circle cx="12" cy="12" r="5" />
|
||||||
|
|
|
@ -10,7 +10,7 @@ function FileAuthor(props: Props) {
|
||||||
const { channelUri } = props;
|
const { channelUri } = props;
|
||||||
|
|
||||||
return channelUri ? (
|
return channelUri ? (
|
||||||
<ClaimPreview uri={channelUri} type="inline" properties={false} hideBlock />
|
<ClaimPreview uri={channelUri} type="inline" properties={false} />
|
||||||
) : (
|
) : (
|
||||||
<div className="claim-preview--inline claim-preview__title">{__('Anonymous')}</div>
|
<div className="claim-preview--inline claim-preview__title">{__('Anonymous')}</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/setting
|
||||||
import { selectHasNavigated, selectActiveChannelClaim } from 'redux/selectors/app';
|
import { selectHasNavigated, selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
import Header from './view';
|
import Header from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state) => ({
|
||||||
language: selectLanguage(state),
|
language: selectLanguage(state),
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
roundedSpendableBalance: formatCredits(selectBalance(state), 2, true),
|
roundedSpendableBalance: formatCredits(selectBalance(state), 2, true),
|
||||||
|
@ -27,10 +27,9 @@ const select = state => ({
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = (dispatch) => ({
|
||||||
setClientSetting: (key, value, push) => dispatch(doSetClientSetting(key, value, push)),
|
setClientSetting: (key, value, push) => dispatch(doSetClientSetting(key, value, push)),
|
||||||
signOut: () => dispatch(doSignOut()),
|
signOut: () => dispatch(doSignOut()),
|
||||||
openChannelCreate: () => dispatch(doOpenModal(MODALS.CREATE_CHANNEL)),
|
|
||||||
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
|
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
|
||||||
clearEmailEntry: () => dispatch(doClearEmailEntry()),
|
clearEmailEntry: () => dispatch(doClearEmailEntry()),
|
||||||
clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
|
clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
|
||||||
|
|
|
@ -33,8 +33,8 @@ type Props = {
|
||||||
index: number,
|
index: number,
|
||||||
length: number,
|
length: number,
|
||||||
location: { pathname: string },
|
location: { pathname: string },
|
||||||
push: string => void,
|
push: (string) => void,
|
||||||
replace: string => void,
|
replace: (string) => void,
|
||||||
},
|
},
|
||||||
currentTheme: string,
|
currentTheme: string,
|
||||||
automaticDarkModeEnabled: boolean,
|
automaticDarkModeEnabled: boolean,
|
||||||
|
@ -52,13 +52,12 @@ type Props = {
|
||||||
syncError: ?string,
|
syncError: ?string,
|
||||||
emailToVerify?: string,
|
emailToVerify?: string,
|
||||||
signOut: () => void,
|
signOut: () => void,
|
||||||
openChannelCreate: () => void,
|
|
||||||
openSignOutModal: () => void,
|
openSignOutModal: () => void,
|
||||||
clearEmailEntry: () => void,
|
clearEmailEntry: () => void,
|
||||||
clearPasswordEntry: () => void,
|
clearPasswordEntry: () => void,
|
||||||
hasNavigated: boolean,
|
hasNavigated: boolean,
|
||||||
sidebarOpen: boolean,
|
sidebarOpen: boolean,
|
||||||
setSidebarOpen: boolean => void,
|
setSidebarOpen: (boolean) => void,
|
||||||
isAbsoluteSideNavHidden: boolean,
|
isAbsoluteSideNavHidden: boolean,
|
||||||
hideCancel: boolean,
|
hideCancel: boolean,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
|
@ -181,7 +180,7 @@ const Header = (props: Props) => {
|
||||||
label={hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance}
|
label={hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance}
|
||||||
icon={ICONS.LBC}
|
icon={ICONS.LBC}
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
onDoubleClick={e => {
|
onDoubleClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -197,7 +196,7 @@ const Header = (props: Props) => {
|
||||||
// @endif
|
// @endif
|
||||||
})}
|
})}
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
onDoubleClick={e => {
|
onDoubleClick={(e) => {
|
||||||
remote.getCurrentWindow().maximize();
|
remote.getCurrentWindow().maximize();
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -250,7 +249,7 @@ const Header = (props: Props) => {
|
||||||
if (history.location.pathname === '/') window.location.reload();
|
if (history.location.pathname === '/') window.location.reload();
|
||||||
}}
|
}}
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
onDoubleClick={e => {
|
onDoubleClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -308,7 +307,7 @@ const Header = (props: Props) => {
|
||||||
icon={ICONS.REMOVE}
|
icon={ICONS.REMOVE}
|
||||||
{...closeButtonNavigationProps}
|
{...closeButtonNavigationProps}
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
onDoubleClick={e => {
|
onDoubleClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -326,8 +325,8 @@ const Header = (props: Props) => {
|
||||||
type HeaderMenuButtonProps = {
|
type HeaderMenuButtonProps = {
|
||||||
authenticated: boolean,
|
authenticated: boolean,
|
||||||
notificationsEnabled: boolean,
|
notificationsEnabled: boolean,
|
||||||
history: { push: string => void },
|
history: { push: (string) => void },
|
||||||
handleThemeToggle: string => void,
|
handleThemeToggle: (string) => void,
|
||||||
currentTheme: string,
|
currentTheme: string,
|
||||||
activeChannelUrl: ?string,
|
activeChannelUrl: ?string,
|
||||||
openSignOutModal: () => void,
|
openSignOutModal: () => void,
|
||||||
|
@ -357,7 +356,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
title={__('Publish a file, or create a channel')}
|
title={__('Publish a file, or create a channel')}
|
||||||
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
|
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
onDoubleClick={e => {
|
onDoubleClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -386,7 +385,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
title={__('Settings')}
|
title={__('Settings')}
|
||||||
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
|
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
onDoubleClick={e => {
|
onDoubleClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -419,7 +418,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
'header__navigation-item--profile-pic': activeChannelUrl,
|
'header__navigation-item--profile-pic': activeChannelUrl,
|
||||||
})}
|
})}
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
onDoubleClick={e => {
|
onDoubleClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @endif
|
||||||
|
|
|
@ -22,7 +22,7 @@ type Props = {
|
||||||
menuButton: boolean,
|
menuButton: boolean,
|
||||||
children: any,
|
children: any,
|
||||||
doReadNotifications: ([number]) => void,
|
doReadNotifications: ([number]) => void,
|
||||||
doDeleteNotification: number => void,
|
doDeleteNotification: (number) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Notification(props: Props) {
|
export default function Notification(props: Props) {
|
||||||
|
@ -166,10 +166,10 @@ export default function Notification(props: Props) {
|
||||||
|
|
||||||
<div className="notification__menu">
|
<div className="notification__menu">
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton onClick={e => e.stopPropagation()}>
|
<MenuButton onClick={(e) => e.stopPropagation()}>
|
||||||
<Icon size={18} icon={ICONS.MORE_VERTICAL} />
|
<Icon size={18} icon={ICONS.MORE_VERTICAL} />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList className="menu__list--comments">
|
<MenuList className="menu__list">
|
||||||
<MenuItem className="menu__link" onSelect={() => doDeleteNotification(id)}>
|
<MenuItem className="menu__link" onSelect={() => doDeleteNotification(id)}>
|
||||||
<Icon aria-hidden icon={ICONS.DELETE} />
|
<Icon aria-hidden icon={ICONS.DELETE} />
|
||||||
{__('Delete')}
|
{__('Delete')}
|
||||||
|
|
|
@ -272,7 +272,7 @@ function AppRouter(props: Props) {
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS_VERIFY}`} component={RewardsVerifyPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS_VERIFY}`} component={RewardsVerifyPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`} component={TagsFollowingManagePage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`} component={TagsFollowingManagePage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_BLOCKED_MUTED}`} component={ListBlockedPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.BUY}`} component={BuyPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.BUY}`} component={BuyPage} />
|
||||||
|
|
|
@ -268,6 +268,15 @@ export const COMMENT_REACT_FAILED = 'COMMENT_REACT_FAILED';
|
||||||
export const COMMENT_PIN_STARTED = 'COMMENT_PIN_STARTED';
|
export const COMMENT_PIN_STARTED = 'COMMENT_PIN_STARTED';
|
||||||
export const COMMENT_PIN_COMPLETED = 'COMMENT_PIN_COMPLETED';
|
export const COMMENT_PIN_COMPLETED = 'COMMENT_PIN_COMPLETED';
|
||||||
export const COMMENT_PIN_FAILED = 'COMMENT_PIN_FAILED';
|
export const COMMENT_PIN_FAILED = 'COMMENT_PIN_FAILED';
|
||||||
|
export const COMMENT_MODERATION_BLOCK_LIST_STARTED = 'COMMENT_MODERATION_BLOCK_LIST_STARTED';
|
||||||
|
export const COMMENT_MODERATION_BLOCK_LIST_COMPLETED = 'COMMENT_MODERATION_BLOCK_LIST_COMPLETED';
|
||||||
|
export const COMMENT_MODERATION_BLOCK_LIST_FAILED = 'COMMENT_MODERATION_BLOCK_LIST_FAILED';
|
||||||
|
export const COMMENT_MODERATION_BLOCK_STARTED = 'COMMENT_MODERATION_BLOCK_STARTED';
|
||||||
|
export const COMMENT_MODERATION_BLOCK_COMPLETE = 'COMMENT_MODERATION_BLOCK_COMPLETE';
|
||||||
|
export const COMMENT_MODERATION_BLOCK_FAILED = 'COMMENT_MODERATION_BLOCK_FAILED';
|
||||||
|
export const COMMENT_MODERATION_UN_BLOCK_STARTED = 'COMMENT_MODERATION_UN_BLOCK_STARTED';
|
||||||
|
export const COMMENT_MODERATION_UN_BLOCK_COMPLETE = 'COMMENT_MODERATION_UN_BLOCK_COMPLETE';
|
||||||
|
export const COMMENT_MODERATION_UN_BLOCK_FAILED = 'COMMENT_MODERATION_UN_BLOCK_FAILED';
|
||||||
|
|
||||||
// Blocked channels
|
// Blocked channels
|
||||||
export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
|
export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
|
||||||
|
|
|
@ -82,6 +82,7 @@ export const LIBRARY = 'Folder';
|
||||||
export const TAG = 'Tag';
|
export const TAG = 'Tag';
|
||||||
export const SUPPORT = 'TrendingUp';
|
export const SUPPORT = 'TrendingUp';
|
||||||
export const BLOCK = 'Slash';
|
export const BLOCK = 'Slash';
|
||||||
|
export const MUTE = 'VolumeX';
|
||||||
export const UNBLOCK = 'Circle';
|
export const UNBLOCK = 'Circle';
|
||||||
export const VIEW = 'View';
|
export const VIEW = 'View';
|
||||||
export const EYE = 'Eye';
|
export const EYE = 'Eye';
|
||||||
|
|
|
@ -41,7 +41,6 @@ export const LIQUIDATE_SUPPORTS = 'liquidate_supports';
|
||||||
export const MASS_TIP_UNLOCK = 'mass_tip_unlock';
|
export const MASS_TIP_UNLOCK = 'mass_tip_unlock';
|
||||||
export const CONFIRM_AGE = 'confirm_age';
|
export const CONFIRM_AGE = 'confirm_age';
|
||||||
export const SYNC_ENABLE = 'SYNC_ENABLE';
|
export const SYNC_ENABLE = 'SYNC_ENABLE';
|
||||||
export const REMOVE_BLOCKED = 'remove_blocked';
|
|
||||||
export const IMAGE_UPLOAD = 'image_upload';
|
export const IMAGE_UPLOAD = 'image_upload';
|
||||||
export const MOBILE_SEARCH = 'mobile_search';
|
export const MOBILE_SEARCH = 'mobile_search';
|
||||||
export const VIEW_IMAGE = 'view_image';
|
export const VIEW_IMAGE = 'view_image';
|
||||||
|
|
|
@ -24,6 +24,7 @@ exports.SEND = 'send';
|
||||||
exports.SETTINGS = 'settings';
|
exports.SETTINGS = 'settings';
|
||||||
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
||||||
exports.SETTINGS_ADVANCED = 'settings/advanced';
|
exports.SETTINGS_ADVANCED = 'settings/advanced';
|
||||||
|
exports.SETTINGS_BLOCKED_MUTED = 'settings/block_and_mute';
|
||||||
exports.SHOW = 'show';
|
exports.SHOW = 'show';
|
||||||
exports.ACCOUNT = 'account';
|
exports.ACCOUNT = 'account';
|
||||||
exports.SEARCH = 'search';
|
exports.SEARCH = 'search';
|
||||||
|
@ -36,7 +37,6 @@ exports.CHANNELS_FOLLOWING = 'following';
|
||||||
exports.DEPRECATED__CHANNELS_FOLLOWING_MANAGE = 'following/channels/manage';
|
exports.DEPRECATED__CHANNELS_FOLLOWING_MANAGE = 'following/channels/manage';
|
||||||
exports.CHANNELS_FOLLOWING_DISCOVER = 'following/discover';
|
exports.CHANNELS_FOLLOWING_DISCOVER = 'following/discover';
|
||||||
exports.WALLET = 'wallet';
|
exports.WALLET = 'wallet';
|
||||||
exports.BLOCKED = 'blocked';
|
|
||||||
exports.CHANNELS = 'channels';
|
exports.CHANNELS = 'channels';
|
||||||
exports.EMBED = 'embed';
|
exports.EMBED = 'embed';
|
||||||
exports.TOP = 'top';
|
exports.TOP = 'top';
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { doHideModal } from 'redux/actions/app';
|
|
||||||
import ChannelCreate from './view';
|
|
||||||
|
|
||||||
const select = state => ({});
|
|
||||||
|
|
||||||
const perform = {
|
|
||||||
doHideModal,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(ChannelCreate);
|
|
|
@ -1,18 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import ChannelForm from 'component/channelForm';
|
|
||||||
import { Modal } from 'modal/modal';
|
|
||||||
|
|
||||||
type Props = { doHideModal: () => void };
|
|
||||||
|
|
||||||
const ModalChannelCreate = (props: Props) => {
|
|
||||||
const { doHideModal } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal isOpen type="card" onAborted={doHideModal}>
|
|
||||||
<ChannelForm onSuccess={doHideModal} />
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ModalChannelCreate;
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { doHideModal } from 'redux/actions/app';
|
|
||||||
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
|
||||||
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
|
||||||
import ModalRemoveBlocked from './view';
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
blockedChannels: selectBlockedChannels(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const perform = dispatch => ({
|
|
||||||
closeModal: () => dispatch(doHideModal()),
|
|
||||||
toggleBlockChannel: uri => dispatch(doToggleBlockChannel(uri)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, perform)(ModalRemoveBlocked);
|
|
|
@ -1,46 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import { Modal } from 'modal/modal';
|
|
||||||
import I18nMessage from 'component/i18nMessage';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
blockedUri: string,
|
|
||||||
closeModal: () => void,
|
|
||||||
blockedChannels: Array<string>,
|
|
||||||
toggleBlockChannel: (uri: string) => void,
|
|
||||||
};
|
|
||||||
|
|
||||||
function ModalRemoveBlocked(props: Props) {
|
|
||||||
const { blockedUri, closeModal, blockedChannels, toggleBlockChannel } = props;
|
|
||||||
|
|
||||||
function handleConfirm() {
|
|
||||||
if (blockedUri && blockedChannels.includes(blockedUri)) {
|
|
||||||
// DANGER: Always ensure the uri is actually in the list since we are using a
|
|
||||||
// toggle function. If 'null' is accidentally toggled INTO the list, the app
|
|
||||||
// won't start. Ideally, we should add a "removeBlockedChannel()", but with
|
|
||||||
// the gating above, it should be safe/good enough.
|
|
||||||
toggleBlockChannel(blockedUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen
|
|
||||||
type="confirm"
|
|
||||||
title={__('Remove from blocked list')}
|
|
||||||
confirmButtonLabel={__('Remove')}
|
|
||||||
onConfirmed={handleConfirm}
|
|
||||||
onAborted={() => closeModal()}
|
|
||||||
>
|
|
||||||
<em>{blockedUri}</em>
|
|
||||||
<p />
|
|
||||||
<p>
|
|
||||||
<I18nMessage>Are you sure you want to remove this from the list?</I18nMessage>
|
|
||||||
</p>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ModalRemoveBlocked;
|
|
|
@ -16,7 +16,6 @@ import ModalRevokeClaim from 'modal/modalRevokeClaim';
|
||||||
import ModalPhoneCollection from 'modal/modalPhoneCollection';
|
import ModalPhoneCollection from 'modal/modalPhoneCollection';
|
||||||
import ModalFirstSubscription from 'modal/modalFirstSubscription';
|
import ModalFirstSubscription from 'modal/modalFirstSubscription';
|
||||||
import ModalConfirmTransaction from 'modal/modalConfirmTransaction';
|
import ModalConfirmTransaction from 'modal/modalConfirmTransaction';
|
||||||
import ModalRemoveBlocked from 'modal/modalRemoveBlocked';
|
|
||||||
import ModalSocialShare from 'modal/modalSocialShare';
|
import ModalSocialShare from 'modal/modalSocialShare';
|
||||||
import ModalSendTip from 'modal/modalSendTip';
|
import ModalSendTip from 'modal/modalSendTip';
|
||||||
import ModalPublish from 'modal/modalPublish';
|
import ModalPublish from 'modal/modalPublish';
|
||||||
|
@ -32,7 +31,6 @@ import ModalCommentAcknowledgement from 'modal/modalCommentAcknowledgement';
|
||||||
import ModalWalletSend from 'modal/modalWalletSend';
|
import ModalWalletSend from 'modal/modalWalletSend';
|
||||||
import ModalWalletReceive from 'modal/modalWalletReceive';
|
import ModalWalletReceive from 'modal/modalWalletReceive';
|
||||||
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
||||||
import ModalCreateChannel from 'modal/modalChannelCreate';
|
|
||||||
import ModalSetReferrer from 'modal/modalSetReferrer';
|
import ModalSetReferrer from 'modal/modalSetReferrer';
|
||||||
import ModalSignOut from 'modal/modalSignOut';
|
import ModalSignOut from 'modal/modalSignOut';
|
||||||
import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate';
|
import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate';
|
||||||
|
@ -130,8 +128,6 @@ function ModalRouter(props: Props) {
|
||||||
return <ModalWalletReceive {...modalProps} />;
|
return <ModalWalletReceive {...modalProps} />;
|
||||||
case MODALS.YOUTUBE_WELCOME:
|
case MODALS.YOUTUBE_WELCOME:
|
||||||
return <ModalYoutubeWelcome />;
|
return <ModalYoutubeWelcome />;
|
||||||
case MODALS.CREATE_CHANNEL:
|
|
||||||
return <ModalCreateChannel {...modalProps} />;
|
|
||||||
case MODALS.SET_REFERRER:
|
case MODALS.SET_REFERRER:
|
||||||
return <ModalSetReferrer {...modalProps} />;
|
return <ModalSetReferrer {...modalProps} />;
|
||||||
case MODALS.SIGN_OUT:
|
case MODALS.SIGN_OUT:
|
||||||
|
@ -142,8 +138,6 @@ function ModalRouter(props: Props) {
|
||||||
return <ModalFileSelection {...modalProps} />;
|
return <ModalFileSelection {...modalProps} />;
|
||||||
case MODALS.LIQUIDATE_SUPPORTS:
|
case MODALS.LIQUIDATE_SUPPORTS:
|
||||||
return <ModalSupportsLiquidate {...modalProps} />;
|
return <ModalSupportsLiquidate {...modalProps} />;
|
||||||
case MODALS.REMOVE_BLOCKED:
|
|
||||||
return <ModalRemoveBlocked {...modalProps} />;
|
|
||||||
case MODALS.IMAGE_UPLOAD:
|
case MODALS.IMAGE_UPLOAD:
|
||||||
return <ModalImageUpload {...modalProps} />;
|
return <ModalImageUpload {...modalProps} />;
|
||||||
case MODALS.SYNC_ENABLE:
|
case MODALS.SYNC_ENABLE:
|
||||||
|
|
|
@ -8,10 +8,11 @@ import {
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
makeSelectClaimIsPending,
|
makeSelectClaimIsPending,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||||
import { selectBlackListedOutpoints, doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
|
import { selectBlackListedOutpoints, doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
|
||||||
import { selectYoutubeChannels } from 'redux/selectors/user';
|
import { selectYoutubeChannels } from 'redux/selectors/user';
|
||||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
|
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import ChannelPage from './view';
|
import ChannelPage from './view';
|
||||||
|
|
||||||
|
@ -23,16 +24,17 @@ const select = (state, props) => ({
|
||||||
page: selectCurrentChannelPage(state),
|
page: selectCurrentChannelPage(state),
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
|
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
|
||||||
channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
|
channelIsBlocked: makeSelectChannelIsMuted(props.uri)(state),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||||
subCount: makeSelectSubCountForUri(props.uri)(state),
|
subCount: makeSelectSubCountForUri(props.uri)(state),
|
||||||
pending: makeSelectClaimIsPending(props.uri)(state),
|
pending: makeSelectClaimIsPending(props.uri)(state),
|
||||||
youtubeChannels: selectYoutubeChannels(state),
|
youtubeChannels: selectYoutubeChannels(state),
|
||||||
|
blockedChannels: selectModerationBlockList(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = (dispatch) => ({
|
||||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
fetchSubCount: claimId => dispatch(doFetchSubCount(claimId)),
|
fetchSubCount: (claimId) => dispatch(doFetchSubCount(claimId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(ChannelPage);
|
export default connect(select, perform)(ChannelPage);
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { parseURI } from 'lbry-redux';
|
||||||
import { YOUTUBE_STATUSES } from 'lbryinc';
|
import { YOUTUBE_STATUSES } from 'lbryinc';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
import BlockButton from 'component/blockButton';
|
|
||||||
import ShareButton from 'component/shareButton';
|
import ShareButton from 'component/shareButton';
|
||||||
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
|
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
@ -21,6 +20,8 @@ import classnames from 'classnames';
|
||||||
import HelpLink from 'component/common/help-link';
|
import HelpLink from 'component/common/help-link';
|
||||||
import ClaimSupportButton from 'component/claimSupportButton';
|
import ClaimSupportButton from 'component/claimSupportButton';
|
||||||
import ChannelStakedIndicator from 'component/channelStakedIndicator';
|
import ChannelStakedIndicator from 'component/channelStakedIndicator';
|
||||||
|
import ClaimMenuList from 'component/claimMenuList';
|
||||||
|
import Yrbl from 'component/yrbl';
|
||||||
|
|
||||||
export const PAGE_VIEW_QUERY = `view`;
|
export const PAGE_VIEW_QUERY = `view`;
|
||||||
const ABOUT_PAGE = `about`;
|
const ABOUT_PAGE = `about`;
|
||||||
|
@ -46,6 +47,7 @@ type Props = {
|
||||||
subCount: number,
|
subCount: number,
|
||||||
pending: boolean,
|
pending: boolean,
|
||||||
youtubeChannels: ?Array<{ channel_claim_id: string, sync_status: string, transfer_state: string }>,
|
youtubeChannels: ?Array<{ channel_claim_id: string, sync_status: string, transfer_state: string }>,
|
||||||
|
blockedChannels: Array<string>,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChannelPage(props: Props) {
|
function ChannelPage(props: Props) {
|
||||||
|
@ -63,12 +65,14 @@ function ChannelPage(props: Props) {
|
||||||
subCount,
|
subCount,
|
||||||
pending,
|
pending,
|
||||||
youtubeChannels,
|
youtubeChannels,
|
||||||
|
blockedChannels,
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
push,
|
push,
|
||||||
goBack,
|
goBack,
|
||||||
location: { search },
|
location: { search },
|
||||||
} = useHistory();
|
} = useHistory();
|
||||||
|
const [viewBlockedChannel, setViewBlockedChannel] = React.useState(false);
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
|
const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
|
||||||
const [discussionWasMounted, setDiscussionWasMounted] = React.useState(false);
|
const [discussionWasMounted, setDiscussionWasMounted] = React.useState(false);
|
||||||
|
@ -77,6 +81,7 @@ function ChannelPage(props: Props) {
|
||||||
const { permanent_url: permanentUrl } = claim;
|
const { permanent_url: permanentUrl } = claim;
|
||||||
const claimId = claim.claim_id;
|
const claimId = claim.claim_id;
|
||||||
const formattedSubCount = Number(subCount).toLocaleString();
|
const formattedSubCount = Number(subCount).toLocaleString();
|
||||||
|
const isBlocked = claim && blockedChannels.includes(claim.permanent_url);
|
||||||
const isMyYouTubeChannel =
|
const isMyYouTubeChannel =
|
||||||
claim &&
|
claim &&
|
||||||
youtubeChannels &&
|
youtubeChannels &&
|
||||||
|
@ -157,7 +162,7 @@ function ChannelPage(props: Props) {
|
||||||
{!channelIsBlocked && !channelIsBlackListed && <ShareButton uri={uri} />}
|
{!channelIsBlocked && !channelIsBlackListed && <ShareButton uri={uri} />}
|
||||||
{!channelIsBlocked && <ClaimSupportButton uri={uri} />}
|
{!channelIsBlocked && <ClaimSupportButton uri={uri} />}
|
||||||
{!channelIsBlocked && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />}
|
{!channelIsBlocked && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />}
|
||||||
{!isSubscribed && <BlockButton uri={permanentUrl} />}
|
<ClaimMenuList uri={claim.permanent_url} inline />
|
||||||
</div>
|
</div>
|
||||||
{cover && (
|
{cover && (
|
||||||
<img
|
<img
|
||||||
|
@ -203,24 +208,44 @@ function ChannelPage(props: Props) {
|
||||||
<div className="channel-cover__gradient" />
|
<div className="channel-cover__gradient" />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Tabs onChange={onTabChange} index={tabIndex}>
|
{isBlocked && !viewBlockedChannel ? (
|
||||||
<TabList className="tabs__list--channel-page">
|
<div className="main--empty">
|
||||||
<Tab disabled={editing}>{__('Content')}</Tab>
|
<Yrbl
|
||||||
<Tab>{editing ? __('Editing Your Channel') : __('About --[tab title in Channel Page]--')}</Tab>
|
title={__('This channel is blocked')}
|
||||||
<Tab disabled={editing}>{__('Community')}</Tab>
|
subtitle={__('Are you sure you want to view this content? Viewing will not unblock @%channel%', {
|
||||||
</TabList>
|
channel: channelName,
|
||||||
<TabPanels>
|
})}
|
||||||
<TabPanel>
|
actions={
|
||||||
<ChannelContent uri={uri} channelIsBlackListed={channelIsBlackListed} />
|
<div className="section__actions">
|
||||||
</TabPanel>
|
<Button button="primary" label={__('View Content')} onClick={() => setViewBlockedChannel(true)} />
|
||||||
<TabPanel>
|
</div>
|
||||||
<ChannelAbout uri={uri} />
|
}
|
||||||
</TabPanel>
|
/>
|
||||||
<TabPanel>
|
</div>
|
||||||
{(discussionWasMounted || currentView === DISCUSSION_PAGE) && <ChannelDiscussion uri={uri} />}
|
) : (
|
||||||
</TabPanel>
|
<Tabs onChange={onTabChange} index={tabIndex}>
|
||||||
</TabPanels>
|
<TabList className="tabs__list--channel-page">
|
||||||
</Tabs>
|
<Tab disabled={editing}>{__('Content')}</Tab>
|
||||||
|
<Tab>{editing ? __('Editing Your Channel') : __('About --[tab title in Channel Page]--')}</Tab>
|
||||||
|
<Tab disabled={editing}>{__('Community')}</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanels>
|
||||||
|
<TabPanel>
|
||||||
|
<ChannelContent
|
||||||
|
uri={uri}
|
||||||
|
channelIsBlackListed={channelIsBlackListed}
|
||||||
|
viewBlockedChannel={viewBlockedChannel}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<ChannelAbout uri={uri} />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
{(discussionWasMounted || currentView === DISCUSSION_PAGE) && <ChannelDiscussion uri={uri} />}
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
)}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
||||||
import { selectBalance } from 'lbry-redux';
|
import { selectBalance } from 'lbry-redux';
|
||||||
import ChannelNew from './view';
|
import ChannelNew from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state) => ({
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,12 @@ function ChannelNew(props: Props) {
|
||||||
<Page noSideNavigation noFooter backout={{ title: __('Create a channel'), backLabel: __('Cancel') }}>
|
<Page noSideNavigation noFooter backout={{ title: __('Create a channel'), backLabel: __('Cancel') }}>
|
||||||
{emptyBalance && <YrblWalletEmpty />}
|
{emptyBalance && <YrblWalletEmpty />}
|
||||||
|
|
||||||
<ChannelEdit disabled={emptyBalance} onDone={() => push(redirectUrl || `/$/${PAGES.CHANNELS}`)} />
|
<ChannelEdit
|
||||||
|
disabled={emptyBalance}
|
||||||
|
onDone={() => {
|
||||||
|
push(redirectUrl || `/$/${PAGES.CHANNELS}`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,14 @@ export default function ChannelsPage(props: Props) {
|
||||||
const { channels, channelUrls, fetchChannelListMine, fetchingChannels, youtubeChannels } = props;
|
const { channels, channelUrls, fetchChannelListMine, fetchingChannels, youtubeChannels } = props;
|
||||||
const [rewardData, setRewardData] = React.useState();
|
const [rewardData, setRewardData] = React.useState();
|
||||||
const hasYoutubeChannels = youtubeChannels && Boolean(youtubeChannels.length);
|
const hasYoutubeChannels = youtubeChannels && Boolean(youtubeChannels.length);
|
||||||
const hasPendingChannels = channels && channels.some(channel => channel.confirmations < 0);
|
const hasPendingChannels = channels && channels.some((channel) => channel.confirmations < 0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchChannelListMine();
|
fetchChannelListMine();
|
||||||
}, [fetchChannelListMine, hasPendingChannels]);
|
}, [fetchChannelListMine, hasPendingChannels]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Lbryio.call('user_rewards', 'view_rate').then(data => setRewardData(data));
|
Lbryio.call('user_rewards', 'view_rate').then((data) => setRewardData(data));
|
||||||
}, [setRewardData]);
|
}, [setRewardData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -52,7 +52,7 @@ export default function ChannelsPage(props: Props) {
|
||||||
}
|
}
|
||||||
loading={fetchingChannels}
|
loading={fetchingChannels}
|
||||||
uris={channelUrls}
|
uris={channelUrls}
|
||||||
renderActions={claim => {
|
renderActions={(claim) => {
|
||||||
const claimsInChannel = claim.meta.claims_in_channel;
|
const claimsInChannel = claim.meta.claims_in_channel;
|
||||||
return claimsInChannel === 0 ? (
|
return claimsInChannel === 0 ? (
|
||||||
<span />
|
<span />
|
||||||
|
@ -67,7 +67,7 @@ export default function ChannelsPage(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
renderProperties={claim => {
|
renderProperties={(claim) => {
|
||||||
const claimsInChannel = claim.meta.claims_in_channel;
|
const claimsInChannel = claim.meta.claims_in_channel;
|
||||||
if (!claim || claimsInChannel === 0) {
|
if (!claim || claimsInChannel === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -76,7 +76,7 @@ export default function ChannelsPage(props: Props) {
|
||||||
const channelRewardData =
|
const channelRewardData =
|
||||||
rewardData &&
|
rewardData &&
|
||||||
rewardData.rates &&
|
rewardData.rates &&
|
||||||
rewardData.rates.find(data => {
|
rewardData.rates.find((data) => {
|
||||||
return data.channel_claim_id === claim.claim_id;
|
return data.channel_claim_id === claim.claim_id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectFollowedTags } from 'redux/selectors/tags';
|
import { selectFollowedTags } from 'redux/selectors/tags';
|
||||||
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
import { selectHomepageData } from 'redux/selectors/settings';
|
import { selectHomepageData } from 'redux/selectors/settings';
|
||||||
import ChannelsFollowingManagePage from './view';
|
import ChannelsFollowingManagePage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state) => ({
|
||||||
followedTags: selectFollowedTags(state),
|
followedTags: selectFollowedTags(state),
|
||||||
subscribedChannels: selectSubscriptions(state),
|
subscribedChannels: selectSubscriptions(state),
|
||||||
blockedChannels: selectBlockedChannels(state),
|
blockedChannels: selectMutedChannels(state),
|
||||||
homepageData: selectHomepageData(state),
|
homepageData: selectHomepageData(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
|
import { selectModerationBlockList, selectFetchingModerationBlockList } from 'redux/selectors/comments';
|
||||||
import ListBlocked from './view';
|
import ListBlocked from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state) => ({
|
||||||
uris: selectBlockedChannels(state),
|
mutedUris: selectMutedChannels(state),
|
||||||
|
blockedUris: selectModerationBlockList(state),
|
||||||
|
fetchingModerationBlockList: selectFetchingModerationBlockList(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, null)(ListBlocked);
|
export default connect(select)(ListBlocked);
|
||||||
|
|
|
@ -1,34 +1,141 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import Card from 'component/common/card';
|
import Spinner from 'component/spinner';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import usePrevious from 'effects/use-previous';
|
||||||
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
|
import ChannelBlockButton from 'component/channelBlockButton';
|
||||||
|
import ChannelMuteButton from 'component/channelMuteButton';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uris: Array<string>,
|
mutedUris: ?Array<string>,
|
||||||
|
blockedUris: ?Array<string>,
|
||||||
|
fetchingModerationBlockList: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const VIEW_BLOCKED = 'blocked';
|
||||||
|
const VIEW_MUTED = 'muted';
|
||||||
|
|
||||||
function ListBlocked(props: Props) {
|
function ListBlocked(props: Props) {
|
||||||
const { uris } = props;
|
const { mutedUris, blockedUris, fetchingModerationBlockList } = props;
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = usePersistedState('blocked-muted:display', VIEW_BLOCKED);
|
||||||
|
const [loading, setLoading] = React.useState(!blockedUris || !blockedUris.length);
|
||||||
|
|
||||||
|
// Keep a local list to allow for undoing actions in this component
|
||||||
|
const [localBlockedList, setLocalBlockedList] = React.useState(undefined);
|
||||||
|
const [localMutedList, setLocalMutedList] = React.useState(undefined);
|
||||||
|
|
||||||
|
const previousFetchingModBlockList = usePrevious(fetchingModerationBlockList);
|
||||||
|
const hasLocalMuteList = localMutedList && localMutedList.length > 0;
|
||||||
|
const hasLocalBlockList = localBlockedList && localBlockedList.length > 0;
|
||||||
|
const stringifiedMutedChannels = JSON.stringify(mutedUris);
|
||||||
|
const justMuted = localMutedList && mutedUris && localMutedList.length < mutedUris.length;
|
||||||
|
const justBlocked = localBlockedList && blockedUris && localBlockedList.length < blockedUris.length;
|
||||||
|
const stringifiedBlockedChannels = JSON.stringify(blockedUris);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (previousFetchingModBlockList && !fetchingModerationBlockList) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [setLoading, previousFetchingModBlockList, fetchingModerationBlockList]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const jsonMutedChannels = stringifiedMutedChannels && JSON.parse(stringifiedMutedChannels);
|
||||||
|
if (!hasLocalMuteList && jsonMutedChannels && jsonMutedChannels.length > 0) {
|
||||||
|
setLocalMutedList(jsonMutedChannels);
|
||||||
|
}
|
||||||
|
}, [stringifiedMutedChannels, hasLocalMuteList]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const jsonBlockedChannels = stringifiedBlockedChannels && JSON.parse(stringifiedBlockedChannels);
|
||||||
|
if (!hasLocalBlockList && jsonBlockedChannels && jsonBlockedChannels.length > 0) {
|
||||||
|
setLocalBlockedList(jsonBlockedChannels);
|
||||||
|
}
|
||||||
|
}, [stringifiedBlockedChannels, hasLocalBlockList]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (justMuted && stringifiedMutedChannels) {
|
||||||
|
setLocalMutedList(JSON.parse(stringifiedMutedChannels));
|
||||||
|
}
|
||||||
|
}, [stringifiedMutedChannels, justMuted, setLocalMutedList]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (justBlocked && stringifiedBlockedChannels) {
|
||||||
|
setLocalBlockedList(JSON.parse(stringifiedBlockedChannels));
|
||||||
|
}
|
||||||
|
}, [stringifiedBlockedChannels, justBlocked, setLocalBlockedList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
{uris && uris.length ? (
|
{loading && (
|
||||||
<Card
|
|
||||||
isBodyList
|
|
||||||
title={__('Your blocked channels')}
|
|
||||||
body={<ClaimList uris={uris} showUnresolvedClaims showHiddenByUser />}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="main--empty">
|
<div className="main--empty">
|
||||||
<section className="card card--section">
|
<Spinner />
|
||||||
<h2 className="card__title card__title--deprecated">{__('You aren’t blocking any channels')}</h2>
|
|
||||||
<p className="section__subtitle">
|
|
||||||
{__('When you block a channel, all content from that channel will be hidden.')}
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!loading && (
|
||||||
|
<>
|
||||||
|
<div className="section__header--actions">
|
||||||
|
<div className="section__actions--inline">
|
||||||
|
<Button
|
||||||
|
icon={ICONS.BLOCK}
|
||||||
|
button="alt"
|
||||||
|
label={__('Blocked')}
|
||||||
|
className={classnames(`button-toggle`, {
|
||||||
|
'button-toggle--active': viewMode === VIEW_BLOCKED,
|
||||||
|
})}
|
||||||
|
onClick={() => setViewMode(VIEW_BLOCKED)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={ICONS.MUTE}
|
||||||
|
button="alt"
|
||||||
|
label={__('Muted')}
|
||||||
|
className={classnames(`button-toggle`, {
|
||||||
|
'button-toggle--active': viewMode === VIEW_MUTED,
|
||||||
|
})}
|
||||||
|
onClick={() => setViewMode(VIEW_MUTED)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="help--notice">
|
||||||
|
{viewMode === VIEW_MUTED
|
||||||
|
? __(
|
||||||
|
'Muted channels will be invisible to you in the app. They will not know they are muted and can still interact with you and your content.'
|
||||||
|
)
|
||||||
|
: __(
|
||||||
|
"Blocked channels will be invisible to you in the app. They will not be able to comment on your content, or reply to you comments left on other channels' content."
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ClaimList
|
||||||
|
uris={viewMode === VIEW_MUTED ? localMutedList : localBlockedList}
|
||||||
|
showUnresolvedClaims
|
||||||
|
showHiddenByUser
|
||||||
|
hideMenu
|
||||||
|
renderActions={(claim) => {
|
||||||
|
return (
|
||||||
|
<div className="section__actions">
|
||||||
|
{viewMode === VIEW_MUTED ? (
|
||||||
|
<>
|
||||||
|
<ChannelMuteButton uri={claim.permanent_url} />
|
||||||
|
<ChannelBlockButton uri={claim.permanent_url} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ChannelBlockButton uri={claim.permanent_url} />
|
||||||
|
<ChannelMuteButton uri={claim.permanent_url} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,10 @@ import {
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { makeSelectClientSetting, selectDaemonSettings, selectLanguage } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectDaemonSettings, selectLanguage } from 'redux/selectors/settings';
|
||||||
import { doWalletStatus, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
|
import { doWalletStatus, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
|
||||||
import { selectBlockedChannelsCount } from 'redux/selectors/blocked';
|
|
||||||
import SettingsPage from './view';
|
import SettingsPage from './view';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state) => ({
|
||||||
daemonSettings: selectDaemonSettings(state),
|
daemonSettings: selectDaemonSettings(state),
|
||||||
allowAnalytics: selectAllowAnalytics(state),
|
allowAnalytics: selectAllowAnalytics(state),
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
|
@ -27,7 +26,6 @@ const select = state => ({
|
||||||
autoplay: makeSelectClientSetting(SETTINGS.AUTOPLAY)(state),
|
autoplay: makeSelectClientSetting(SETTINGS.AUTOPLAY)(state),
|
||||||
walletEncrypted: selectWalletIsEncrypted(state),
|
walletEncrypted: selectWalletIsEncrypted(state),
|
||||||
autoDownload: makeSelectClientSetting(SETTINGS.AUTO_DOWNLOAD)(state),
|
autoDownload: makeSelectClientSetting(SETTINGS.AUTO_DOWNLOAD)(state),
|
||||||
userBlockedChannelsCount: selectBlockedChannelsCount(state),
|
|
||||||
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
|
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
|
||||||
floatingPlayer: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
|
floatingPlayer: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
|
||||||
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
|
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
|
||||||
|
@ -35,14 +33,14 @@ const select = state => ({
|
||||||
language: selectLanguage(state),
|
language: selectLanguage(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = (dispatch) => ({
|
||||||
setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
|
setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
|
||||||
clearDaemonSetting: key => dispatch(doClearDaemonSetting(key)),
|
clearDaemonSetting: (key) => dispatch(doClearDaemonSetting(key)),
|
||||||
toggle3PAnalytics: allow => dispatch(doToggle3PAnalytics(allow)),
|
toggle3PAnalytics: (allow) => dispatch(doToggle3PAnalytics(allow)),
|
||||||
clearCache: () => dispatch(doClearCache()),
|
clearCache: () => dispatch(doClearCache()),
|
||||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||||
updateWalletStatus: () => dispatch(doWalletStatus()),
|
updateWalletStatus: () => dispatch(doWalletStatus()),
|
||||||
confirmForgetPassword: modalProps => dispatch(doNotifyForgetPassword(modalProps)),
|
confirmForgetPassword: (modalProps) => dispatch(doNotifyForgetPassword(modalProps)),
|
||||||
clearPlayingUri: () => dispatch(doSetPlayingUri({ uri: null })),
|
clearPlayingUri: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||||
setDarkTime: (time, options) => dispatch(doSetDarkTime(time, options)),
|
setDarkTime: (time, options) => dispatch(doSetDarkTime(time, options)),
|
||||||
openModal: (id, params) => dispatch(doOpenModal(id, params)),
|
openModal: (id, params) => dispatch(doOpenModal(id, params)),
|
||||||
|
|
|
@ -44,9 +44,9 @@ type DaemonSettings = {
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
|
setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
|
||||||
clearDaemonSetting: string => void,
|
clearDaemonSetting: (string) => void,
|
||||||
setClientSetting: (string, SetDaemonSettingArg) => void,
|
setClientSetting: (string, SetDaemonSettingArg) => void,
|
||||||
toggle3PAnalytics: boolean => void,
|
toggle3PAnalytics: (boolean) => void,
|
||||||
clearCache: () => Promise<any>,
|
clearCache: () => Promise<any>,
|
||||||
daemonSettings: DaemonSettings,
|
daemonSettings: DaemonSettings,
|
||||||
allowAnalytics: boolean,
|
allowAnalytics: boolean,
|
||||||
|
@ -60,14 +60,13 @@ type Props = {
|
||||||
autoplay: boolean,
|
autoplay: boolean,
|
||||||
updateWalletStatus: () => void,
|
updateWalletStatus: () => void,
|
||||||
walletEncrypted: boolean,
|
walletEncrypted: boolean,
|
||||||
userBlockedChannelsCount?: number,
|
|
||||||
confirmForgetPassword: ({}) => void,
|
confirmForgetPassword: ({}) => void,
|
||||||
floatingPlayer: boolean,
|
floatingPlayer: boolean,
|
||||||
hideReposts: ?boolean,
|
hideReposts: ?boolean,
|
||||||
clearPlayingUri: () => void,
|
clearPlayingUri: () => void,
|
||||||
darkModeTimes: DarkModeTimes,
|
darkModeTimes: DarkModeTimes,
|
||||||
setDarkTime: (string, {}) => void,
|
setDarkTime: (string, {}) => void,
|
||||||
openModal: string => void,
|
openModal: (string) => void,
|
||||||
language?: string,
|
language?: string,
|
||||||
enterSettings: () => void,
|
enterSettings: () => void,
|
||||||
exitSettings: () => void,
|
exitSettings: () => void,
|
||||||
|
@ -98,7 +97,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
if (isAuthenticated || !IS_WEB) {
|
if (isAuthenticated || !IS_WEB) {
|
||||||
this.props.updateWalletStatus();
|
this.props.updateWalletStatus();
|
||||||
getPasswordFromCookie().then(p => {
|
getPasswordFromCookie().then((p) => {
|
||||||
if (typeof p === 'string') {
|
if (typeof p === 'string') {
|
||||||
this.setState({ storedPassword: true });
|
this.setState({ storedPassword: true });
|
||||||
}
|
}
|
||||||
|
@ -172,7 +171,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
setDaemonSetting,
|
setDaemonSetting,
|
||||||
setClientSetting,
|
setClientSetting,
|
||||||
toggle3PAnalytics,
|
toggle3PAnalytics,
|
||||||
userBlockedChannelsCount,
|
|
||||||
floatingPlayer,
|
floatingPlayer,
|
||||||
hideReposts,
|
hideReposts,
|
||||||
clearPlayingUri,
|
clearPlayingUri,
|
||||||
|
@ -262,7 +260,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
value={currentTheme}
|
value={currentTheme}
|
||||||
disabled={automaticDarkModeEnabled}
|
disabled={automaticDarkModeEnabled}
|
||||||
>
|
>
|
||||||
{themes.map(theme => (
|
{themes.map((theme) => (
|
||||||
<option key={theme} value={theme}>
|
<option key={theme} value={theme}>
|
||||||
{theme === 'light' ? __('Light') : __('Dark')}
|
{theme === 'light' ? __('Light') : __('Dark')}
|
||||||
</option>
|
</option>
|
||||||
|
@ -282,11 +280,11 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
<FormField
|
<FormField
|
||||||
type="select"
|
type="select"
|
||||||
name="automatic_dark_mode_range"
|
name="automatic_dark_mode_range"
|
||||||
onChange={value => this.onChangeTime(value, { fromTo: 'from', time: 'hour' })}
|
onChange={(value) => this.onChangeTime(value, { fromTo: 'from', time: 'hour' })}
|
||||||
value={darkModeTimes.from.hour}
|
value={darkModeTimes.from.hour}
|
||||||
label={__('From --[initial time]--')}
|
label={__('From --[initial time]--')}
|
||||||
>
|
>
|
||||||
{startHours.map(time => (
|
{startHours.map((time) => (
|
||||||
<option key={time} value={time}>
|
<option key={time} value={time}>
|
||||||
{this.to12Hour(time)}
|
{this.to12Hour(time)}
|
||||||
</option>
|
</option>
|
||||||
|
@ -296,10 +294,10 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
type="select"
|
type="select"
|
||||||
name="automatic_dark_mode_range"
|
name="automatic_dark_mode_range"
|
||||||
label={__('To --[final time]--')}
|
label={__('To --[final time]--')}
|
||||||
onChange={value => this.onChangeTime(value, { fromTo: 'to', time: 'hour' })}
|
onChange={(value) => this.onChangeTime(value, { fromTo: 'to', time: 'hour' })}
|
||||||
value={darkModeTimes.to.hour}
|
value={darkModeTimes.to.hour}
|
||||||
>
|
>
|
||||||
{endHours.map(time => (
|
{endHours.map((time) => (
|
||||||
<option key={time} value={time}>
|
<option key={time} value={time}>
|
||||||
{this.to12Hour(time)}
|
{this.to12Hour(time)}
|
||||||
</option>
|
</option>
|
||||||
|
@ -342,7 +340,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
<FormField
|
<FormField
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="hide_reposts"
|
name="hide_reposts"
|
||||||
onChange={e => {
|
onChange={(e) => {
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
let param = e.target.checked ? { add: 'noreposts' } : { remove: 'noreposts' };
|
let param = e.target.checked ? { add: 'noreposts' } : { remove: 'noreposts' };
|
||||||
Lbryio.call('user_tag', 'edit', param);
|
Lbryio.call('user_tag', 'edit', param);
|
||||||
|
@ -411,7 +409,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
<FormField
|
<FormField
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="share_third_party"
|
name="share_third_party"
|
||||||
onChange={e => toggle3PAnalytics(e.target.checked)}
|
onChange={(e) => toggle3PAnalytics(e.target.checked)}
|
||||||
checked={allowAnalytics}
|
checked={allowAnalytics}
|
||||||
label={__('Allow the app to access third party analytics platforms')}
|
label={__('Allow the app to access third party analytics platforms')}
|
||||||
helper={__('We use detailed analytics to improve all aspects of the LBRY experience.')}
|
helper={__('We use detailed analytics to improve all aspects of the LBRY experience.')}
|
||||||
|
@ -438,21 +436,14 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
title={__('Blocked channels')}
|
title={__('Blocked and muted channels')}
|
||||||
subtitle={
|
|
||||||
userBlockedChannelsCount === 0
|
|
||||||
? __("You don't have blocked channels.")
|
|
||||||
: userBlockedChannelsCount === 1
|
|
||||||
? __('You have one blocked channel.')
|
|
||||||
: __('You have %channels% blocked channels.', { channels: userBlockedChannelsCount })
|
|
||||||
}
|
|
||||||
actions={
|
actions={
|
||||||
<div className="section__actions">
|
<div className="section__actions">
|
||||||
<Button
|
<Button
|
||||||
button="secondary"
|
button="secondary"
|
||||||
label={__('Manage')}
|
label={__('Manage')}
|
||||||
icon={ICONS.SETTINGS}
|
icon={ICONS.SETTINGS}
|
||||||
navigate={`/$/${PAGES.BLOCKED}`}
|
navigate={`/$/${PAGES.SETTINGS_BLOCKED_MUTED}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as ACTIONS from 'constants/action_types';
|
||||||
import { selectPrefsReady } from 'redux/selectors/sync';
|
import { selectPrefsReady } from 'redux/selectors/sync';
|
||||||
import { doAlertWaitingForSync } from 'redux/actions/app';
|
import { doAlertWaitingForSync } from 'redux/actions/app';
|
||||||
|
|
||||||
export const doToggleBlockChannel = (uri: string) => (dispatch: Dispatch, getState: GetState) => {
|
export const doToggleMuteChannel = (uri: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const ready = selectPrefsReady(state);
|
const ready = selectPrefsReady(state);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import * as REACTION_TYPES from 'constants/reactions';
|
import * as REACTION_TYPES from 'constants/reactions';
|
||||||
import { Lbry, selectClaimsByUri } from 'lbry-redux';
|
import { Lbry, parseURI, buildURI, selectClaimsByUri, selectMyChannelClaims } from 'lbry-redux';
|
||||||
import { doToast, doSeeNotifications } from 'redux/actions/notifications';
|
import { doToast, doSeeNotifications } from 'redux/actions/notifications';
|
||||||
import {
|
import {
|
||||||
makeSelectCommentIdsForUri,
|
makeSelectCommentIdsForUri,
|
||||||
makeSelectMyReactionsForComment,
|
makeSelectMyReactionsForComment,
|
||||||
makeSelectOthersReactionsForComment,
|
makeSelectOthersReactionsForComment,
|
||||||
selectPendingCommentReacts,
|
selectPendingCommentReacts,
|
||||||
|
selectModerationBlockList,
|
||||||
} from 'redux/selectors/comments';
|
} from 'redux/selectors/comments';
|
||||||
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
|
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
|
||||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
@ -50,7 +51,7 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number =
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_LIST_FAILED,
|
type: ACTIONS.COMMENT_LIST_FAILED,
|
||||||
data: error,
|
data: error,
|
||||||
|
@ -89,7 +90,7 @@ export function doCommentReactList(uri: string | null, commentId?: string) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
|
type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
|
||||||
data: error,
|
data: error,
|
||||||
|
@ -171,14 +172,14 @@ export function doCommentReact(commentId: string, type: string) {
|
||||||
data: commentId + type,
|
data: commentId + type,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_REACT_FAILED,
|
type: ACTIONS.COMMENT_REACT_FAILED,
|
||||||
data: commentId + type,
|
data: commentId + type,
|
||||||
});
|
});
|
||||||
|
|
||||||
const myRevertedReactsObj = myReacts
|
const myRevertedReactsObj = myReacts
|
||||||
.filter(el => el !== type)
|
.filter((el) => el !== type)
|
||||||
.reduce((acc, el) => {
|
.reduce((acc, el) => {
|
||||||
acc[el] = 1;
|
acc[el] = 1;
|
||||||
return acc;
|
return acc;
|
||||||
|
@ -233,14 +234,20 @@ export function doCommentCreate(comment: string = '', claim_id: string = '', par
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_CREATE_FAILED,
|
type: ACTIONS.COMMENT_CREATE_FAILED,
|
||||||
data: error,
|
data: error,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let toastMessage = __('Unable to create comment, please try again later.');
|
||||||
|
if (error && error.message === 'channel is blocked by publisher') {
|
||||||
|
toastMessage = __('Unable to comment. This channel has blocked you.');
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
doToast({
|
doToast({
|
||||||
message: 'Unable to create comment, please try again later.',
|
message: toastMessage,
|
||||||
isError: true,
|
isError: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -274,7 +281,7 @@ export function doCommentPin(commentId: string, remove: boolean) {
|
||||||
data: result,
|
data: result,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_PIN_FAILED,
|
type: ACTIONS.COMMENT_PIN_FAILED,
|
||||||
data: error,
|
data: error,
|
||||||
|
@ -339,7 +346,7 @@ export function doCommentAbandon(commentId: string, creatorChannelUri?: string)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_ABANDON_FAILED,
|
type: ACTIONS.COMMENT_ABANDON_FAILED,
|
||||||
data: error,
|
data: error,
|
||||||
|
@ -389,7 +396,7 @@ export function doCommentUpdate(comment_id: string, comment: string) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_UPDATE_FAILED,
|
type: ACTIONS.COMMENT_UPDATE_FAILED,
|
||||||
data: error,
|
data: error,
|
||||||
|
@ -406,38 +413,203 @@ 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
|
// Hides a users comments from all creator's claims and prevent them from commenting in the future
|
||||||
export function doCommentModBlock(commentAuthor: string) {
|
export function doCommentModToggleBlock(channelUri: string, unblock: boolean = false) {
|
||||||
return async (dispatch: Dispatch, getState: GetState) => {
|
return async (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const claim = selectClaimsByUri(state)[commentAuthor];
|
const myChannels = selectMyChannelClaims(state);
|
||||||
|
const claim = selectClaimsByUri(state)[channelUri];
|
||||||
|
|
||||||
if (!claim) {
|
if (!claim) {
|
||||||
console.error("Can't find claim to block"); // eslint-disable-line
|
console.error("Can't find claim to block"); // eslint-disable-line
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const creatorIdToBan = claim ? claim.claim_id : null;
|
dispatch({
|
||||||
const creatorNameToBan = claim ? claim.name : null;
|
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_STARTED : ACTIONS.COMMENT_MODERATION_BLOCK_STARTED,
|
||||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
data: {
|
||||||
|
uri: channelUri,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
let channelSignature = {};
|
const creatorIdForAction = claim ? claim.claim_id : null;
|
||||||
if (activeChannelClaim) {
|
const creatorNameForAction = claim ? claim.name : null;
|
||||||
try {
|
|
||||||
channelSignature = await Lbry.channel_sign({
|
let channelSignatures = [];
|
||||||
channel_id: activeChannelClaim.claim_id,
|
if (myChannels) {
|
||||||
hexdata: toHex(activeChannelClaim.name),
|
for (const channelClaim of myChannels) {
|
||||||
});
|
try {
|
||||||
} catch (e) {}
|
const channelSignature = await Lbry.channel_sign({
|
||||||
|
channel_id: channelClaim.claim_id,
|
||||||
|
hexdata: toHex(channelClaim.name),
|
||||||
|
});
|
||||||
|
|
||||||
|
channelSignatures.push({ ...channelSignature, claim_id: channelClaim.claim_id, name: channelClaim.name });
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Comments.moderation_block({
|
const sharedModBlockParams = unblock
|
||||||
mod_channel_id: activeChannelClaim.claim_id,
|
? {
|
||||||
mod_channel_name: activeChannelClaim.name,
|
un_blocked_channel_id: creatorIdForAction,
|
||||||
signature: channelSignature.signature,
|
un_blocked_channel_name: creatorNameForAction,
|
||||||
signing_ts: channelSignature.signing_ts,
|
}
|
||||||
banned_channel_id: creatorIdToBan,
|
: {
|
||||||
banned_channel_name: creatorNameToBan,
|
blocked_channel_id: creatorIdForAction,
|
||||||
delete_all: true,
|
blocked_channel_name: creatorNameForAction,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const commentAction = unblock ? Comments.moderation_unblock : Comments.moderation_block;
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
channelSignatures.map((signatureData) =>
|
||||||
|
commentAction({
|
||||||
|
mod_channel_id: signatureData.claim_id,
|
||||||
|
mod_channel_name: signatureData.name,
|
||||||
|
signature: signatureData.signature,
|
||||||
|
signing_ts: signatureData.signing_ts,
|
||||||
|
...sharedModBlockParams,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
dispatch({
|
||||||
|
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_COMPLETE : ACTIONS.COMMENT_MODERATION_BLOCK_COMPLETE,
|
||||||
|
data: { channelUri },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!unblock) {
|
||||||
|
dispatch(doToast({ message: __('Channel blocked. You will not see them again.') }));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
dispatch({
|
||||||
|
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_FAILED : ACTIONS.COMMENT_MODERATION_BLOCK_FAILED,
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doCommentModBlock(commentAuthor: string) {
|
||||||
|
return (dispatch: Dispatch) => {
|
||||||
|
return dispatch(doCommentModToggleBlock(commentAuthor));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doCommentModUnBlock(commentAuthor: string) {
|
||||||
|
return (dispatch: Dispatch) => {
|
||||||
|
return dispatch(doCommentModToggleBlock(commentAuthor, true));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doFetchModBlockedList() {
|
||||||
|
return async (dispatch: Dispatch, getState: GetState) => {
|
||||||
|
const state = getState();
|
||||||
|
const myChannels = selectMyChannelClaims(state);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_STARTED,
|
||||||
|
});
|
||||||
|
|
||||||
|
let channelSignatures = [];
|
||||||
|
if (myChannels) {
|
||||||
|
for (const channelClaim of myChannels) {
|
||||||
|
try {
|
||||||
|
const channelSignature = await Lbry.channel_sign({
|
||||||
|
channel_id: channelClaim.claim_id,
|
||||||
|
hexdata: toHex(channelClaim.name),
|
||||||
|
});
|
||||||
|
|
||||||
|
channelSignatures.push({ ...channelSignature, claim_id: channelClaim.claim_id, name: channelClaim.name });
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
channelSignatures.map((signatureData) =>
|
||||||
|
Comments.moderation_block_list({
|
||||||
|
mod_channel_id: signatureData.claim_id,
|
||||||
|
mod_channel_name: signatureData.name,
|
||||||
|
signature: signatureData.signature,
|
||||||
|
signing_ts: signatureData.signing_ts,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then((blockLists) => {
|
||||||
|
let globalBlockList = [];
|
||||||
|
blockLists
|
||||||
|
.sort((a, b) => {
|
||||||
|
return 1;
|
||||||
|
})
|
||||||
|
.forEach((channelBlockListData) => {
|
||||||
|
const blockListForChannel = channelBlockListData && channelBlockListData.blocked_channels;
|
||||||
|
if (blockListForChannel) {
|
||||||
|
blockListForChannel.forEach((blockedChannel) => {
|
||||||
|
if (blockedChannel.blocked_channel_name) {
|
||||||
|
const channelUri = buildURI({
|
||||||
|
channelName: blockedChannel.blocked_channel_name,
|
||||||
|
claimId: blockedChannel.blocked_channel_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!globalBlockList.find((blockedChannel) => blockedChannel.channelUri === channelUri)) {
|
||||||
|
globalBlockList.push({ channelUri, blockedAt: blockedChannel.blocked_at });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_COMPLETED,
|
||||||
|
data: {
|
||||||
|
blockList: globalBlockList
|
||||||
|
.sort((a, b) => new Date(a.blockedAt) - new Date(b.blockedAt))
|
||||||
|
.map((blockedChannel) => blockedChannel.channelUri),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_FAILED,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const doUpdateBlockListForPublishedChannel = (channelClaim: ChannelClaim) => {
|
||||||
|
return async (dispatch: Dispatch, getState: GetState) => {
|
||||||
|
const state = getState();
|
||||||
|
const blockedUris = selectModerationBlockList(state);
|
||||||
|
|
||||||
|
let channelSignature: ?{
|
||||||
|
signature: string,
|
||||||
|
signing_ts: string,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
channelSignature = await Lbry.channel_sign({
|
||||||
|
channel_id: channelClaim.claim_id,
|
||||||
|
hexdata: toHex(channelClaim.name),
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (!channelSignature) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
blockedUris.map((uri) => {
|
||||||
|
const { channelName, channelClaimId } = parseURI(uri);
|
||||||
|
|
||||||
|
return Comments.moderation_block({
|
||||||
|
mod_channel_id: channelClaim.claim_id,
|
||||||
|
mod_channel_name: channelClaim.name,
|
||||||
|
// $FlowFixMe
|
||||||
|
signature: channelSignature.signature,
|
||||||
|
// $FlowFixMe
|
||||||
|
signing_ts: channelSignature.signing_ts,
|
||||||
|
blocked_channel_id: channelClaimId,
|
||||||
|
blocked_channel_name: channelName,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -15,9 +15,9 @@ export default handleActions(
|
||||||
let newBlockedChannels = blockedChannels.slice();
|
let newBlockedChannels = blockedChannels.slice();
|
||||||
|
|
||||||
if (newBlockedChannels.includes(uri)) {
|
if (newBlockedChannels.includes(uri)) {
|
||||||
newBlockedChannels = newBlockedChannels.filter(id => id !== uri);
|
newBlockedChannels = newBlockedChannels.filter((id) => id !== uri);
|
||||||
} else {
|
} else {
|
||||||
newBlockedChannels.push(uri);
|
newBlockedChannels.unshift(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -29,7 +29,7 @@ export default handleActions(
|
||||||
action: { data: { blocked: ?Array<string> } }
|
action: { data: { blocked: ?Array<string> } }
|
||||||
) => {
|
) => {
|
||||||
const { blocked } = action.data;
|
const { blocked } = action.data;
|
||||||
const sanitizedBlocked = blocked && blocked.filter(e => typeof e === 'string');
|
const sanitizedBlocked = blocked && blocked.filter((e) => typeof e === 'string');
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
blockedChannels: sanitizedBlocked && sanitizedBlocked.length ? sanitizedBlocked : state.blockedChannels,
|
blockedChannels: sanitizedBlocked && sanitizedBlocked.length ? sanitizedBlocked : state.blockedChannels,
|
||||||
|
|
|
@ -16,6 +16,10 @@ const defaultState: CommentsState = {
|
||||||
typesReacting: [],
|
typesReacting: [],
|
||||||
myReactsByCommentId: undefined,
|
myReactsByCommentId: undefined,
|
||||||
othersReactsByCommentId: undefined,
|
othersReactsByCommentId: undefined,
|
||||||
|
moderationBlockList: undefined,
|
||||||
|
fetchingModerationBlockList: false,
|
||||||
|
blockingByUri: {},
|
||||||
|
unBlockingByUri: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
|
@ -144,7 +148,7 @@ export default handleActions(
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
[ACTIONS.COMMENT_LIST_STARTED]: state => ({ ...state, isLoading: true }),
|
[ACTIONS.COMMENT_LIST_STARTED]: (state) => ({ ...state, isLoading: true }),
|
||||||
|
|
||||||
[ACTIONS.COMMENT_LIST_COMPLETED]: (state: CommentsState, action: any) => {
|
[ACTIONS.COMMENT_LIST_COMPLETED]: (state: CommentsState, action: any) => {
|
||||||
const { comments, claimId, uri } = action.data;
|
const { comments, claimId, uri } = action.data;
|
||||||
|
@ -227,17 +231,15 @@ export default handleActions(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// do nothing
|
|
||||||
[ACTIONS.COMMENT_ABANDON_FAILED]: (state: CommentsState, action: any) => ({
|
[ACTIONS.COMMENT_ABANDON_FAILED]: (state: CommentsState, action: any) => ({
|
||||||
...state,
|
...state,
|
||||||
isCommenting: false,
|
isCommenting: false,
|
||||||
}),
|
}),
|
||||||
// do nothing
|
|
||||||
[ACTIONS.COMMENT_UPDATE_STARTED]: (state: CommentsState, action: any) => ({
|
[ACTIONS.COMMENT_UPDATE_STARTED]: (state: CommentsState, action: any) => ({
|
||||||
...state,
|
...state,
|
||||||
isCommenting: true,
|
isCommenting: true,
|
||||||
}),
|
}),
|
||||||
// replace existing comment with comment returned here under its comment_id
|
|
||||||
[ACTIONS.COMMENT_UPDATE_COMPLETED]: (state: CommentsState, action: any) => {
|
[ACTIONS.COMMENT_UPDATE_COMPLETED]: (state: CommentsState, action: any) => {
|
||||||
const { comment } = action.data;
|
const { comment } = action.data;
|
||||||
const commentById = Object.assign({}, state.commentById);
|
const commentById = Object.assign({}, state.commentById);
|
||||||
|
@ -249,25 +251,100 @@ export default handleActions(
|
||||||
isCommenting: false,
|
isCommenting: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// nothing can be done here
|
|
||||||
[ACTIONS.COMMENT_UPDATE_FAILED]: (state: CommentsState, action: any) => ({
|
[ACTIONS.COMMENT_UPDATE_FAILED]: (state: CommentsState, action: any) => ({
|
||||||
...state,
|
...state,
|
||||||
isCmmenting: false,
|
isCmmenting: false,
|
||||||
}),
|
}),
|
||||||
// nothing can really be done here
|
[ACTIONS.COMMENT_MODERATION_BLOCK_LIST_STARTED]: (state: CommentsState, action: any) => ({
|
||||||
[ACTIONS.COMMENT_HIDE_STARTED]: (state: CommentsState, action: any) => ({
|
|
||||||
...state,
|
...state,
|
||||||
isLoading: true,
|
fetchingModerationBlockList: true,
|
||||||
}),
|
}),
|
||||||
[ACTIONS.COMMENT_HIDE_COMPLETED]: (state: CommentsState, action: any) => ({
|
[ACTIONS.COMMENT_MODERATION_BLOCK_LIST_COMPLETED]: (state: CommentsState, action: any) => {
|
||||||
...state, // todo: add HiddenComments state & create selectors
|
const { blockList } = action.data;
|
||||||
isLoading: false,
|
|
||||||
}),
|
return {
|
||||||
// nothing can be done here
|
...state,
|
||||||
[ACTIONS.COMMENT_HIDE_FAILED]: (state: CommentsState, action: any) => ({
|
moderationBlockList: blockList,
|
||||||
|
fetchingModerationBlockList: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[ACTIONS.COMMENT_MODERATION_BLOCK_LIST_FAILED]: (state: CommentsState, action: any) => ({
|
||||||
...state,
|
...state,
|
||||||
isLoading: false,
|
fetchingModerationBlockList: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
[ACTIONS.COMMENT_MODERATION_BLOCK_STARTED]: (state: CommentsState, action: any) => ({
|
||||||
|
...state,
|
||||||
|
blockingByUri: {
|
||||||
|
...state.blockingByUri,
|
||||||
|
[action.data.uri]: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
[ACTIONS.COMMENT_MODERATION_UN_BLOCK_STARTED]: (state: CommentsState, action: any) => ({
|
||||||
|
...state,
|
||||||
|
unBlockingByUri: {
|
||||||
|
...state.unBlockingByUri,
|
||||||
|
[action.data.uri]: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[ACTIONS.COMMENT_MODERATION_BLOCK_FAILED]: (state: CommentsState, action: any) => ({
|
||||||
|
...state,
|
||||||
|
blockingByUri: {
|
||||||
|
...state.blockingByUri,
|
||||||
|
[action.data.uri]: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
[ACTIONS.COMMENT_MODERATION_UN_BLOCK_FAILED]: (state: CommentsState, action: any) => ({
|
||||||
|
...state,
|
||||||
|
unBlockingByUri: {
|
||||||
|
...state.unBlockingByUri,
|
||||||
|
[action.data.uri]: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
[ACTIONS.COMMENT_MODERATION_BLOCK_COMPLETE]: (state: CommentsState, action: any) => {
|
||||||
|
const { channelUri } = action.data;
|
||||||
|
const commentById = Object.assign({}, state.commentById);
|
||||||
|
const blockingByUri = Object.assign({}, state.blockingByUri);
|
||||||
|
const moderationBlockList = state.moderationBlockList || [];
|
||||||
|
const newModerationBlockList = moderationBlockList.slice();
|
||||||
|
|
||||||
|
for (const commentId in commentById) {
|
||||||
|
const comment = commentById[commentId];
|
||||||
|
|
||||||
|
if (channelUri === comment.channel_url) {
|
||||||
|
delete commentById[comment.comment_id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete blockingByUri[channelUri];
|
||||||
|
|
||||||
|
newModerationBlockList.push(channelUri);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
commentById,
|
||||||
|
blockingByUri,
|
||||||
|
moderationBlockList: newModerationBlockList,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[ACTIONS.COMMENT_MODERATION_UN_BLOCK_COMPLETE]: (state: CommentsState, action: any) => {
|
||||||
|
const { channelUri } = action.data;
|
||||||
|
const unBlockingByUri = Object.assign(state.unBlockingByUri, {});
|
||||||
|
const moderationBlockList = state.moderationBlockList || [];
|
||||||
|
const newModerationBlockList = moderationBlockList.slice().filter((uri) => uri !== channelUri);
|
||||||
|
|
||||||
|
delete unBlockingByUri[channelUri];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
unBlockingByUri,
|
||||||
|
moderationBlockList: newModerationBlockList,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
defaultState
|
defaultState
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,23 +3,11 @@ import { createSelector } from 'reselect';
|
||||||
|
|
||||||
const selectState = (state: { blocked: BlocklistState }) => state.blocked || {};
|
const selectState = (state: { blocked: BlocklistState }) => state.blocked || {};
|
||||||
|
|
||||||
export const selectBlockedChannels = createSelector(selectState, (state: BlocklistState) => {
|
export const selectMutedChannels = createSelector(selectState, (state: BlocklistState) => {
|
||||||
return state.blockedChannels.filter(e => typeof e === 'string');
|
return state.blockedChannels.filter((e) => typeof e === 'string');
|
||||||
});
|
});
|
||||||
|
|
||||||
export const selectBlockedChannelsCount = createSelector(selectBlockedChannels, (state: Array<string>) => state.length);
|
export const makeSelectChannelIsMuted = (uri: string) =>
|
||||||
|
createSelector(selectMutedChannels, (state: Array<string>) => {
|
||||||
export const selectBlockedChannelsObj = createSelector(selectState, (state: BlocklistState) => {
|
|
||||||
return state.blockedChannels.reduce((acc: any, val: any) => {
|
|
||||||
const outpoint = `${val.txid}:${String(val.nout)}`;
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[outpoint]: 1,
|
|
||||||
};
|
|
||||||
}, {});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const selectChannelIsBlocked = (uri: string) =>
|
|
||||||
createSelector(selectBlockedChannels, (state: Array<string>) => {
|
|
||||||
return state.includes(uri);
|
return state.includes(uri);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc';
|
import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc';
|
||||||
import { selectClaimsById, isClaimNsfw, selectMyActiveClaims } from 'lbry-redux';
|
import { selectClaimsById, isClaimNsfw, selectMyActiveClaims } from 'lbry-redux';
|
||||||
|
|
||||||
const selectState = state => state.comments || {};
|
const selectState = (state) => state.comments || {};
|
||||||
|
|
||||||
export const selectCommentsById = createSelector(selectState, state => state.commentById || {});
|
export const selectCommentsById = createSelector(selectState, (state) => state.commentById || {});
|
||||||
|
export const selectIsFetchingComments = createSelector(selectState, (state) => state.isLoading);
|
||||||
export const selectIsFetchingComments = createSelector(selectState, state => state.isLoading);
|
export const selectIsPostingComment = createSelector(selectState, (state) => state.isCommenting);
|
||||||
|
export const selectIsFetchingReacts = createSelector(selectState, (state) => state.isFetchingReacts);
|
||||||
export const selectIsPostingComment = createSelector(selectState, state => state.isCommenting);
|
export const selectOthersReactsById = createSelector(selectState, (state) => state.othersReactsByCommentId);
|
||||||
|
export const selectModerationBlockList = createSelector(selectState, (state) =>
|
||||||
export const selectIsFetchingReacts = createSelector(selectState, state => state.isFetchingReacts);
|
state.moderationBlockList ? state.moderationBlockList.reverse() : []
|
||||||
|
);
|
||||||
export const selectOthersReactsById = createSelector(selectState, state => state.othersReactsByCommentId);
|
export const selectBlockingByUri = createSelector(selectState, (state) => state.blockingByUri);
|
||||||
|
export const selectUnBlockingByUri = createSelector(selectState, (state) => state.unBlockingByUri);
|
||||||
|
export const selectFetchingModerationBlockList = createSelector(
|
||||||
|
selectState,
|
||||||
|
(state) => state.fetchingModerationBlockList
|
||||||
|
);
|
||||||
|
|
||||||
export const selectCommentsByClaimId = createSelector(selectState, selectCommentsById, (state, byId) => {
|
export const selectCommentsByClaimId = createSelector(selectState, selectCommentsById, (state, byId) => {
|
||||||
const byClaimId = state.byId || {};
|
const byClaimId = state.byId || {};
|
||||||
|
@ -40,7 +45,7 @@ export const selectTopLevelCommentsByClaimId = createSelector(selectState, selec
|
||||||
const comments = {};
|
const comments = {};
|
||||||
|
|
||||||
// replace every comment_id in the list with the actual comment object
|
// replace every comment_id in the list with the actual comment object
|
||||||
Object.keys(byClaimId).forEach(claimId => {
|
Object.keys(byClaimId).forEach((claimId) => {
|
||||||
const commentIds = byClaimId[claimId];
|
const commentIds = byClaimId[claimId];
|
||||||
|
|
||||||
comments[claimId] = Array(commentIds === null ? 0 : commentIds.length);
|
comments[claimId] = Array(commentIds === null ? 0 : commentIds.length);
|
||||||
|
@ -53,14 +58,14 @@ export const selectTopLevelCommentsByClaimId = createSelector(selectState, selec
|
||||||
});
|
});
|
||||||
|
|
||||||
export const makeSelectCommentForCommentId = (commentId: string) =>
|
export const makeSelectCommentForCommentId = (commentId: string) =>
|
||||||
createSelector(selectCommentsById, comments => comments[commentId]);
|
createSelector(selectCommentsById, (comments) => comments[commentId]);
|
||||||
|
|
||||||
export const selectRepliesByParentId = createSelector(selectState, selectCommentsById, (state, byId) => {
|
export const selectRepliesByParentId = createSelector(selectState, selectCommentsById, (state, byId) => {
|
||||||
const byParentId = state.repliesByParentId || {};
|
const byParentId = state.repliesByParentId || {};
|
||||||
const comments = {};
|
const comments = {};
|
||||||
|
|
||||||
// replace every comment_id in the list with the actual comment object
|
// replace every comment_id in the list with the actual comment object
|
||||||
Object.keys(byParentId).forEach(id => {
|
Object.keys(byParentId).forEach((id) => {
|
||||||
const commentIds = byParentId[id];
|
const commentIds = byParentId[id];
|
||||||
|
|
||||||
comments[id] = Array(commentIds === null ? 0 : commentIds.length);
|
comments[id] = Array(commentIds === null ? 0 : commentIds.length);
|
||||||
|
@ -77,10 +82,10 @@ export const selectRepliesByParentId = createSelector(selectState, selectComment
|
||||||
selectState,
|
selectState,
|
||||||
state => state.byId || {}
|
state => state.byId || {}
|
||||||
); */
|
); */
|
||||||
export const selectCommentsByUri = createSelector(selectState, state => {
|
export const selectCommentsByUri = createSelector(selectState, (state) => {
|
||||||
const byUri = state.commentsByUri || {};
|
const byUri = state.commentsByUri || {};
|
||||||
const comments = {};
|
const comments = {};
|
||||||
Object.keys(byUri).forEach(uri => {
|
Object.keys(byUri).forEach((uri) => {
|
||||||
const claimId = byUri[uri];
|
const claimId = byUri[uri];
|
||||||
if (claimId === null) {
|
if (claimId === null) {
|
||||||
comments[uri] = null;
|
comments[uri] = null;
|
||||||
|
@ -99,16 +104,16 @@ export const makeSelectCommentIdsForUri = (uri: string) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
export const makeSelectMyReactionsForComment = (commentId: string) =>
|
export const makeSelectMyReactionsForComment = (commentId: string) =>
|
||||||
createSelector(selectState, state => {
|
createSelector(selectState, (state) => {
|
||||||
return state.myReactsByCommentId[commentId] || [];
|
return state.myReactsByCommentId[commentId] || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
export const makeSelectOthersReactionsForComment = (commentId: string) =>
|
export const makeSelectOthersReactionsForComment = (commentId: string) =>
|
||||||
createSelector(selectState, state => {
|
createSelector(selectState, (state) => {
|
||||||
return state.othersReactsByCommentId[commentId];
|
return state.othersReactsByCommentId[commentId];
|
||||||
});
|
});
|
||||||
|
|
||||||
export const selectPendingCommentReacts = createSelector(selectState, state => state.pendingCommentReactions);
|
export const selectPendingCommentReacts = createSelector(selectState, (state) => state.pendingCommentReactions);
|
||||||
|
|
||||||
export const makeSelectCommentsForUri = (uri: string) =>
|
export const makeSelectCommentsForUri = (uri: string) =>
|
||||||
createSelector(
|
createSelector(
|
||||||
|
@ -116,7 +121,7 @@ export const makeSelectCommentsForUri = (uri: string) =>
|
||||||
selectCommentsByUri,
|
selectCommentsByUri,
|
||||||
selectClaimsById,
|
selectClaimsById,
|
||||||
selectMyActiveClaims,
|
selectMyActiveClaims,
|
||||||
selectBlockedChannels,
|
selectMutedChannels,
|
||||||
selectBlacklistedOutpointMap,
|
selectBlacklistedOutpointMap,
|
||||||
selectFilteredOutpointMap,
|
selectFilteredOutpointMap,
|
||||||
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
||||||
|
@ -125,7 +130,12 @@ export const makeSelectCommentsForUri = (uri: string) =>
|
||||||
const comments = byClaimId && byClaimId[claimId];
|
const comments = byClaimId && byClaimId[claimId];
|
||||||
|
|
||||||
return comments
|
return comments
|
||||||
? comments.filter(comment => {
|
? comments.filter((comment) => {
|
||||||
|
if (!comment) {
|
||||||
|
// It may have been recently deleted after being blocked
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const channelClaim = claimsById[comment.channel_id];
|
const channelClaim = claimsById[comment.channel_id];
|
||||||
|
|
||||||
// Return comment if `channelClaim` doesn't exist so the component knows to resolve the author
|
// Return comment if `channelClaim` doesn't exist so the component knows to resolve the author
|
||||||
|
@ -162,7 +172,7 @@ export const makeSelectTopLevelCommentsForUri = (uri: string) =>
|
||||||
selectCommentsByUri,
|
selectCommentsByUri,
|
||||||
selectClaimsById,
|
selectClaimsById,
|
||||||
selectMyActiveClaims,
|
selectMyActiveClaims,
|
||||||
selectBlockedChannels,
|
selectMutedChannels,
|
||||||
selectBlacklistedOutpointMap,
|
selectBlacklistedOutpointMap,
|
||||||
selectFilteredOutpointMap,
|
selectFilteredOutpointMap,
|
||||||
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
||||||
|
@ -171,7 +181,7 @@ export const makeSelectTopLevelCommentsForUri = (uri: string) =>
|
||||||
const comments = byClaimId && byClaimId[claimId];
|
const comments = byClaimId && byClaimId[claimId];
|
||||||
|
|
||||||
return comments
|
return comments
|
||||||
? comments.filter(comment => {
|
? comments.filter((comment) => {
|
||||||
if (!comment) {
|
if (!comment) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -212,7 +222,7 @@ export const makeSelectRepliesForParentId = (id: string) =>
|
||||||
selectCommentsById,
|
selectCommentsById,
|
||||||
selectClaimsById,
|
selectClaimsById,
|
||||||
selectMyActiveClaims,
|
selectMyActiveClaims,
|
||||||
selectBlockedChannels,
|
selectMutedChannels,
|
||||||
selectBlacklistedOutpointMap,
|
selectBlacklistedOutpointMap,
|
||||||
selectFilteredOutpointMap,
|
selectFilteredOutpointMap,
|
||||||
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
||||||
|
@ -223,13 +233,13 @@ export const makeSelectRepliesForParentId = (id: string) =>
|
||||||
if (!replyIdsForParent.length) return null;
|
if (!replyIdsForParent.length) return null;
|
||||||
|
|
||||||
const comments = [];
|
const comments = [];
|
||||||
replyIdsForParent.forEach(cid => {
|
replyIdsForParent.forEach((cid) => {
|
||||||
comments.push(commentsById[cid]);
|
comments.push(commentsById[cid]);
|
||||||
});
|
});
|
||||||
// const comments = byParentId && byParentId[id];
|
// const comments = byParentId && byParentId[id];
|
||||||
|
|
||||||
return comments
|
return comments
|
||||||
? comments.filter(comment => {
|
? comments.filter((comment) => {
|
||||||
if (!comment) {
|
if (!comment) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -265,6 +275,20 @@ export const makeSelectRepliesForParentId = (id: string) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const makeSelectTotalCommentsCountForUri = (uri: string) =>
|
export const makeSelectTotalCommentsCountForUri = (uri: string) =>
|
||||||
createSelector(makeSelectCommentsForUri(uri), comments => {
|
createSelector(makeSelectCommentsForUri(uri), (comments) => {
|
||||||
return comments ? comments.length : 0;
|
return comments ? comments.length : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const makeSelectChannelIsBlocked = (uri: string) =>
|
||||||
|
createSelector(selectModerationBlockList, (blockedChannelUris) => {
|
||||||
|
if (!blockedChannelUris || !blockedChannelUris) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockedChannelUris.includes(uri);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const makeSelectUriIsBlockingOrUnBlocking = (uri: string) =>
|
||||||
|
createSelector(selectBlockingByUri, selectUnBlockingByUri, (blockingByUri, unBlockingByUri) => {
|
||||||
|
return blockingByUri[uri] || unBlockingByUri[uri];
|
||||||
|
});
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
makeSelectFileNameForUri,
|
makeSelectFileNameForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { makeSelectRecommendedContentForUri } from 'redux/selectors/search';
|
import { makeSelectRecommendedContentForUri } from 'redux/selectors/search';
|
||||||
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { selectAllCostInfoByUri, makeSelectCostInfoForUri } from 'lbryinc';
|
import { selectAllCostInfoByUri, makeSelectCostInfoForUri } from 'lbryinc';
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||||
|
@ -81,7 +81,7 @@ export const makeSelectNextUnplayedRecommended = (uri: string) =>
|
||||||
selectHistory,
|
selectHistory,
|
||||||
selectClaimsByUri,
|
selectClaimsByUri,
|
||||||
selectAllCostInfoByUri,
|
selectAllCostInfoByUri,
|
||||||
selectBlockedChannels,
|
selectMutedChannels,
|
||||||
(
|
(
|
||||||
recommendedForUri: Array<string>,
|
recommendedForUri: Array<string>,
|
||||||
history: Array<{ uri: string }>,
|
history: Array<{ uri: string }>,
|
||||||
|
|
|
@ -372,9 +372,9 @@ $metadata-z-index: 1;
|
||||||
.channel-staked__wrapper {
|
.channel-staked__wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 0.25rem;
|
padding: 0.2rem;
|
||||||
bottom: -0.75rem;
|
bottom: -0.75rem;
|
||||||
left: -0.75rem;
|
left: -0.8rem;
|
||||||
background-color: var(--color-card-background);
|
background-color: var(--color-card-background);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
@ -390,6 +390,16 @@ $metadata-z-index: 1;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channel-staked__wrapper--inline {
|
||||||
|
position: relative;
|
||||||
|
background-color: transparent;
|
||||||
|
display: inline-block;
|
||||||
|
bottom: auto;
|
||||||
|
left: auto;
|
||||||
|
top: var(--spacing-xs);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.channel-staked__indicator {
|
.channel-staked__indicator {
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
|
|
@ -64,6 +64,12 @@
|
||||||
.claim-preview__wrapper {
|
.claim-preview__wrapper {
|
||||||
padding: var(--spacing-m);
|
padding: var(--spacing-m);
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.claim__menu-button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-preview__wrapper--notice {
|
.claim-preview__wrapper--notice {
|
||||||
|
@ -247,17 +253,6 @@
|
||||||
|
|
||||||
.claim-preview-info {
|
.claim-preview-info {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
.channel-thumbnail {
|
|
||||||
display: none;
|
|
||||||
@include handleChannelGif(1.4rem);
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: var(--spacing-s);
|
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-preview-info,
|
.claim-preview-info,
|
||||||
|
@ -408,6 +403,10 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.claim__menu-button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $breakpoint-large) {
|
@media (min-width: $breakpoint-large) {
|
||||||
|
@ -456,12 +455,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-tile__title {
|
.claim-tile__title {
|
||||||
margin: var(--spacing-s);
|
position: relative;
|
||||||
|
|
||||||
|
padding: var(--spacing-s);
|
||||||
|
padding-right: var(--spacing-m);
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: var(--spacing-s);
|
||||||
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-size: var(--font-small);
|
font-size: var(--font-small);
|
||||||
min-height: 2rem;
|
min-height: 2rem;
|
||||||
|
|
||||||
|
.claim__menu-button {
|
||||||
|
right: 0.2rem;
|
||||||
|
top: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
@media (min-width: $breakpoint-small) {
|
||||||
min-height: 2.5rem;
|
min-height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
@ -571,3 +581,36 @@
|
||||||
.claim-preview__null-label {
|
.claim-preview__null-label {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.claim-preview__channel-staked {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.channel-thumbnail {
|
||||||
|
@include handleChannelGif(1.4rem);
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claim__menu-button {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--spacing-xs);
|
||||||
|
right: var(--spacing-xs);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
stroke: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([aria-expanded='true']) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claim__menu-button--inline {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
right: auto;
|
||||||
|
top: auto;
|
||||||
|
@extend .button--alt;
|
||||||
|
padding: 0 var(--spacing-xxs);
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,15 @@
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu__button {
|
||||||
|
&:hover {
|
||||||
|
.icon {
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--color-card-background-highlighted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.menu__title {
|
.menu__title {
|
||||||
&[aria-expanded='true'] {
|
&[aria-expanded='true'] {
|
||||||
background-color: var(--color-primary-alt);
|
background-color: var(--color-primary-alt);
|
||||||
|
@ -54,16 +63,7 @@
|
||||||
animation: menu-animate-in var(--animation-duration) var(--animation-style);
|
animation: menu-animate-in var(--animation-duration) var(--animation-style);
|
||||||
border-bottom-left-radius: var(--border-radius);
|
border-bottom-left-radius: var(--border-radius);
|
||||||
border-bottom-right-radius: var(--border-radius);
|
border-bottom-right-radius: var(--border-radius);
|
||||||
}
|
|
||||||
|
|
||||||
.menu__list--header {
|
|
||||||
@extend .menu__list;
|
|
||||||
padding: var(--spacing-xs);
|
|
||||||
margin-top: 19px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__list--comments {
|
|
||||||
@extend .menu__list;
|
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
padding: var(--spacing-xs) 0;
|
padding: var(--spacing-xs) 0;
|
||||||
|
@ -73,6 +73,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu__list--header {
|
||||||
|
@extend .menu__list;
|
||||||
|
margin-top: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu__list--comments {
|
||||||
|
@extend .menu__list;
|
||||||
|
}
|
||||||
|
|
||||||
.menu__link {
|
.menu__link {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
--color-button-primary-bg-hover: var(--color-primary-alt-2);
|
--color-button-primary-bg-hover: var(--color-primary-alt-2);
|
||||||
--color-button-primary-hover-text: var(--color-primary-alt);
|
--color-button-primary-hover-text: var(--color-primary-alt);
|
||||||
--color-button-secondary-bg: var(--color-secondary-alt);
|
--color-button-secondary-bg: var(--color-secondary-alt);
|
||||||
--color-button-secondary-border: #c5d7ed;
|
--color-button-secondary-border: var(--color-secondary-alt);
|
||||||
--color-button-secondary-text: var(--color-secondary);
|
--color-button-secondary-text: var(--color-secondary);
|
||||||
--color-button-secondary-bg-hover: #b9d0e9;
|
--color-button-secondary-bg-hover: #b9d0e9;
|
||||||
--color-button-alt-bg: var(--color-gray-1);
|
--color-button-alt-bg: var(--color-gray-1);
|
||||||
|
|
|
@ -262,6 +262,12 @@ textarea {
|
||||||
background-color: var(--color-help-warning-bg);
|
background-color: var(--color-help-warning-bg);
|
||||||
color: var(--color-help-warning-text);
|
color: var(--color-help-warning-text);
|
||||||
margin-bottom: var(--spacing-s);
|
margin-bottom: var(--spacing-s);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help--notice {
|
||||||
|
@extend .help--warning;
|
||||||
|
background-color: var(--color-card-background-highlighted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.help--inline {
|
.help--inline {
|
||||||
|
|
|
@ -24,9 +24,9 @@ function isNotFunction(object) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBulkThunkMiddleware() {
|
function createBulkThunkMiddleware() {
|
||||||
return ({ dispatch, getState }) => next => action => {
|
return ({ dispatch, getState }) => (next) => (action) => {
|
||||||
if (action.type === 'BATCH_ACTIONS') {
|
if (action.type === 'BATCH_ACTIONS') {
|
||||||
action.actions.filter(isFunction).map(actionFn => actionFn(dispatch, getState));
|
action.actions.filter(isFunction).map((actionFn) => actionFn(dispatch, getState));
|
||||||
}
|
}
|
||||||
return next(action);
|
return next(action);
|
||||||
};
|
};
|
||||||
|
@ -68,7 +68,6 @@ const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||||
const blockedFilter = createFilter('blocked', ['blockedChannels']);
|
const blockedFilter = createFilter('blocked', ['blockedChannels']);
|
||||||
const settingsFilter = createBlacklistFilter('settings', ['loadedLanguages', 'language']);
|
const settingsFilter = createBlacklistFilter('settings', ['loadedLanguages', 'language']);
|
||||||
const whiteListedReducers = [
|
const whiteListedReducers = [
|
||||||
'comments',
|
|
||||||
'fileInfo',
|
'fileInfo',
|
||||||
'publish',
|
'publish',
|
||||||
'wallet',
|
'wallet',
|
||||||
|
@ -143,7 +142,7 @@ const sharedStateFilters = {
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
source: 'subscriptions',
|
source: 'subscriptions',
|
||||||
property: 'subscriptions',
|
property: 'subscriptions',
|
||||||
transform: function(value) {
|
transform: (value) => {
|
||||||
return value.map(({ uri }) => uri);
|
return value.map(({ uri }) => uri);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -162,7 +161,7 @@ const sharedStateCb = ({ dispatch, getState }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const populateAuthTokenHeader = () => {
|
const populateAuthTokenHeader = () => {
|
||||||
return next => action => {
|
return (next) => (action) => {
|
||||||
if (
|
if (
|
||||||
(action.type === ACTIONS.USER_FETCH_SUCCESS || action.type === ACTIONS.AUTHENTICATION_SUCCESS) &&
|
(action.type === ACTIONS.USER_FETCH_SUCCESS || action.type === ACTIONS.AUTHENTICATION_SUCCESS) &&
|
||||||
action.data.user.has_verified_email === true
|
action.data.user.has_verified_email === true
|
||||||
|
|
|
@ -1,8 +1,46 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
export function toHex(str: string): string {
|
export function toHex(str: string): string {
|
||||||
var result = '';
|
const array = Array.from(str);
|
||||||
for (var i = 0; i < str.length; i++) {
|
|
||||||
result += str.charCodeAt(i).toString(16);
|
let result = '';
|
||||||
|
|
||||||
|
for (var i = 0; i < array.length; i++) {
|
||||||
|
const val = array[i];
|
||||||
|
const utf = toUTF8Array(val)
|
||||||
|
.map((num) => num.toString(16))
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
result += utf;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://gist.github.com/joni/3760795
|
||||||
|
// See comment that fixes an issue in the original gist
|
||||||
|
function toUTF8Array(str: string): Array<number> {
|
||||||
|
var utf8 = [];
|
||||||
|
for (var i = 0; i < str.length; i++) {
|
||||||
|
var charcode = str.charCodeAt(i);
|
||||||
|
if (charcode < 0x80) utf8.push(charcode);
|
||||||
|
else if (charcode < 0x800) {
|
||||||
|
utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f));
|
||||||
|
} else if (charcode < 0xd800 || charcode >= 0xe000) {
|
||||||
|
utf8.push(0xe0 | (charcode >> 12), 0x80 | ((charcode >> 6) & 0x3f), 0x80 | (charcode & 0x3f));
|
||||||
|
}
|
||||||
|
// surrogate pair
|
||||||
|
else {
|
||||||
|
i++;
|
||||||
|
charcode = (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)) + 0x010000;
|
||||||
|
utf8.push(
|
||||||
|
0xf0 | (charcode >> 18),
|
||||||
|
0x80 | ((charcode >> 12) & 0x3f),
|
||||||
|
0x80 | ((charcode >> 6) & 0x3f),
|
||||||
|
0x80 | (charcode & 0x3f)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue