Extend markdown support for LBRY urls #2521

Merged
btzr-io merged 27 commits from smart-links into master 2019-06-26 07:23:39 +02:00
7 changed files with 284 additions and 2 deletions
Showing only changes of commit 3dcdf913cf - Show all commits

View file

@ -147,6 +147,7 @@
"react-hot-loader": "^4.7.2", "react-hot-loader": "^4.7.2",
"react-modal": "^3.1.7", "react-modal": "^3.1.7",
"react-paginate": "^5.2.1", "react-paginate": "^5.2.1",
"react-portal-tooltip": "^2.4.7",
"react-pose": "^4.0.5", "react-pose": "^4.0.5",
"react-redux": "^6.0.1", "react-redux": "^6.0.1",
"react-router": "^5.0.0", "react-router": "^5.0.0",

View file

@ -7,7 +7,6 @@ import { formatLbryUriForWeb } from 'util/uri';
import { OutboundLink } from 'react-ga'; import { OutboundLink } from 'react-ga';
type Props = { type Props = {
onClick: ?(any) => any,
href: ?string, href: ?string,
title: ?string, title: ?string,
label: ?string, label: ?string,
@ -24,6 +23,11 @@ type Props = {
iconSize?: number, iconSize?: number,
constrict: ?boolean, // to shorten the button and ellipsis, only use for links constrict: ?boolean, // to shorten the button and ellipsis, only use for links
activeClass?: string, activeClass?: string,
innerRef: ?any,
// Events
onClick: ?(any) => any,
onMouseEnter: ?(any) => any,
onMouseLeave: ?(any) => any,
}; };
class Button extends React.PureComponent<Props> { class Button extends React.PureComponent<Props> {
@ -34,6 +38,9 @@ class Button extends React.PureComponent<Props> {
render() { render() {
const { const {
onClick, onClick,
onMouseEnter,
onMouseLeave,
innerRef,
href, href,
title, title,
label, label,
@ -111,8 +118,11 @@ class Button extends React.PureComponent<Props> {
onClick(); onClick();
} }
}} }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={combinedClassName} className={combinedClassName}
activeClassName={activeClass} activeClassName={activeClass}
innerRef={innerRef}
> >
{content} {content}
</NavLink> </NavLink>

View 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);

View 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;

View file

@ -4,6 +4,7 @@ import * as ICONS from 'constants/icons';
import * as React from 'react'; import * as React from 'react';
import { isURIValid } from 'lbry-redux'; import { isURIValid } from 'lbry-redux';
import Button from 'component/button'; import Button from 'component/button';
import ChannelLink from 'component/channelLink';
type Props = { type Props = {
href: string, href: string,
@ -40,7 +41,7 @@ class ExternalLink extends React.PureComponent<Props> {
} }
// Return local link if protocol is lbry uri // Return local link if protocol is lbry uri
if (protocol && protocol[0] === 'lbry:' && isURIValid(href)) { 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; return element;

View file

@ -85,3 +85,66 @@ $metadata-z-index: 1;
margin-top: -0.25rem; margin-top: -0.25rem;
color: rgba($lbry-white, 0.75); 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;
}

View file

@ -9432,6 +9432,11 @@ react-paginate@^5.2.1:
dependencies: dependencies:
prop-types "^15.6.1" 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: react-pose@^4.0.5:
version "4.0.8" version "4.0.8"
resolved "https://registry.yarnpkg.com/react-pose/-/react-pose-4.0.8.tgz#91bdfafde60e4096e7878a35dcc77715bed68f24" resolved "https://registry.yarnpkg.com/react-pose/-/react-pose-4.0.8.tgz#91bdfafde60e4096e7878a35dcc77715bed68f24"