New repost flow
Clearer display of takeover amounts Repost from empty search result, from top page, or from claim review changes final touches bump empty comment copy they emptier validation cleanup extra
This commit is contained in:
parent
73dea00e41
commit
99ab165a8f
31 changed files with 938 additions and 75 deletions
|
@ -1527,6 +1527,9 @@
|
||||||
"Explore": "Explore",
|
"Explore": "Explore",
|
||||||
"There is a bug... somewhere": "There is a bug... somewhere",
|
"There is a bug... somewhere": "There is a bug... somewhere",
|
||||||
"Try refreshing to fix the issue. If that doesn't work, email help@lbry.com for support.": "Try refreshing to fix the issue. If that doesn't work, email help@lbry.com for support.",
|
"Try refreshing to fix the issue. If that doesn't work, email help@lbry.com for support.": "Try refreshing to fix the issue. If that doesn't work, email help@lbry.com for support.",
|
||||||
|
"No Results": "No Results",
|
||||||
|
"Content preview": "Content preview",
|
||||||
|
"Repost url": "Repost url",
|
||||||
"Close sidebar - hide channels you are following": "Close sidebar - hide channels you are following",
|
"Close sidebar - hide channels you are following": "Close sidebar - hide channels you are following",
|
||||||
"Expand sidebar - view channels you are following.": "Expand sidebar - view channels you are following.",
|
"Expand sidebar - view channels you are following.": "Expand sidebar - view channels you are following.",
|
||||||
"--end--": "--end--"
|
"--end--": "--end--"
|
||||||
|
|
|
@ -14,7 +14,7 @@ function ClaimEffectiveAmount(props: Props) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <CreditAmount amount={Number(claim.meta.effective_amount)} />;
|
return <CreditAmount amount={Number(claim.meta.effective_amount || claim.amount)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ClaimEffectiveAmount;
|
export default ClaimEffectiveAmount;
|
||||||
|
|
30
ui/component/claimPreview/claim-preview-loading.jsx
Normal file
30
ui/component/claimPreview/claim-preview-loading.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isChannel: boolean,
|
||||||
|
type: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ClaimPreviewLoading(props: Props) {
|
||||||
|
const { isChannel, type } = props;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames('claim-preview__wrapper', {
|
||||||
|
'claim-preview__wrapper--channel': isChannel && type !== 'inline',
|
||||||
|
'claim-preview__wrapper--inline': type === 'inline',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={classnames('claim-preview', { 'claim-preview--large': type === 'large' })}>
|
||||||
|
<div className="placeholder media__thumb" />
|
||||||
|
<div className="placeholder__wrapper">
|
||||||
|
<div className="placeholder claim-preview__title" />
|
||||||
|
<div className="placeholder media__subtitle" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClaimPreviewLoading;
|
32
ui/component/claimPreview/claim-preview-no-content.jsx
Normal file
32
ui/component/claimPreview/claim-preview-no-content.jsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// @flow
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import Empty from 'component/common/empty';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isChannel: boolean,
|
||||||
|
type: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ClaimPreviewNoContent(props: Props) {
|
||||||
|
const { isChannel, type } = props;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames('claim-preview__wrapper', {
|
||||||
|
'claim-preview__wrapper--channel': isChannel && type !== 'inline',
|
||||||
|
'claim-preview__wrapper--inline': type === 'inline',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classnames('claim-preview', {
|
||||||
|
'claim-preview--large': type === 'large',
|
||||||
|
'claim-preview__empty': true,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Empty text={__('Nothing found here. Like big tech ethics.')} />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClaimPreviewNoContent;
|
35
ui/component/claimPreview/claim-preview-no-mature.jsx
Normal file
35
ui/component/claimPreview/claim-preview-no-mature.jsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// @flow
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import Empty from 'component/common/empty';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isChannel: boolean,
|
||||||
|
type: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ClaimPreviewNoMature(props: Props) {
|
||||||
|
const { isChannel, type } = props;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames('claim-preview__wrapper', {
|
||||||
|
'claim-preview__wrapper--channel': isChannel && type !== 'inline',
|
||||||
|
'claim-preview__wrapper--inline': type === 'inline',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={classnames('claim-preview', { 'claim-preview--large': type === 'large' })}>
|
||||||
|
<div className="media__thumb" />
|
||||||
|
<div
|
||||||
|
className={classnames('claim-preview', {
|
||||||
|
'claim-preview--large': type === 'large',
|
||||||
|
'claim-preview__empty': true,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Empty text={__('Mature content hidden by your preferences')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClaimPreviewNoMature;
|
|
@ -22,6 +22,9 @@ 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 ClaimPreviewLoading from './claim-preview-loading';
|
||||||
|
import ClaimPreviewNoMature from './claim-preview-no-mature';
|
||||||
|
import ClaimPreviewNoContent from './claim-preview-no-content';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -58,6 +61,7 @@ type Props = {
|
||||||
getFile: string => void,
|
getFile: string => void,
|
||||||
customShouldHide?: Claim => boolean,
|
customShouldHide?: Claim => boolean,
|
||||||
showUnresolvedClaim?: boolean,
|
showUnresolvedClaim?: boolean,
|
||||||
|
showNullPlaceholder?: boolean,
|
||||||
includeSupportAction?: boolean,
|
includeSupportAction?: boolean,
|
||||||
hideActions?: boolean,
|
hideActions?: boolean,
|
||||||
renderActions?: Claim => ?Node,
|
renderActions?: Claim => ?Node,
|
||||||
|
@ -94,6 +98,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
streamingUrl,
|
streamingUrl,
|
||||||
customShouldHide,
|
customShouldHide,
|
||||||
showUnresolvedClaim,
|
showUnresolvedClaim,
|
||||||
|
showNullPlaceholder,
|
||||||
includeSupportAction,
|
includeSupportAction,
|
||||||
hideActions = false,
|
hideActions = false,
|
||||||
renderActions,
|
renderActions,
|
||||||
|
@ -195,29 +200,22 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
}
|
}
|
||||||
}, [isValid, isResolvingUri, uri, resolveUri, shouldFetch]);
|
}, [isValid, isResolvingUri, uri, resolveUri, shouldFetch]);
|
||||||
|
|
||||||
if (shouldHide) {
|
if (shouldHide && !showNullPlaceholder) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (placeholder === 'loading' || claim === undefined || (isResolvingUri && !claim)) {
|
if (placeholder === 'loading' || (isResolvingUri && !claim)) {
|
||||||
return (
|
return <ClaimPreviewLoading isChannel={isChannel} type={type} />;
|
||||||
<li
|
|
||||||
disabled
|
|
||||||
className={classnames('claim-preview__wrapper', {
|
|
||||||
'claim-preview__wrapper--channel': isChannel && type !== 'inline',
|
|
||||||
'claim-preview__wrapper--inline': type === 'inline',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className={classnames('claim-preview', { 'claim-preview--large': type === 'large' })}>
|
|
||||||
<div className="placeholder media__thumb" />
|
|
||||||
<div className="placeholder__wrapper">
|
|
||||||
<div className="placeholder claim-preview__title" />
|
|
||||||
<div className="placeholder media__subtitle" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (claim && showNullPlaceholder && shouldHide && nsfw) {
|
||||||
|
return <ClaimPreviewNoMature isChannel={isChannel} type={type} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!claim && showNullPlaceholder) {
|
||||||
|
return <ClaimPreviewNoContent isChannel={isChannel} type={type} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (!shouldFetch && showUnresolvedClaim && !isResolvingUri && claim === null) {
|
if (!shouldFetch && showUnresolvedClaim && !isResolvingUri && claim === null) {
|
||||||
return <AbandonedChannelPreview uri={uri} type />;
|
return <AbandonedChannelPreview uri={uri} type />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import CommentCreate from 'component/commentCreate';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import { ENABLE_COMMENT_REACTIONS } from 'config';
|
import { ENABLE_COMMENT_REACTIONS } from 'config';
|
||||||
import { sortComments } from 'util/comments';
|
import { sortComments } from 'util/comments';
|
||||||
|
import Empty from 'component/common/empty';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
comments: Array<Comment>,
|
comments: Array<Comment>,
|
||||||
|
@ -58,7 +59,7 @@ function CommentList(props: Props) {
|
||||||
Boolean(reactionsById) || !ENABLE_COMMENT_REACTIONS
|
Boolean(reactionsById) || !ENABLE_COMMENT_REACTIONS
|
||||||
);
|
);
|
||||||
const linkedCommentId = linkedComment && linkedComment.comment_id;
|
const linkedCommentId = linkedComment && linkedComment.comment_id;
|
||||||
const hasNoComments = totalComments === 0;
|
const hasNoComments = !totalComments;
|
||||||
const moreBelow = totalComments - end > 0;
|
const moreBelow = totalComments - end > 0;
|
||||||
const isMyComment = (channelId: string): boolean => {
|
const isMyComment = (channelId: string): boolean => {
|
||||||
if (myChannels != null && channelId != null) {
|
if (myChannels != null && channelId != null) {
|
||||||
|
@ -210,7 +211,7 @@ function CommentList(props: Props) {
|
||||||
<>
|
<>
|
||||||
<CommentCreate uri={uri} />
|
<CommentCreate uri={uri} />
|
||||||
|
|
||||||
{!isFetchingComments && hasNoComments && <div className="main--empty">{__('Be the first to comment!')}</div>}
|
{!isFetchingComments && hasNoComments && <Empty text={__('That was pretty deep. What do you think?')} />}
|
||||||
|
|
||||||
<ul className="comments" ref={commentRef}>
|
<ul className="comments" ref={commentRef}>
|
||||||
{comments &&
|
{comments &&
|
||||||
|
|
30
ui/component/common/empty.jsx
Normal file
30
ui/component/common/empty.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
text: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Empty extends React.PureComponent<Props> {
|
||||||
|
static defaultProps = {
|
||||||
|
text: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { text } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="empty__wrap">
|
||||||
|
<div>
|
||||||
|
{text && (
|
||||||
|
<div className="empty__content">
|
||||||
|
<p className="empty__text">{text}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Empty;
|
|
@ -74,7 +74,7 @@ function FileActions(props: Props) {
|
||||||
push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`);
|
push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`);
|
||||||
doToast({ message: __('A channel is required to repost on %SITE_NAME%', { SITE_NAME }) });
|
doToast({ message: __('A channel is required to repost on %SITE_NAME%', { SITE_NAME }) });
|
||||||
} else {
|
} else {
|
||||||
openModal(MODALS.REPOST, { uri });
|
push(`/$/${PAGES.REPOST_NEW}?from=${encodeURIComponent(uri)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -205,7 +205,7 @@ const Header = (props: Props) => {
|
||||||
icon={ICONS.ARROW_LEFT}
|
icon={ICONS.ARROW_LEFT}
|
||||||
/>
|
/>
|
||||||
{backTitle && <h1 className="header__auth-title">{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>}
|
{backTitle && <h1 className="header__auth-title">{isMobile ? simpleBackTitle || backTitle : backTitle}</h1>}
|
||||||
{authenticated ? (
|
{authenticated || !IS_WEB ? (
|
||||||
<Button
|
<Button
|
||||||
aria-label={__('Your wallet')}
|
aria-label={__('Your wallet')}
|
||||||
navigate={`/$/${PAGES.WALLET}`}
|
navigate={`/$/${PAGES.WALLET}`}
|
||||||
|
|
45
ui/component/repostCreate/index.js
Normal file
45
ui/component/repostCreate/index.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doHideModal } from 'redux/actions/app';
|
||||||
|
import {
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
makeSelectTitleForUri,
|
||||||
|
selectBalance,
|
||||||
|
selectMyChannelClaims,
|
||||||
|
doRepost,
|
||||||
|
selectRepostError,
|
||||||
|
selectRepostLoading,
|
||||||
|
doClearRepostError,
|
||||||
|
selectMyClaimsWithoutChannels,
|
||||||
|
doCheckPublishNameAvailability,
|
||||||
|
doCheckPendingClaims,
|
||||||
|
makeSelectEffectiveAmountForUri,
|
||||||
|
makeSelectIsUriResolving,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
import RepostCreate from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
channels: selectMyChannelClaims(state),
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
passedRepostClaim: makeSelectClaimForUri(props.name)(state),
|
||||||
|
passedRepostAmount: makeSelectEffectiveAmountForUri(props.name)(state),
|
||||||
|
enteredContentClaim: makeSelectClaimForUri(props.contentUri)(state),
|
||||||
|
enteredRepostClaim: makeSelectClaimForUri(props.repostUri)(state),
|
||||||
|
enteredRepostAmount: makeSelectEffectiveAmountForUri(props.repostUri)(state),
|
||||||
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
|
balance: selectBalance(state),
|
||||||
|
error: selectRepostError(state),
|
||||||
|
reposting: selectRepostLoading(state),
|
||||||
|
myClaims: selectMyClaimsWithoutChannels(state),
|
||||||
|
isResolvingPassedRepost: props.name && makeSelectIsUriResolving(`lbry://${props.name}`)(state),
|
||||||
|
isResolvingEnteredRepost: props.repostUri && makeSelectIsUriResolving(`lbry://${props.repostUri}`)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, {
|
||||||
|
doHideModal,
|
||||||
|
doRepost,
|
||||||
|
doClearRepostError,
|
||||||
|
doToast,
|
||||||
|
doCheckPublishNameAvailability,
|
||||||
|
doCheckPendingClaims,
|
||||||
|
})(RepostCreate);
|
416
ui/component/repostCreate/view.jsx
Normal file
416
ui/component/repostCreate/view.jsx
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import { CHANNEL_NEW, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
|
||||||
|
import React from 'react';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import SelectChannel from 'component/selectChannel';
|
||||||
|
import { FormField } from 'component/common/form';
|
||||||
|
import { parseURI, isNameValid, creditsToString, isURIValid, normalizeURI } from 'lbry-redux';
|
||||||
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
|
import analytics from 'analytics';
|
||||||
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
|
import ClaimPreview from 'component/claimPreview';
|
||||||
|
import { URL as SITE_URL, URL_LOCAL, URL_DEV } from 'config';
|
||||||
|
import HelpLink from 'component/common/help-link';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
doToast: ({ message: string }) => void,
|
||||||
|
doClearRepostError: () => void,
|
||||||
|
doRepost: StreamRepostOptions => Promise<*>,
|
||||||
|
title: string, //
|
||||||
|
claim?: StreamClaim,
|
||||||
|
enteredContentClaim?: StreamClaim,
|
||||||
|
balance: number,
|
||||||
|
channels: ?Array<ChannelClaim>,
|
||||||
|
doCheckPublishNameAvailability: string => Promise<*>,
|
||||||
|
error: ?string,
|
||||||
|
reposting: boolean,
|
||||||
|
uri: string,
|
||||||
|
name: string,
|
||||||
|
contentUri: string,
|
||||||
|
setRepostUri: string => void,
|
||||||
|
setContentUri: string => void,
|
||||||
|
doCheckPendingClaims: () => void,
|
||||||
|
redirectUri?: string,
|
||||||
|
passedRepostAmount: number,
|
||||||
|
enteredRepostAmount: number,
|
||||||
|
isResolvingPassedRepost: boolean,
|
||||||
|
isResolvingEnteredRepost: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
function RepostCreate(props: Props) {
|
||||||
|
const {
|
||||||
|
redirectUri,
|
||||||
|
doToast,
|
||||||
|
doClearRepostError,
|
||||||
|
doRepost,
|
||||||
|
claim,
|
||||||
|
enteredContentClaim,
|
||||||
|
balance,
|
||||||
|
channels,
|
||||||
|
reposting,
|
||||||
|
doCheckPublishNameAvailability,
|
||||||
|
uri, // ?from
|
||||||
|
name, // ?to
|
||||||
|
contentUri,
|
||||||
|
setRepostUri,
|
||||||
|
setContentUri,
|
||||||
|
doCheckPendingClaims,
|
||||||
|
enteredRepostAmount,
|
||||||
|
passedRepostAmount,
|
||||||
|
isResolvingPassedRepost,
|
||||||
|
isResolvingEnteredRepost,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const defaultName = name || (claim && claim.name) || '';
|
||||||
|
const contentClaimId = claim && claim.claim_id;
|
||||||
|
const enteredClaimId = enteredContentClaim && enteredContentClaim.claim_id;
|
||||||
|
const [repostChannel, setRepostChannel] = usePersistedState('repost-channel', 'anonymous');
|
||||||
|
|
||||||
|
const [repostBid, setRepostBid] = React.useState(0.01);
|
||||||
|
const [repostBidError, setRepostBidError] = React.useState(undefined);
|
||||||
|
// const [bidChanged, setBidChanged] = React.useState(false);
|
||||||
|
const [takeoverAmount, setTakeoverAmount] = React.useState(0);
|
||||||
|
const [enteredRepostName, setEnteredRepostName] = React.useState(defaultName);
|
||||||
|
const [available, setAvailable] = React.useState(true);
|
||||||
|
const [enteredContent, setEnteredContentUri] = React.useState(undefined);
|
||||||
|
const [contentError, setContentError] = React.useState('');
|
||||||
|
|
||||||
|
const { replace, goBack } = useHistory();
|
||||||
|
const resolvingRepost = isResolvingEnteredRepost || isResolvingPassedRepost;
|
||||||
|
const repostUrlName = `lbry://${
|
||||||
|
!repostChannel || repostChannel === CHANNEL_NEW || repostChannel === 'anonymous' ? '' : `${repostChannel}/`
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const contentFirstRender = React.useRef(true);
|
||||||
|
const setAutoRepostBid = amount => {
|
||||||
|
if (balance > amount) {
|
||||||
|
if (amount > 5) {
|
||||||
|
setRepostBid(Number(amount.toFixed(2)));
|
||||||
|
} else {
|
||||||
|
setRepostBid(5);
|
||||||
|
}
|
||||||
|
} else if (balance) {
|
||||||
|
setRepostBid(0.01);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSearchUri(value) {
|
||||||
|
const WEB_DEV_PREFIX = `${URL_DEV}/`;
|
||||||
|
const WEB_LOCAL_PREFIX = `${URL_LOCAL}/`;
|
||||||
|
const WEB_PROD_PREFIX = `${SITE_URL}/`;
|
||||||
|
const ODYSEE_PREFIX = `https://odysee.com/`;
|
||||||
|
const includesLbryTvProd = value.includes(WEB_PROD_PREFIX);
|
||||||
|
const includesOdysee = value.includes(ODYSEE_PREFIX);
|
||||||
|
const includesLbryTvLocal = value.includes(WEB_LOCAL_PREFIX);
|
||||||
|
const includesLbryTvDev = value.includes(WEB_DEV_PREFIX);
|
||||||
|
const wasCopiedFromWeb = includesLbryTvDev || includesLbryTvLocal || includesLbryTvProd || includesOdysee;
|
||||||
|
const isLbryUrl = value.startsWith('lbry://') && value !== 'lbry://';
|
||||||
|
const error = '';
|
||||||
|
|
||||||
|
const addLbryIfNot = term => {
|
||||||
|
return term.startsWith('lbry://') ? term : `lbry://${term}`;
|
||||||
|
};
|
||||||
|
if (wasCopiedFromWeb) {
|
||||||
|
let prefix = WEB_PROD_PREFIX;
|
||||||
|
if (includesLbryTvLocal) prefix = WEB_LOCAL_PREFIX;
|
||||||
|
if (includesLbryTvDev) prefix = WEB_DEV_PREFIX;
|
||||||
|
if (includesOdysee) prefix = ODYSEE_PREFIX;
|
||||||
|
|
||||||
|
let query = (value && value.slice(prefix.length).replace(/:/g, '#')) || '';
|
||||||
|
try {
|
||||||
|
const lbryUrl = `lbry://${query}`;
|
||||||
|
parseURI(lbryUrl);
|
||||||
|
return [lbryUrl, null];
|
||||||
|
} catch (e) {
|
||||||
|
return [query, 'error'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLbryUrl) {
|
||||||
|
if (value.startsWith('@')) {
|
||||||
|
if (isNameValid(value.slice(1))) {
|
||||||
|
return [value, null];
|
||||||
|
} else {
|
||||||
|
return [value, error];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [addLbryIfNot(value), null];
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const isValid = isURIValid(value);
|
||||||
|
if (isValid) {
|
||||||
|
let uri;
|
||||||
|
try {
|
||||||
|
uri = normalizeURI(value);
|
||||||
|
} catch (e) {
|
||||||
|
return [value, null];
|
||||||
|
}
|
||||||
|
return [uri, null];
|
||||||
|
} else {
|
||||||
|
return [value, null];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return [value, 'error'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// repostName
|
||||||
|
let repostNameError;
|
||||||
|
if (!enteredRepostName) {
|
||||||
|
repostNameError = __('A name is required');
|
||||||
|
} else if (!isNameValid(enteredRepostName, false)) {
|
||||||
|
repostNameError = INVALID_NAME_ERROR;
|
||||||
|
} else if (!available) {
|
||||||
|
repostNameError = __('You already have a claim with this name.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// contentName
|
||||||
|
let contentNameError;
|
||||||
|
if (!enteredContent && enteredContent !== undefined) {
|
||||||
|
contentNameError = __('A name is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// repostChannel
|
||||||
|
const channelStrings = channels && channels.map(channel => channel.permanent_url).join(',');
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!repostChannel && channelStrings) {
|
||||||
|
const channels = channelStrings.split(',');
|
||||||
|
const newChannelUrl = channels[0];
|
||||||
|
const { claimName } = parseURI(newChannelUrl);
|
||||||
|
setRepostChannel(claimName);
|
||||||
|
}
|
||||||
|
}, [channelStrings]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (enteredRepostName && isNameValid(enteredRepostName, false)) {
|
||||||
|
doCheckPublishNameAvailability(enteredRepostName).then(r => setAvailable(r));
|
||||||
|
}
|
||||||
|
}, [enteredRepostName, doCheckPublishNameAvailability]);
|
||||||
|
|
||||||
|
// takeover amount, bid suggestion
|
||||||
|
React.useEffect(() => {
|
||||||
|
const repostTakeoverAmount = Number(enteredRepostAmount)
|
||||||
|
? Number(enteredRepostAmount) + 0.01
|
||||||
|
: Number(passedRepostAmount) + 0.01;
|
||||||
|
|
||||||
|
if (repostTakeoverAmount) {
|
||||||
|
setTakeoverAmount(Number(repostTakeoverAmount.toFixed(2)));
|
||||||
|
setAutoRepostBid(repostTakeoverAmount);
|
||||||
|
}
|
||||||
|
}, [setTakeoverAmount, enteredRepostAmount, passedRepostAmount]);
|
||||||
|
|
||||||
|
// repost bid error
|
||||||
|
React.useEffect(() => {
|
||||||
|
let rBidError;
|
||||||
|
if (repostBid === 0) {
|
||||||
|
rBidError = __('Deposit cannot be 0');
|
||||||
|
} else if (balance === repostBid) {
|
||||||
|
rBidError = __('Please decrease your deposit to account for transaction fees');
|
||||||
|
} else if (balance < repostBid) {
|
||||||
|
rBidError = __('Deposit cannot be higher than your balance');
|
||||||
|
} else if (repostBid < MINIMUM_PUBLISH_BID) {
|
||||||
|
rBidError = __('Your deposit must be higher');
|
||||||
|
}
|
||||||
|
setRepostBidError(rBidError);
|
||||||
|
}, [setRepostBidError, repostBidError, repostBid]);
|
||||||
|
|
||||||
|
// setContentUri given enteredUri
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!enteredContent && !contentFirstRender.current) {
|
||||||
|
setContentError(__('A name is required'));
|
||||||
|
}
|
||||||
|
if (enteredContent) {
|
||||||
|
contentFirstRender.current = false;
|
||||||
|
const [searchContent, error] = getSearchUri(enteredContent);
|
||||||
|
if (error) {
|
||||||
|
setContentError(__('Something not quite right..'));
|
||||||
|
} else {
|
||||||
|
setContentError('');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { streamName, channelName, isChannel } = parseURI(searchContent);
|
||||||
|
if (!isChannel && streamName && isNameValid(streamName)) {
|
||||||
|
// contentNameValid = true;
|
||||||
|
setContentUri(searchContent);
|
||||||
|
} else if (isChannel && channelName && isNameValid(channelName)) {
|
||||||
|
// contentNameValid = true;
|
||||||
|
setContentUri(searchContent);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (enteredContent !== '@') setContentError('');
|
||||||
|
setContentUri(``);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [enteredContent, setContentUri, setContentError, parseURI, isNameValid]);
|
||||||
|
|
||||||
|
// setRepostName
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (enteredRepostName) {
|
||||||
|
let isValid = false;
|
||||||
|
try {
|
||||||
|
parseURI(enteredRepostName);
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
if (isValid) {
|
||||||
|
setRepostUri(enteredRepostName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [enteredRepostName, setRepostUri, parseURI]);
|
||||||
|
|
||||||
|
const repostClaimId = contentClaimId || enteredClaimId;
|
||||||
|
|
||||||
|
const getRedirect = (entered, passed, redirect) => {
|
||||||
|
if (redirect) {
|
||||||
|
return redirect;
|
||||||
|
} else if (entered) {
|
||||||
|
try {
|
||||||
|
const { claimName } = parseURI(entered);
|
||||||
|
return `/${claimName}`;
|
||||||
|
} catch (e) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
} else if (passed) {
|
||||||
|
try {
|
||||||
|
const { claimName } = parseURI(passed);
|
||||||
|
return `/${claimName}`;
|
||||||
|
} catch (e) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
const channelToRepostTo = channels && channels.find(channel => channel.name === repostChannel);
|
||||||
|
if (enteredRepostName && repostBid && repostClaimId) {
|
||||||
|
doRepost({
|
||||||
|
name: enteredRepostName,
|
||||||
|
bid: creditsToString(repostBid),
|
||||||
|
channel_id: channelToRepostTo ? channelToRepostTo.claim_id : undefined,
|
||||||
|
claim_id: repostClaimId,
|
||||||
|
}).then((repostClaim: StreamClaim) => {
|
||||||
|
doCheckPendingClaims();
|
||||||
|
analytics.apiLogPublish(repostClaim);
|
||||||
|
doToast({ message: __('Woohoo! Successfully reposted this claim.') });
|
||||||
|
replace(getRedirect(contentUri, uri, redirectUri));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelIt() {
|
||||||
|
doClearRepostError();
|
||||||
|
goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card
|
||||||
|
actions={
|
||||||
|
<div>
|
||||||
|
{uri && (
|
||||||
|
<fieldset-section>
|
||||||
|
<ClaimPreview key={uri} uri={uri} actions={''} type={'inline'} showNullPlaceholder />
|
||||||
|
</fieldset-section>
|
||||||
|
)}
|
||||||
|
{!uri && name && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
label={'Content to repost'}
|
||||||
|
type="text"
|
||||||
|
name="content_url"
|
||||||
|
value={enteredContent}
|
||||||
|
error={contentError}
|
||||||
|
onChange={event => setEnteredContentUri(event.target.value)}
|
||||||
|
placeholder={__('Enter a name or %domain% URL', { domain: SITE_URL })}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!uri && (
|
||||||
|
<fieldset-section>
|
||||||
|
<ClaimPreview key={contentUri} uri={contentUri} actions={''} type={'large'} showNullPlaceholder />
|
||||||
|
</fieldset-section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<React.Fragment>
|
||||||
|
<fieldset-section>
|
||||||
|
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
||||||
|
<fieldset-section>
|
||||||
|
<label htmlFor="auth_first_channel">
|
||||||
|
{repostNameError ? (
|
||||||
|
<span className="error__text">{repostNameError}</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
{__('Repost URL')}
|
||||||
|
<HelpLink href="https://lbry.com/faq/naming" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<div className="form-field__prefix">{repostUrlName}</div>
|
||||||
|
</fieldset-section>
|
||||||
|
<FormField
|
||||||
|
type="text"
|
||||||
|
name="repost_name"
|
||||||
|
value={enteredRepostName}
|
||||||
|
onChange={event => setEnteredRepostName(event.target.value)}
|
||||||
|
placeholder={__('Do a thing')}
|
||||||
|
/>
|
||||||
|
</fieldset-group>
|
||||||
|
</fieldset-section>
|
||||||
|
<SelectChannel
|
||||||
|
label={__('Channel to repost on')}
|
||||||
|
hideNew
|
||||||
|
channel={repostChannel}
|
||||||
|
onChannelChange={newChannel => setRepostChannel(newChannel)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
type="number"
|
||||||
|
name="repost_bid"
|
||||||
|
min="0"
|
||||||
|
step="any"
|
||||||
|
placeholder="0.123"
|
||||||
|
className="form-field--price-amount"
|
||||||
|
label={<LbcSymbol postfix={__('Support')} size={14} />}
|
||||||
|
value={repostBid}
|
||||||
|
error={repostBidError}
|
||||||
|
helper={__('Winning amount: %amount%', {
|
||||||
|
amount: Number(takeoverAmount).toFixed(2),
|
||||||
|
})}
|
||||||
|
disabled={!enteredRepostName || resolvingRepost}
|
||||||
|
onChange={event => setRepostBid(event.target.value)}
|
||||||
|
onWheel={e => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
|
||||||
|
<div className="section__actions">
|
||||||
|
<Button
|
||||||
|
icon={ICONS.REPOST}
|
||||||
|
disabled={
|
||||||
|
resolvingRepost ||
|
||||||
|
reposting ||
|
||||||
|
repostBidError ||
|
||||||
|
repostNameError ||
|
||||||
|
((!uri || enteredContent) && contentNameError) ||
|
||||||
|
(!uri && !enteredContentClaim)
|
||||||
|
}
|
||||||
|
button="primary"
|
||||||
|
label={reposting ? __('Reposting') : __('Repost')}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
/>
|
||||||
|
<Button button="link" label={__('Cancel')} onClick={cancelIt} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RepostCreate;
|
|
@ -43,6 +43,7 @@ import CreatorDashboard from 'page/creatorDashboard';
|
||||||
import RewardsVerifyPage from 'page/rewardsVerify';
|
import RewardsVerifyPage from 'page/rewardsVerify';
|
||||||
import CheckoutPage from 'page/checkoutPage';
|
import CheckoutPage from 'page/checkoutPage';
|
||||||
import ChannelNew from 'page/channelNew';
|
import ChannelNew from 'page/channelNew';
|
||||||
|
import RepostNew from 'page/repost';
|
||||||
import BuyPage from 'page/buy';
|
import BuyPage from 'page/buy';
|
||||||
import NotificationsPage from 'page/notifications';
|
import NotificationsPage from 'page/notifications';
|
||||||
import SignInWalletPasswordPage from 'page/signInWalletPassword';
|
import SignInWalletPasswordPage from 'page/signInWalletPassword';
|
||||||
|
@ -262,6 +263,7 @@ function AppRouter(props: Props) {
|
||||||
/>
|
/>
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.CHANNEL_NEW}`} component={ChannelNew} />
|
<PrivateRoute {...props} path={`/$/${PAGES.CHANNEL_NEW}`} component={ChannelNew} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.REPOST_NEW}`} component={RepostNew} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.UPLOADS}`} component={FileListPublished} />
|
<PrivateRoute {...props} path={`/$/${PAGES.UPLOADS}`} component={FileListPublished} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.CREATOR_DASHBOARD}`} component={CreatorDashboard} />
|
<PrivateRoute {...props} path={`/$/${PAGES.CREATOR_DASHBOARD}`} component={CreatorDashboard} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.UPLOAD}`} component={PublishPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.UPLOAD}`} component={PublishPage} />
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doResolveUris } from 'lbry-redux';
|
import { doResolveUris, doClearPublish, doPrepareEdit, selectPendingIds } from 'lbry-redux';
|
||||||
import { makeSelectWinningUriForQuery } from 'redux/selectors/search';
|
import { makeSelectWinningUriForQuery } from 'redux/selectors/search';
|
||||||
import SearchTopClaim from './view';
|
import SearchTopClaim from './view';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
winningUri: makeSelectWinningUriForQuery(props.query)(state),
|
winningUri: makeSelectWinningUriForQuery(props.query)(state),
|
||||||
|
pendingIds: selectPendingIds(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, {
|
const perform = dispatch => ({
|
||||||
doResolveUris,
|
beginPublish: name => {
|
||||||
})(SearchTopClaim);
|
dispatch(doClearPublish());
|
||||||
|
dispatch(doPrepareEdit({ name }));
|
||||||
|
dispatch(push(`/$/${PAGES.UPLOAD}`));
|
||||||
|
},
|
||||||
|
doResolveUris: uris => dispatch(doResolveUris(uris)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(SearchTopClaim);
|
||||||
|
|
|
@ -6,8 +6,11 @@ import { parseURI } from 'lbry-redux';
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ClaimEffectiveAmount from 'component/claimEffectiveAmount';
|
import ClaimEffectiveAmount from 'component/claimEffectiveAmount';
|
||||||
import HelpLink from 'component/common/help-link';
|
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
|
import { DOMAIN } from 'config';
|
||||||
|
import Yrbl from 'component/yrbl';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
query: string,
|
query: string,
|
||||||
|
@ -15,20 +18,26 @@ type Props = {
|
||||||
doResolveUris: (Array<string>) => void,
|
doResolveUris: (Array<string>) => void,
|
||||||
hideLink?: boolean,
|
hideLink?: boolean,
|
||||||
setChannelActive: boolean => void,
|
setChannelActive: boolean => void,
|
||||||
|
beginPublish: string => void,
|
||||||
|
pendingIds: Array<string>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SearchTopClaim(props: Props) {
|
export default function SearchTopClaim(props: Props) {
|
||||||
const { doResolveUris, query = '', winningUri, hideLink = false, setChannelActive } = props;
|
const { doResolveUris, query = '', winningUri, hideLink = false, setChannelActive, beginPublish } = props;
|
||||||
const uriFromQuery = `lbry://${query}`;
|
const uriFromQuery = `lbry://${query}`;
|
||||||
|
const { push } = useHistory();
|
||||||
|
let name;
|
||||||
let channelUriFromQuery;
|
let channelUriFromQuery;
|
||||||
let winningUriIsChannel;
|
let winningUriIsChannel;
|
||||||
try {
|
try {
|
||||||
const { isChannel } = parseURI(uriFromQuery);
|
const { isChannel, streamName, channelName } = parseURI(uriFromQuery);
|
||||||
const { isChannel: winnerIsChannel } = parseURI(winningUri);
|
const { isChannel: winnerIsChannel } = parseURI(winningUri);
|
||||||
winningUriIsChannel = winnerIsChannel;
|
winningUriIsChannel = winnerIsChannel;
|
||||||
if (!isChannel) {
|
if (!isChannel) {
|
||||||
channelUriFromQuery = `lbry://@${query}`;
|
channelUriFromQuery = `lbry://@${query}`;
|
||||||
|
name = streamName;
|
||||||
|
} else {
|
||||||
|
name = channelName;
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
|
@ -51,19 +60,27 @@ export default function SearchTopClaim(props: Props) {
|
||||||
}
|
}
|
||||||
}, [doResolveUris, uriFromQuery, channelUriFromQuery]);
|
}, [doResolveUris, uriFromQuery, channelUriFromQuery]);
|
||||||
|
|
||||||
if (!winningUri) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="search">
|
<section className="search">
|
||||||
<header className="search__header">
|
<header className="search__header">
|
||||||
|
{winningUri && (
|
||||||
<div className="claim-preview__actions--header">
|
<div className="claim-preview__actions--header">
|
||||||
<span className="media__uri">
|
<a
|
||||||
{__('Most supported')}
|
className="media__uri"
|
||||||
<HelpLink href="https://lbry.com/faq/tipping" />
|
href="https://lbry.com/faq/trending"
|
||||||
</span>
|
title={__('Learn more about LBRY Credits on %DOMAIN%', { DOMAIN })}
|
||||||
|
>
|
||||||
|
<I18nMessage
|
||||||
|
tokens={{
|
||||||
|
lbc: <LbcSymbol />,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Most supported %lbc%
|
||||||
|
</I18nMessage>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{winningUri && (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<ClaimPreview
|
<ClaimPreview
|
||||||
hideRepostLabel
|
hideRepostLabel
|
||||||
|
@ -77,7 +94,34 @@ export default function SearchTopClaim(props: Props) {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!hideLink && (
|
)}
|
||||||
|
{!winningUri && uriFromQuery && (
|
||||||
|
<div className="empty empty--centered">
|
||||||
|
<Yrbl
|
||||||
|
type="happy"
|
||||||
|
title={__('Whoa!')}
|
||||||
|
small
|
||||||
|
subtitle={
|
||||||
|
<I18nMessage
|
||||||
|
tokens={{
|
||||||
|
repost: (
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
onClick={() => push(`/$/${PAGES.REPOST_NEW}?to=${name}`)}
|
||||||
|
label={__('Repost')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
publish: <Button button="link" onClick={() => beginPublish(name)} label={'publish'} />,
|
||||||
|
name: <strong>name</strong>,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
You have found the edge of the internet. %repost% or %publish% your stuff here to claim this spot.
|
||||||
|
</I18nMessage>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!hideLink && winningUri && (
|
||||||
<div className="section__actions--between section__actions--no-margin">
|
<div className="section__actions--between section__actions--no-margin">
|
||||||
<span />
|
<span />
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -11,6 +11,7 @@ type Props = {
|
||||||
className?: string,
|
className?: string,
|
||||||
actions?: Node,
|
actions?: Node,
|
||||||
alwaysShow?: boolean,
|
alwaysShow?: boolean,
|
||||||
|
small: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const yrblTypes = {
|
const yrblTypes = {
|
||||||
|
@ -24,7 +25,7 @@ export default class extends React.PureComponent<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { title, subtitle, type, className, actions, alwaysShow = false } = this.props;
|
const { title, subtitle, type, className, actions, small, alwaysShow = false } = this.props;
|
||||||
|
|
||||||
const image = yrblTypes[type];
|
const image = yrblTypes[type];
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ export default class extends React.PureComponent<Props> {
|
||||||
<div className="yrbl__wrap">
|
<div className="yrbl__wrap">
|
||||||
<img
|
<img
|
||||||
alt="Friendly gerbil"
|
alt="Friendly gerbil"
|
||||||
className={classnames('yrbl', className, {
|
className={classnames(small ? 'yrbl--small' : 'yrbl', className, {
|
||||||
'yrbl--always-show': alwaysShow,
|
'yrbl--always-show': alwaysShow,
|
||||||
})}
|
})}
|
||||||
src={`${image}`}
|
src={`${image}`}
|
||||||
|
|
|
@ -19,6 +19,7 @@ exports.GET_CREDITS = 'getcredits';
|
||||||
exports.REPORT = 'report';
|
exports.REPORT = 'report';
|
||||||
exports.REWARDS = 'rewards';
|
exports.REWARDS = 'rewards';
|
||||||
exports.REWARDS_VERIFY = 'rewards/verify';
|
exports.REWARDS_VERIFY = 'rewards/verify';
|
||||||
|
exports.REPOST_NEW = 'repost';
|
||||||
exports.SEND = 'send';
|
exports.SEND = 'send';
|
||||||
exports.SETTINGS = 'settings';
|
exports.SETTINGS = 'settings';
|
||||||
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { parseURI, isNameValid, creditsToString } from 'lbry-redux';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import LbcSymbol from '../../component/common/lbc-symbol';
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
doHideModal: () => void,
|
doHideModal: () => void,
|
||||||
|
|
|
@ -6,7 +6,6 @@ import YrblWalletEmpty from 'component/yrblWalletEmpty';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
balance: number,
|
balance: number,
|
||||||
totalRewardValue: number,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function PublishPage(props: Props) {
|
function PublishPage(props: Props) {
|
||||||
|
|
15
ui/page/repost/index.js
Normal file
15
ui/page/repost/index.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { doResolveUri, selectBalance } from 'lbry-redux';
|
||||||
|
|
||||||
|
import RepostPage from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
balance: selectBalance(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(RepostPage);
|
77
ui/page/repost/view.jsx
Normal file
77
ui/page/repost/view.jsx
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Page from 'component/page';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import RepostCreate from 'component/repostCreate';
|
||||||
|
import YrblWalletEmpty from 'component/yrblWalletEmpty';
|
||||||
|
import useThrottle from 'effects/use-throttle';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
balance: number,
|
||||||
|
resolveUri: string => void,
|
||||||
|
};
|
||||||
|
function RepostPage(props: Props) {
|
||||||
|
const { balance, resolveUri } = props;
|
||||||
|
|
||||||
|
const REPOST_FROM = 'from';
|
||||||
|
const REPOST_TO = 'to';
|
||||||
|
const REDIRECT = 'redirect';
|
||||||
|
const {
|
||||||
|
location: { search },
|
||||||
|
} = useHistory();
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const repostFrom = urlParams.get(REPOST_FROM);
|
||||||
|
const redirectUri = urlParams.get(REDIRECT);
|
||||||
|
const repostTo = urlParams.get(REPOST_TO);
|
||||||
|
const [contentUri, setContentUri] = React.useState('');
|
||||||
|
const [repostUri, setRepostUri] = React.useState('');
|
||||||
|
const throttledContentValue = useThrottle(contentUri, 500);
|
||||||
|
const throttledRepostValue = useThrottle(repostUri, 500);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (throttledContentValue) {
|
||||||
|
resolveUri(throttledContentValue);
|
||||||
|
}
|
||||||
|
}, [throttledContentValue, resolveUri]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (throttledRepostValue) {
|
||||||
|
resolveUri(throttledRepostValue);
|
||||||
|
}
|
||||||
|
}, [throttledRepostValue, resolveUri]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (repostTo) {
|
||||||
|
resolveUri(repostTo);
|
||||||
|
}
|
||||||
|
}, [repostTo, resolveUri]);
|
||||||
|
|
||||||
|
const decodedFrom = repostFrom && decodeURIComponent(repostFrom);
|
||||||
|
return (
|
||||||
|
<Page
|
||||||
|
noFooter
|
||||||
|
noSideNavigation
|
||||||
|
backout={{
|
||||||
|
title: __('Repost'),
|
||||||
|
backLabel: __('Back'),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{balance === 0 && <YrblWalletEmpty />}
|
||||||
|
<div className={classnames({ 'card--disabled': balance === 0 })}>
|
||||||
|
<RepostCreate
|
||||||
|
uri={decodedFrom}
|
||||||
|
name={repostTo}
|
||||||
|
redirectUri={redirectUri}
|
||||||
|
repostUri={repostUri}
|
||||||
|
contentUri={contentUri}
|
||||||
|
setContentUri={setContentUri}
|
||||||
|
setRepostUri={setRepostUri}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RepostPage;
|
|
@ -11,6 +11,7 @@ import Ads from 'web/component/ads';
|
||||||
import SearchTopClaim from 'component/searchTopClaim';
|
import SearchTopClaim from 'component/searchTopClaim';
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
import ClaimPreview from 'component/claimPreview';
|
||||||
|
|
||||||
type AdditionalOptions = {
|
type AdditionalOptions = {
|
||||||
isBackgroundSearch: boolean,
|
isBackgroundSearch: boolean,
|
||||||
|
@ -97,7 +98,6 @@ export default function SearchPage(props: Props) {
|
||||||
{urlQuery && (
|
{urlQuery && (
|
||||||
<>
|
<>
|
||||||
{isValid && <SearchTopClaim query={modifiedUrlQuery} />}
|
{isValid && <SearchTopClaim query={modifiedUrlQuery} />}
|
||||||
|
|
||||||
<ClaimList
|
<ClaimList
|
||||||
uris={uris}
|
uris={uris}
|
||||||
loading={isSearching}
|
loading={isSearching}
|
||||||
|
@ -121,6 +121,7 @@ export default function SearchPage(props: Props) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{isSearching && new Array(5).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}
|
||||||
|
|
||||||
<div className="main--empty help">{__('These search results are provided by LBRY, Inc.')}</div>
|
<div className="main--empty help">{__('These search results are provided by LBRY, Inc.')}</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import TopPage from './view';
|
import TopPage from './view';
|
||||||
|
import { doClearPublish, doPrepareEdit, doResolveUris } from 'lbry-redux';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const { search } = props.location;
|
const { search } = props.location;
|
||||||
|
@ -11,4 +14,13 @@ const select = (state, props) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(select)(TopPage);
|
const perform = dispatch => ({
|
||||||
|
beginPublish: name => {
|
||||||
|
dispatch(doClearPublish());
|
||||||
|
dispatch(doPrepareEdit({ name }));
|
||||||
|
dispatch(push(`/$/${PAGES.UPLOAD}`));
|
||||||
|
},
|
||||||
|
doResolveUris: uris => dispatch(doResolveUris(uris)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(TopPage);
|
||||||
|
|
|
@ -7,22 +7,42 @@ import ClaimEffectiveAmount from 'component/claimEffectiveAmount';
|
||||||
import SearchTopClaim from 'component/searchTopClaim';
|
import SearchTopClaim from 'component/searchTopClaim';
|
||||||
import { ORDER_BY_TOP, FRESH_ALL } from 'constants/claim_search';
|
import { ORDER_BY_TOP, FRESH_ALL } from 'constants/claim_search';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string,
|
name: string,
|
||||||
|
beginPublish: string => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function TopPage(props: Props) {
|
function TopPage(props: Props) {
|
||||||
const { name } = props;
|
const { name, beginPublish } = props;
|
||||||
const [channelActive, setChannelActive] = React.useState(false);
|
const [channelActive, setChannelActive] = React.useState(false);
|
||||||
|
// if the query was actually '@name', still offer repost for 'name'
|
||||||
|
const queryName = name[0] === '@' ? name.slice(1) : name;
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<SearchTopClaim query={name} hideLink setChannelActive={setChannelActive} />
|
<SearchTopClaim query={name} hideLink setChannelActive={setChannelActive} />
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
name={channelActive ? `@${name}` : name}
|
name={channelActive ? `@${queryName}` : queryName}
|
||||||
defaultFreshness={FRESH_ALL}
|
defaultFreshness={FRESH_ALL}
|
||||||
defaultOrderBy={ORDER_BY_TOP}
|
defaultOrderBy={ORDER_BY_TOP}
|
||||||
|
meta={
|
||||||
|
<I18nMessage
|
||||||
|
tokens={{
|
||||||
|
repost: (
|
||||||
|
<Button
|
||||||
|
button="secondary"
|
||||||
|
navigate={`/$/${PAGES.REPOST_NEW}?to=${queryName}`}
|
||||||
|
label={__('Repost Here')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
publish: <Button button="secondary" onClick={() => beginPublish(queryName)} label={'Publish Here'} />,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
%repost% %publish%
|
||||||
|
</I18nMessage>
|
||||||
|
}
|
||||||
includeSupportAction
|
includeSupportAction
|
||||||
renderProperties={claim => (
|
renderProperties={claim => (
|
||||||
<span className="claim-preview__custom-properties">
|
<span className="claim-preview__custom-properties">
|
||||||
|
@ -33,7 +53,7 @@ function TopPage(props: Props) {
|
||||||
header={
|
header={
|
||||||
<div className="claim-search__menu-group">
|
<div className="claim-search__menu-group">
|
||||||
<Button
|
<Button
|
||||||
label={name}
|
label={queryName}
|
||||||
button="alt"
|
button="alt"
|
||||||
onClick={() => setChannelActive(false)}
|
onClick={() => setChannelActive(false)}
|
||||||
className={classnames('button-toggle', {
|
className={classnames('button-toggle', {
|
||||||
|
@ -41,7 +61,7 @@ function TopPage(props: Props) {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
label={`@${name}`}
|
label={`@${queryName}`}
|
||||||
button="alt"
|
button="alt"
|
||||||
onClick={() => setChannelActive(true)}
|
onClick={() => setChannelActive(true)}
|
||||||
className={classnames('button-toggle', {
|
className={classnames('button-toggle', {
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { getSearchQueryString } from 'util/query-params';
|
import { getSearchQueryString } from 'util/query-params';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { parseURI, makeSelectClaimForUri, makeSelectClaimIsNsfw, buildURI, SETTINGS, isClaimNsfw } from 'lbry-redux';
|
import {
|
||||||
|
parseURI,
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
makeSelectClaimIsNsfw,
|
||||||
|
buildURI,
|
||||||
|
SETTINGS,
|
||||||
|
isClaimNsfw,
|
||||||
|
makeSelectPendingClaimForUri,
|
||||||
|
} from 'lbry-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
type State = { search: SearchState };
|
type State = { search: SearchState };
|
||||||
|
@ -79,7 +87,6 @@ export const makeSelectRecommendedContentForUri = (uri: string) =>
|
||||||
recommendedContent = searchUris;
|
recommendedContent = searchUris;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return recommendedContent;
|
return recommendedContent;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -97,11 +104,13 @@ export const makeSelectWinningUriForQuery = (query: string) => {
|
||||||
|
|
||||||
return createSelector(
|
return createSelector(
|
||||||
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
makeSelectClientSetting(SETTINGS.SHOW_MATURE),
|
||||||
|
makeSelectPendingClaimForUri(uriFromQuery),
|
||||||
makeSelectClaimForUri(uriFromQuery),
|
makeSelectClaimForUri(uriFromQuery),
|
||||||
makeSelectClaimForUri(channelUriFromQuery),
|
makeSelectClaimForUri(channelUriFromQuery),
|
||||||
(matureEnabled, claim1, claim2) => {
|
(matureEnabled, pendingClaim, claim1, claim2) => {
|
||||||
const claim1Mature = claim1 && isClaimNsfw(claim1);
|
const claim1Mature = claim1 && isClaimNsfw(claim1);
|
||||||
const claim2Mature = claim2 && isClaimNsfw(claim2);
|
const claim2Mature = claim2 && isClaimNsfw(claim2);
|
||||||
|
let pendingAmount = pendingClaim && pendingClaim.amount;
|
||||||
|
|
||||||
if (!claim1 && !claim2) {
|
if (!claim1 && !claim2) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -124,7 +133,13 @@ export const makeSelectWinningUriForQuery = (query: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Number(effectiveAmount1) > Number(effectiveAmount2) ? claim1.canonical_url : claim2.canonical_url;
|
const returnBeforePending =
|
||||||
|
Number(effectiveAmount1) > Number(effectiveAmount2) ? claim1.canonical_url : claim2.canonical_url;
|
||||||
|
if (pendingAmount && pendingAmount > effectiveAmount1 && pendingAmount > effectiveAmount2) {
|
||||||
|
return pendingAmount.permanent_url;
|
||||||
|
} else {
|
||||||
|
return returnBeforePending;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -58,3 +58,4 @@
|
||||||
@import 'component/tags';
|
@import 'component/tags';
|
||||||
@import 'component/wunderbar';
|
@import 'component/wunderbar';
|
||||||
@import 'component/yrbl';
|
@import 'component/yrbl';
|
||||||
|
@import 'component/empty';
|
||||||
|
|
|
@ -98,6 +98,12 @@
|
||||||
margin-right: var(--spacing-m);
|
margin-right: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media__thumb-placeholder-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
@include handleChannelGif(6rem);
|
@include handleChannelGif(6rem);
|
||||||
}
|
}
|
||||||
|
@ -115,29 +121,47 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
@include handleChannelGif(5rem);
|
@include handleChannelGif(4rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.claim-preview__empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.claim-preview--large {
|
.claim-preview--large {
|
||||||
border: none;
|
border: none;
|
||||||
|
min-height: 8rem;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
.media__thumb {
|
.media__thumb {
|
||||||
width: 14rem;
|
width: 14rem;
|
||||||
@include handleClaimListGifThumbnail(14rem);
|
@include handleClaimListGifThumbnail(14rem);
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
width: 5rem;
|
min-height: 5rem;
|
||||||
|
width: 8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
|
width: 7.5rem;
|
||||||
@include handleChannelGif(7.5rem);
|
@include handleChannelGif(7.5rem);
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
width: 5rem;
|
||||||
|
@include handleChannelGif(5rem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +225,9 @@
|
||||||
|
|
||||||
.claim-preview__custom-properties {
|
.claim-preview__custom-properties {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-preview-metadata {
|
.claim-preview-metadata {
|
||||||
|
@ -523,3 +550,7 @@
|
||||||
.claim-link {
|
.claim-link {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.claim-preview__null-label {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
|
@ -97,3 +97,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.claim-search__menu-group--between {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
.button-toggle:last-of-type {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
21
ui/scss/component/_empty.scss
Normal file
21
ui/scss/component/_empty.scss
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
.empty__wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty__text {
|
||||||
|
color: var(--color-text-empty);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty__content {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
|
@ -34,6 +34,16 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.yrbl--small {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
display: block;
|
||||||
|
height: 8rem;
|
||||||
|
margin-right: calc(var(--spacing-xl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.yrbl__content {
|
.yrbl__content {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,8 @@
|
||||||
--color-text: #d8d8d8;
|
--color-text: #d8d8d8;
|
||||||
--color-text-error: #f5748c;
|
--color-text-error: #f5748c;
|
||||||
--color-text-help-warning: #f5ec74;
|
--color-text-help-warning: #f5ec74;
|
||||||
--color-text-empty: #bbbbbb;
|
--color-text-empty: var(--color-text-subtitle);
|
||||||
|
|
||||||
--color-text-help: #bbbbbb;
|
--color-text-help: #bbbbbb;
|
||||||
--color-text-subtitle: #9fafc0;
|
--color-text-subtitle: #9fafc0;
|
||||||
--color-text-warning: #212529;
|
--color-text-warning: #212529;
|
||||||
|
@ -74,7 +75,7 @@
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
--color-input: #f4f4f5;
|
--color-input: #f4f4f5;
|
||||||
--color-input-label: #d4d4d4;
|
--color-input-label: #a7a7a7;
|
||||||
--color-input-placeholder: #f4f4f5;
|
--color-input-placeholder: #f4f4f5;
|
||||||
--color-input-bg: #464d53;
|
--color-input-bg: #464d53;
|
||||||
--color-input-bg-copyable: #4f5861;
|
--color-input-bg-copyable: #4f5861;
|
||||||
|
|
Loading…
Reference in a new issue