Extend markdown support for LBRY urls #2521
7 changed files with 284 additions and 2 deletions
|
@ -147,6 +147,7 @@
|
|||
"react-hot-loader": "^4.7.2",
|
||||
"react-modal": "^3.1.7",
|
||||
"react-paginate": "^5.2.1",
|
||||
"react-portal-tooltip": "^2.4.7",
|
||||
"react-pose": "^4.0.5",
|
||||
"react-redux": "^6.0.1",
|
||||
"react-router": "^5.0.0",
|
||||
|
|
|
@ -7,7 +7,6 @@ import { formatLbryUriForWeb } from 'util/uri';
|
|||
import { OutboundLink } from 'react-ga';
|
||||
|
||||
type Props = {
|
||||
onClick: ?(any) => any,
|
||||
href: ?string,
|
||||
title: ?string,
|
||||
label: ?string,
|
||||
|
@ -24,6 +23,11 @@ type Props = {
|
|||
iconSize?: number,
|
||||
constrict: ?boolean, // to shorten the button and ellipsis, only use for links
|
||||
activeClass?: string,
|
||||
innerRef: ?any,
|
||||
// Events
|
||||
onClick: ?(any) => any,
|
||||
onMouseEnter: ?(any) => any,
|
||||
onMouseLeave: ?(any) => any,
|
||||
};
|
||||
|
||||
class Button extends React.PureComponent<Props> {
|
||||
|
@ -34,6 +38,9 @@ class Button extends React.PureComponent<Props> {
|
|||
render() {
|
||||
const {
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
innerRef,
|
||||
href,
|
||||
title,
|
||||
label,
|
||||
|
@ -111,8 +118,11 @@ class Button extends React.PureComponent<Props> {
|
|||
onClick();
|
||||
}
|
||||
}}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className={combinedClassName}
|
||||
activeClassName={activeClass}
|
||||
innerRef={innerRef}
|
||||
>
|
||||
{content}
|
||||
</NavLink>
|
||||
|
|
39
src/ui/component/channelLink/index.js
Normal file
39
src/ui/component/channelLink/index.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
doResolveUri,
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectTitleForUri,
|
||||
makeSelectThumbnailForUri,
|
||||
makeSelectCoverForUri,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectIsUriResolving,
|
||||
makeSelectMetadataItemForUri,
|
||||
} from 'lbry-redux';
|
||||
|
||||
import { selectBlackListedOutpoints } from 'lbryinc';
|
||||
|
||||
import ChannelLink from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
return {
|
||||
uri: props.uri,
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
cover: makeSelectCoverForUri(props.uri)(state),
|
||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||
description: makeSelectMetadataItemForUri(props.uri, 'description')(state),
|
||||
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = dispatch => ({
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(ChannelLink);
|
163
src/ui/component/channelLink/view.jsx
Normal file
163
src/ui/component/channelLink/view.jsx
Normal file
|
@ -0,0 +1,163 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
import ToolTip from 'react-portal-tooltip';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
title: ?string,
|
||||
cover: ?string,
|
||||
claim: StreamClaim,
|
||||
children: React.Node,
|
||||
thumbnail: ?string,
|
||||
description: ?string,
|
||||
isResolvingUri: boolean,
|
||||
resolveUri: string => void,
|
||||
blackListedOutpoints: Array<{
|
||||
txid: string,
|
||||
nout: number,
|
||||
}>,
|
||||
};
|
||||
|
||||
type TooltipProps = {
|
||||
uri: string,
|
||||
style: Object,
|
||||
title: ?string,
|
||||
active: ?boolean,
|
||||
parent: ?HTMLElement,
|
||||
claimId: ?string,
|
||||
thumbnail: ?string,
|
||||
claimName: ?string,
|
||||
channelName: ?string,
|
||||
description: ?string,
|
||||
};
|
||||
|
||||
type State = {
|
||||
isTooltipActive: boolean,
|
||||
};
|
||||
|
||||
const ChannelTooltip = (props: TooltipProps) => {
|
||||
const { style, title, active, parent, claimId, thumbnail, claimName, channelName, description } = props;
|
||||
|
||||
return (
|
||||
<ToolTip active={active} position="bottom" style={style} arrow="left" align="left" parent={parent}>
|
||||
<div className={'channel-tooltip'}>
|
||||
<div className={'channel-tooltip__info'}>
|
||||
<img className={'channel-tooltip__thumbnail'} src={thumbnail} />
|
||||
<div>
|
||||
<h2 className={'channel-tooltip__title'}>{title || channelName}</h2>
|
||||
<h3 className={'channel-tooltip__url'}>
|
||||
{claimName}
|
||||
{claimId && `#${claimId}`}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'channel-tooltip__description'}>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
<div className={'channel-tooltip__stats'} />
|
||||
</div>
|
||||
</ToolTip>
|
||||
);
|
||||
};
|
||||
|
||||
class ChannelLink extends React.Component<Props, State> {
|
||||
buttonRef: { current: ?any };
|
||||
|
||||
static defaultProps = {
|
||||
href: null,
|
||||
title: null,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { isTooltipActive: false };
|
||||
(this: any).buttonRef = React.createRef();
|
||||
}
|
||||
|
||||
showTooltip = () => {
|
||||
this.setState({ isTooltipActive: true });
|
||||
};
|
||||
|
||||
hideTooltip = () => {
|
||||
this.setState({ isTooltipActive: false });
|
||||
};
|
||||
|
||||
isClaimBlackListed() {
|
||||
const { claim, blackListedOutpoints } = this.props;
|
||||
|
||||
if (claim && blackListedOutpoints) {
|
||||
let blackListed = false;
|
||||
|
||||
for (let i = 0; i < blackListedOutpoints.length; i += 1) {
|
||||
const outpoint = blackListedOutpoints[i];
|
||||
if (outpoint.txid === claim.txid && outpoint.nout === claim.nout) {
|
||||
blackListed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return blackListed;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { isResolvingUri, resolveUri, uri } = this.props;
|
||||
if (!isResolvingUri) {
|
||||
resolveUri(uri);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { isResolvingUri, resolveUri, claim, uri } = this.props;
|
||||
if (!isResolvingUri && uri && claim === undefined) {
|
||||
resolveUri(uri);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { uri, claim, title, description, thumbnail, children, isResolvingUri } = this.props;
|
||||
const { channelName, claimName, claimId } = parseURI(uri);
|
||||
const blackListed = this.isClaimBlackListed();
|
||||
const isReady = !blackListed && !isResolvingUri && claim !== null;
|
||||
const tooltipReady = isReady && this.buttonRef.current !== null;
|
||||
|
||||
const bgColor = '#32373b';
|
||||
|
||||
const tooltipStyle = {
|
||||
style: { background: bgColor },
|
||||
arrowStyle: { color: bgColor },
|
||||
};
|
||||
|
||||
return isReady ? (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
button="link"
|
||||
label={children}
|
||||
navigate={uri}
|
||||
innerRef={this.buttonRef}
|
||||
onMouseEnter={this.showTooltip}
|
||||
onMouseLeave={this.hideTooltip}
|
||||
/>
|
||||
{tooltipReady && (
|
||||
<ChannelTooltip
|
||||
uri={uri}
|
||||
style={tooltipStyle}
|
||||
title={title}
|
||||
claimId={claimId}
|
||||
claimName={claimName}
|
||||
channelName={channelName}
|
||||
thumbnail={thumbnail}
|
||||
description={description}
|
||||
active={this.state.isTooltipActive}
|
||||
parent={this.buttonRef.current}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<span>{children}</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChannelLink;
|
|
@ -4,6 +4,7 @@ import * as ICONS from 'constants/icons';
|
|||
import * as React from 'react';
|
||||
import { isURIValid } from 'lbry-redux';
|
||||
import Button from 'component/button';
|
||||
import ChannelLink from 'component/channelLink';
|
||||
|
||||
type Props = {
|
||||
href: string,
|
||||
|
@ -40,7 +41,7 @@ class ExternalLink extends React.PureComponent<Props> {
|
|||
}
|
||||
// Return local link if protocol is lbry uri
|
||||
if (protocol && protocol[0] === 'lbry:' && isURIValid(href)) {
|
||||
element = <Button button="link" title={title || href} label={children} navigate={href} />;
|
||||
element = <ChannelLink uri={href}>{children}</ChannelLink>;
|
||||
}
|
||||
|
||||
return element;
|
||||
|
|
|
@ -85,3 +85,66 @@ $metadata-z-index: 1;
|
|||
margin-top: -0.25rem;
|
||||
color: rgba($lbry-white, 0.75);
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
|
||||
.channel-tooltip__thumbnail {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
background: #fff;
|
||||
margin-right: 8px;
|
||||
border: 0;
|
||||
border-radius: var(--card-radius);
|
||||
background-size: cover;
|
||||
box-shadow: 0px 8px 40px -3px $lbry-black;
|
||||
}
|
||||
|
||||
.channel-tooltip {
|
||||
width: 18rem;
|
||||
}
|
||||
|
||||
.channel-tooltip__description {
|
||||
padding: 8px;
|
||||
margin: 4px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.channel-tooltip__description p {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
/*
|
||||
This fixes the line-clamp issue on resize:
|
||||
https://stackoverflow.com/questions/38989475/css-multi-line-line-clamp-ellipsis-doesnt-work
|
||||
*/
|
||||
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.channel-tooltip__info {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
padding: 8px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.channel-tooltip__title {
|
||||
font-weight: bold;
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.5em;
|
||||
margin: 0;
|
||||
margin-left: 4px;
|
||||
color: $lbry-white;
|
||||
}
|
||||
|
||||
.channel-tooltip__url {
|
||||
font-size: 1rem;
|
||||
margin-left: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -9432,6 +9432,11 @@ react-paginate@^5.2.1:
|
|||
dependencies:
|
||||
prop-types "^15.6.1"
|
||||
|
||||
react-portal-tooltip@^2.4.7:
|
||||
version "2.4.7"
|
||||
resolved "https://registry.yarnpkg.com/react-portal-tooltip/-/react-portal-tooltip-2.4.7.tgz#77fd4a75760afecd116736c480485aa22cbbb740"
|
||||
integrity sha512-ZOuCKpvu8rr6aS6nNjckd78qEgWyHZnKidZ9Yqrd0HR5K9NYJ7csb4duc0kEXeG8DiCWsLPU70o1EVP+MxlINg==
|
||||
|
||||
react-pose@^4.0.5:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/react-pose/-/react-pose-4.0.8.tgz#91bdfafde60e4096e7878a35dcc77715bed68f24"
|
||||
|
|
Loading…
Reference in a new issue