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,
|
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;
|
|
|
@ -129,7 +129,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
(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 = 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 langParam = urlParams.get(CS.LANGUAGE_KEY) || null;
|
||||||
const languageParams = searchInLanguage
|
const languageParams = searchInLanguage
|
||||||
|
|
|
@ -33,7 +33,7 @@ function ClaimMenuList(props: Props) {
|
||||||
? claim.permanent_url
|
? claim.permanent_url
|
||||||
: claim.signing_channel && claim.signing_channel.permanent_url);
|
: claim.signing_channel && claim.signing_channel.permanent_url);
|
||||||
|
|
||||||
if (!channelUri) {
|
if (!channelUri || claimIsMine) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ 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 ClaimMenuList from 'component/claimMenuList';
|
||||||
import ChannelStakedIndicator from 'component/channelStakedIndicator';
|
|
||||||
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';
|
||||||
|
@ -329,8 +328,8 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
<div className="claim-preview__primary-actions">
|
<div className="claim-preview__primary-actions">
|
||||||
{!isChannelUri && signingChannel && (
|
{!isChannelUri && signingChannel && (
|
||||||
<div className="claim-preview__channel-staked">
|
<div className="claim-preview__channel-staked">
|
||||||
<ChannelThumbnail uri={signingChannel.permanent_url} hideStakedIndicator />
|
<ChannelThumbnail uri={signingChannel.permanent_url} />
|
||||||
<ChannelStakedIndicator uri={signingChannel.permanent_url} inline />
|
{/* <ChannelStakedIndicator uri={signingChannel.permanent_url} inline /> */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
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 { selectMutedChannels } 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';
|
||||||
|
@ -10,7 +11,8 @@ const select = (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: selectMutedChannels(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,7 +19,8 @@ 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>,
|
notChannelIds?: Array<string>,
|
||||||
|
@ -39,7 +40,8 @@ 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,
|
||||||
|
@ -60,6 +62,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 +89,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: notChannelIds || (!channelIds ? 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 +108,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 +143,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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 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';
|
||||||
|
@ -129,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:
|
||||||
|
|
|
@ -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,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, buildURI, selectClaimsByUri, selectMyChannelClaims } 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';
|
||||||
|
@ -238,9 +239,15 @@ export function doCommentCreate(comment: string = '', claim_id: string = '', par
|
||||||
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,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -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 {
|
.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%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
// @flow
|
// @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 {
|
export function toHex(str: string): string {
|
||||||
const array = Array.from(str);
|
const array = Array.from(str);
|
||||||
|
|
||||||
|
@ -9,14 +7,9 @@ export function toHex(str: string): string {
|
||||||
|
|
||||||
for (var i = 0; i < array.length; i++) {
|
for (var i = 0; i < array.length; i++) {
|
||||||
const val = array[i];
|
const val = array[i];
|
||||||
|
const utf = toUTF8Array(val)
|
||||||
const isEmoji = EMOJI_REGEX.test(val);
|
.map((num) => num.toString(16))
|
||||||
|
.join('');
|
||||||
const utf = isEmoji
|
|
||||||
? toUTF8Array(val)
|
|
||||||
.map((num) => num.toString(16))
|
|
||||||
.join('')
|
|
||||||
: val.charCodeAt(0).toString(16);
|
|
||||||
|
|
||||||
result += utf;
|
result += utf;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +17,9 @@ export function toHex(str: string): string {
|
||||||
return result;
|
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 = [];
|
var utf8 = [];
|
||||||
for (var i = 0; i < str.length; i++) {
|
for (var i = 0; i < str.length; i++) {
|
||||||
var charcode = str.charCodeAt(i);
|
var charcode = str.charCodeAt(i);
|
||||||
|
@ -46,5 +41,6 @@ function toUTF8Array(str) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return utf8;
|
return utf8;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue