New moderation tools: block & mute #5572
21 changed files with 108 additions and 360 deletions
|
@ -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,
|
||||
} from 'lbry-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
|
||||
import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments';
|
||||
import ChannelPage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -38,12 +38,16 @@ const select = (state, props) => ({
|
|||
balance: selectBalance(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
const perform = (dispatch) => ({
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
updateChannel: params => dispatch(doUpdateChannel(params)),
|
||||
createChannel: params => {
|
||||
updateChannel: (params) => dispatch(doUpdateChannel(params)),
|
||||
createChannel: (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()),
|
||||
});
|
||||
|
|
|
@ -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;
|
|
@ -129,7 +129,7 @@ function ClaimListDiscover(props: Props) {
|
|||
(urlParams.get(CS.TAGS_KEY) !== null && urlParams.get(CS.TAGS_KEY)) ||
|
||||
(defaultTags && getParamFromTags(defaultTags));
|
||||
const freshnessParam = freshness || urlParams.get(CS.FRESH_KEY) || defaultFreshness;
|
||||
const mutedAndBlockedChannelIds = mutedUris.concat(blockedUris).map((uri) => uri.split('#')[1]);
|
||||
const mutedAndBlockedChannelIds = Array.from(new Set(mutedUris.concat(blockedUris).map((uri) => uri.split('#')[1])));
|
||||
|
||||
const langParam = urlParams.get(CS.LANGUAGE_KEY) || null;
|
||||
const languageParams = searchInLanguage
|
||||
|
|
|
@ -33,7 +33,7 @@ function ClaimMenuList(props: Props) {
|
|||
? claim.permanent_url
|
||||
: claim.signing_channel && claim.signing_channel.permanent_url);
|
||||
|
||||
if (!channelUri) {
|
||||
if (!channelUri || claimIsMine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import FileDownloadLink from 'component/fileDownloadLink';
|
|||
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
|
||||
import PublishPending from 'component/publishPending';
|
||||
import ClaimMenuList from 'component/claimMenuList';
|
||||
import ChannelStakedIndicator from 'component/channelStakedIndicator';
|
||||
import ClaimPreviewLoading from './claim-preview-loading';
|
||||
import ClaimPreviewHidden from './claim-preview-no-mature';
|
||||
import ClaimPreviewNoContent from './claim-preview-no-content';
|
||||
|
@ -329,8 +328,8 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
<div className="claim-preview__primary-actions">
|
||||
{!isChannelUri && signingChannel && (
|
||||
<div className="claim-preview__channel-staked">
|
||||
<ChannelThumbnail uri={signingChannel.permanent_url} hideStakedIndicator />
|
||||
<ChannelStakedIndicator uri={signingChannel.permanent_url} inline />
|
||||
<ChannelThumbnail uri={signingChannel.permanent_url} />
|
||||
{/* <ChannelStakedIndicator uri={signingChannel.permanent_url} inline /> */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doClaimSearch, selectClaimSearchByQuery, selectFetchingClaimSearchByQuery, SETTINGS } from 'lbry-redux';
|
||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import ClaimListDiscover from './view';
|
||||
|
@ -10,7 +11,8 @@ const select = (state) => ({
|
|||
fetchingClaimSearchByQuery: selectFetchingClaimSearchByQuery(state),
|
||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
||||
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
|
||||
hiddenUris: selectMutedChannels(state),
|
||||
mutedUris: selectMutedChannels(state),
|
||||
blockedUris: selectModerationBlockList(state),
|
||||
});
|
||||
|
||||
const perform = {
|
||||
|
|
|
@ -10,7 +10,7 @@ type Props = {
|
|||
doClaimSearch: ({}) => void,
|
||||
showNsfw: boolean,
|
||||
hideReposts: boolean,
|
||||
history: { action: string, push: string => void, replace: string => void },
|
||||
history: { action: string, push: (string) => void, replace: (string) => void },
|
||||
claimSearchByQuery: {
|
||||
[string]: Array<string>,
|
||||
},
|
||||
|
@ -19,7 +19,8 @@ type Props = {
|
|||
},
|
||||
// claim search options are below
|
||||
tags: Array<string>,
|
||||
hiddenUris: Array<string>,
|
||||
blockedUris: Array<string>,
|
||||
mutedUris: Array<string>,
|
||||
claimIds?: Array<string>,
|
||||
channelIds?: Array<string>,
|
||||
notChannelIds?: Array<string>,
|
||||
|
@ -39,7 +40,8 @@ function ClaimTilesDiscover(props: Props) {
|
|||
claimSearchByQuery,
|
||||
showNsfw,
|
||||
hideReposts,
|
||||
hiddenUris,
|
||||
blockedUris,
|
||||
mutedUris,
|
||||
// Below are options to pass that are forwarded to claim_search
|
||||
tags,
|
||||
channelIds,
|
||||
|
@ -60,6 +62,7 @@ function ClaimTilesDiscover(props: Props) {
|
|||
const urlParams = new URLSearchParams(location.search);
|
||||
const feeAmountInUrl = urlParams.get('fee_amount');
|
||||
const feeAmountParam = feeAmountInUrl || feeAmount;
|
||||
const mutedAndBlockedChannelIds = Array.from(new Set(mutedUris.concat(blockedUris).map((uri) => uri.split('#')[1])));
|
||||
const options: {
|
||||
page_size: number,
|
||||
no_totals: boolean,
|
||||
|
@ -86,10 +89,7 @@ function ClaimTilesDiscover(props: Props) {
|
|||
not_tags: !showNsfw ? MATURE_TAGS : [],
|
||||
any_languages: languages,
|
||||
channel_ids: channelIds || [],
|
||||
not_channel_ids:
|
||||
notChannelIds ||
|
||||
// If channelIds were passed in, we don't need not_channel_ids
|
||||
(!channelIds && hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : []),
|
||||
not_channel_ids: notChannelIds || (!channelIds ? mutedAndBlockedChannelIds : []),
|
||||
order_by: orderBy || ['trending_group', 'trending_mixed'],
|
||||
};
|
||||
|
||||
|
@ -108,7 +108,7 @@ function ClaimTilesDiscover(props: Props) {
|
|||
// https://github.com/lbryio/lbry-desktop/issues/3774
|
||||
if (hideReposts) {
|
||||
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 {
|
||||
options.claim_type = ['stream', 'channel'];
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ function ClaimTilesDiscover(props: Props) {
|
|||
return (
|
||||
<ul className="claim-grid">
|
||||
{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 />)}
|
||||
</ul>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/setting
|
|||
import { selectHasNavigated, selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import Header from './view';
|
||||
|
||||
const select = state => ({
|
||||
const select = (state) => ({
|
||||
language: selectLanguage(state),
|
||||
balance: selectBalance(state),
|
||||
roundedSpendableBalance: formatCredits(selectBalance(state), 2, true),
|
||||
|
@ -27,10 +27,9 @@ const select = state => ({
|
|||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
const perform = (dispatch) => ({
|
||||
setClientSetting: (key, value, push) => dispatch(doSetClientSetting(key, value, push)),
|
||||
signOut: () => dispatch(doSignOut()),
|
||||
openChannelCreate: () => dispatch(doOpenModal(MODALS.CREATE_CHANNEL)),
|
||||
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
|
||||
clearEmailEntry: () => dispatch(doClearEmailEntry()),
|
||||
clearPasswordEntry: () => dispatch(doClearPasswordEntry()),
|
||||
|
|
|
@ -33,8 +33,8 @@ type Props = {
|
|||
index: number,
|
||||
length: number,
|
||||
location: { pathname: string },
|
||||
push: string => void,
|
||||
replace: string => void,
|
||||
push: (string) => void,
|
||||
replace: (string) => void,
|
||||
},
|
||||
currentTheme: string,
|
||||
automaticDarkModeEnabled: boolean,
|
||||
|
@ -52,13 +52,12 @@ type Props = {
|
|||
syncError: ?string,
|
||||
emailToVerify?: string,
|
||||
signOut: () => void,
|
||||
openChannelCreate: () => void,
|
||||
openSignOutModal: () => void,
|
||||
clearEmailEntry: () => void,
|
||||
clearPasswordEntry: () => void,
|
||||
hasNavigated: boolean,
|
||||
sidebarOpen: boolean,
|
||||
setSidebarOpen: boolean => void,
|
||||
setSidebarOpen: (boolean) => void,
|
||||
isAbsoluteSideNavHidden: boolean,
|
||||
hideCancel: boolean,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
|
@ -181,7 +180,7 @@ const Header = (props: Props) => {
|
|||
label={hideBalance || Number(roundedBalance) === 0 ? __('Your Wallet') : roundedBalance}
|
||||
icon={ICONS.LBC}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
|
@ -197,7 +196,7 @@ const Header = (props: Props) => {
|
|||
// @endif
|
||||
})}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
onDoubleClick={(e) => {
|
||||
remote.getCurrentWindow().maximize();
|
||||
}}
|
||||
// @endif
|
||||
|
@ -250,7 +249,7 @@ const Header = (props: Props) => {
|
|||
if (history.location.pathname === '/') window.location.reload();
|
||||
}}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
|
@ -308,7 +307,7 @@ const Header = (props: Props) => {
|
|||
icon={ICONS.REMOVE}
|
||||
{...closeButtonNavigationProps}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
|
@ -326,8 +325,8 @@ const Header = (props: Props) => {
|
|||
type HeaderMenuButtonProps = {
|
||||
authenticated: boolean,
|
||||
notificationsEnabled: boolean,
|
||||
history: { push: string => void },
|
||||
handleThemeToggle: string => void,
|
||||
history: { push: (string) => void },
|
||||
handleThemeToggle: (string) => void,
|
||||
currentTheme: string,
|
||||
activeChannelUrl: ?string,
|
||||
openSignOutModal: () => void,
|
||||
|
@ -357,7 +356,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
|||
title={__('Publish a file, or create a channel')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
|
@ -386,7 +385,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
|||
title={__('Settings')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon mobile-hidden"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
|
@ -419,7 +418,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
|||
'header__navigation-item--profile-pic': activeChannelUrl,
|
||||
})}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
|
|
|
@ -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;
|
|
@ -31,7 +31,6 @@ import ModalCommentAcknowledgement from 'modal/modalCommentAcknowledgement';
|
|||
import ModalWalletSend from 'modal/modalWalletSend';
|
||||
import ModalWalletReceive from 'modal/modalWalletReceive';
|
||||
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
||||
import ModalCreateChannel from 'modal/modalChannelCreate';
|
||||
import ModalSetReferrer from 'modal/modalSetReferrer';
|
||||
import ModalSignOut from 'modal/modalSignOut';
|
||||
import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate';
|
||||
|
@ -129,8 +128,6 @@ function ModalRouter(props: Props) {
|
|||
return <ModalWalletReceive {...modalProps} />;
|
||||
case MODALS.YOUTUBE_WELCOME:
|
||||
return <ModalYoutubeWelcome />;
|
||||
case MODALS.CREATE_CHANNEL:
|
||||
return <ModalCreateChannel {...modalProps} />;
|
||||
case MODALS.SET_REFERRER:
|
||||
return <ModalSetReferrer {...modalProps} />;
|
||||
case MODALS.SIGN_OUT:
|
||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
|||
import { selectBalance } from 'lbry-redux';
|
||||
import ChannelNew from './view';
|
||||
|
||||
const select = state => ({
|
||||
const select = (state) => ({
|
||||
balance: selectBalance(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -21,7 +21,12 @@ function ChannelNew(props: Props) {
|
|||
<Page noSideNavigation noFooter backout={{ title: __('Create a channel'), backLabel: __('Cancel') }}>
|
||||
{emptyBalance && <YrblWalletEmpty />}
|
||||
|
||||
<ChannelEdit disabled={emptyBalance} onDone={() => push(redirectUrl || `/$/${PAGES.CHANNELS}`)} />
|
||||
<ChannelEdit
|
||||
disabled={emptyBalance}
|
||||
onDone={() => {
|
||||
push(redirectUrl || `/$/${PAGES.CHANNELS}`);
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,14 +24,14 @@ export default function ChannelsPage(props: Props) {
|
|||
const { channels, channelUrls, fetchChannelListMine, fetchingChannels, youtubeChannels } = props;
|
||||
const [rewardData, setRewardData] = React.useState();
|
||||
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(() => {
|
||||
fetchChannelListMine();
|
||||
}, [fetchChannelListMine, hasPendingChannels]);
|
||||
|
||||
useEffect(() => {
|
||||
Lbryio.call('user_rewards', 'view_rate').then(data => setRewardData(data));
|
||||
Lbryio.call('user_rewards', 'view_rate').then((data) => setRewardData(data));
|
||||
}, [setRewardData]);
|
||||
|
||||
return (
|
||||
|
@ -52,7 +52,7 @@ export default function ChannelsPage(props: Props) {
|
|||
}
|
||||
loading={fetchingChannels}
|
||||
uris={channelUrls}
|
||||
renderActions={claim => {
|
||||
renderActions={(claim) => {
|
||||
const claimsInChannel = claim.meta.claims_in_channel;
|
||||
return claimsInChannel === 0 ? (
|
||||
<span />
|
||||
|
@ -67,7 +67,7 @@ export default function ChannelsPage(props: Props) {
|
|||
</div>
|
||||
);
|
||||
}}
|
||||
renderProperties={claim => {
|
||||
renderProperties={(claim) => {
|
||||
const claimsInChannel = claim.meta.claims_in_channel;
|
||||
if (!claim || claimsInChannel === 0) {
|
||||
return null;
|
||||
|
@ -76,7 +76,7 @@ export default function ChannelsPage(props: Props) {
|
|||
const channelRewardData =
|
||||
rewardData &&
|
||||
rewardData.rates &&
|
||||
rewardData.rates.find(data => {
|
||||
rewardData.rates.find((data) => {
|
||||
return data.channel_claim_id === claim.claim_id;
|
||||
});
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as REACTION_TYPES from 'constants/reactions';
|
||||
import { Lbry, buildURI, selectClaimsByUri, selectMyChannelClaims } from 'lbry-redux';
|
||||
import { Lbry, parseURI, buildURI, selectClaimsByUri, selectMyChannelClaims } from 'lbry-redux';
|
||||
import { doToast, doSeeNotifications } from 'redux/actions/notifications';
|
||||
import {
|
||||
makeSelectCommentIdsForUri,
|
||||
makeSelectMyReactionsForComment,
|
||||
makeSelectOthersReactionsForComment,
|
||||
selectPendingCommentReacts,
|
||||
selectModerationBlockList,
|
||||
} from 'redux/selectors/comments';
|
||||
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
|
@ -238,9 +239,15 @@ export function doCommentCreate(comment: string = '', claim_id: string = '', par
|
|||
type: ACTIONS.COMMENT_CREATE_FAILED,
|
||||
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(
|
||||
doToast({
|
||||
message: 'Unable to create comment, please try again later.',
|
||||
message: toastMessage,
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
|
@ -568,3 +575,42 @@ export function doFetchModBlockedList() {
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -372,9 +372,9 @@ $metadata-z-index: 1;
|
|||
.channel-staked__wrapper {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
padding: 0.25rem;
|
||||
padding: 0.2rem;
|
||||
bottom: -0.75rem;
|
||||
left: -0.75rem;
|
||||
left: -0.8rem;
|
||||
background-color: var(--color-card-background);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
// @flow
|
||||
|
||||
const EMOJI_REGEX = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*/g;
|
||||
|
||||
export function toHex(str: string): string {
|
||||
const array = Array.from(str);
|
||||
|
||||
|
@ -9,14 +7,9 @@ export function toHex(str: string): string {
|
|||
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
const val = array[i];
|
||||
|
||||
const isEmoji = EMOJI_REGEX.test(val);
|
||||
|
||||
const utf = isEmoji
|
||||
? toUTF8Array(val)
|
||||
.map((num) => num.toString(16))
|
||||
.join('')
|
||||
: val.charCodeAt(0).toString(16);
|
||||
const utf = toUTF8Array(val)
|
||||
.map((num) => num.toString(16))
|
||||
.join('');
|
||||
|
||||
result += utf;
|
||||
}
|
||||
|
@ -24,7 +17,9 @@ export function toHex(str: string): string {
|
|||
return result;
|
||||
}
|
||||
|
||||
function toUTF8Array(str) {
|
||||
// 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);
|
||||
|
@ -46,5 +41,6 @@ function toUTF8Array(str) {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
return utf8;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue