auto-embed lbry urls
This commit is contained in:
parent
f4b099f69e
commit
9893e61443
12 changed files with 339 additions and 65 deletions
|
@ -162,11 +162,13 @@
|
||||||
"remark": "^9.0.0",
|
"remark": "^9.0.0",
|
||||||
"remark-emoji": "^2.0.1",
|
"remark-emoji": "^2.0.1",
|
||||||
"remark-react": "^4.0.3",
|
"remark-react": "^4.0.3",
|
||||||
|
"remark-squeeze-paragraphs": "^3.0.3",
|
||||||
"render-media": "^3.1.0",
|
"render-media": "^3.1.0",
|
||||||
"reselect": "^3.0.0",
|
"reselect": "^3.0.0",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"semver": "^5.3.0",
|
"semver": "^5.3.0",
|
||||||
"stream-to-blob-url": "^2.1.1",
|
"stream-to-blob-url": "^2.1.1",
|
||||||
|
"strip-markdown": "^3.0.3",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
"terser-webpack-plugin": "^1.2.3",
|
"terser-webpack-plugin": "^1.2.3",
|
||||||
"three": "^0.93.0",
|
"three": "^0.93.0",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
import ToolTip from 'react-portal-tooltip';
|
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import ChannelTooltip from 'component/common/channel-tooltip';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -20,48 +20,10 @@ type Props = {
|
||||||
}>,
|
}>,
|
||||||
};
|
};
|
||||||
|
|
||||||
type TooltipProps = {
|
|
||||||
uri: string,
|
|
||||||
style: Object,
|
|
||||||
title: ?string,
|
|
||||||
active: ?boolean,
|
|
||||||
parent: ?HTMLElement,
|
|
||||||
claimId: ?string,
|
|
||||||
thumbnail: ?string,
|
|
||||||
claimName: ?string,
|
|
||||||
channelName: ?string,
|
|
||||||
description: ?string,
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
isTooltipActive: boolean,
|
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> {
|
class ChannelLink extends React.Component<Props, State> {
|
||||||
buttonRef: { current: ?any };
|
buttonRef: { current: ?any };
|
||||||
|
|
||||||
|
@ -102,15 +64,15 @@ class ChannelLink extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { isResolvingUri, resolveUri, uri } = this.props;
|
const { isResolvingUri, resolveUri, claim, uri } = this.props;
|
||||||
if (!isResolvingUri) {
|
if (!isResolvingUri && uri && !claim) {
|
||||||
resolveUri(uri);
|
resolveUri(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { isResolvingUri, resolveUri, claim, uri } = this.props;
|
const { isResolvingUri, resolveUri, claim, uri } = this.props;
|
||||||
if (!isResolvingUri && uri && claim === undefined) {
|
if (!isResolvingUri && uri && !claim) {
|
||||||
resolveUri(uri);
|
resolveUri(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,12 +83,6 @@ class ChannelLink extends React.Component<Props, State> {
|
||||||
const blackListed = this.isClaimBlackListed();
|
const blackListed = this.isClaimBlackListed();
|
||||||
const isReady = !blackListed && !isResolvingUri && claim !== null;
|
const isReady = !blackListed && !isResolvingUri && claim !== null;
|
||||||
const tooltipReady = this.buttonRef.current !== null;
|
const tooltipReady = this.buttonRef.current !== null;
|
||||||
const bgColor = '#32373b';
|
|
||||||
|
|
||||||
const tooltipStyle = {
|
|
||||||
style: { background: bgColor },
|
|
||||||
arrowStyle: { color: bgColor },
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -141,7 +97,6 @@ class ChannelLink extends React.Component<Props, State> {
|
||||||
{tooltipReady && (
|
{tooltipReady && (
|
||||||
<ChannelTooltip
|
<ChannelTooltip
|
||||||
uri={uri}
|
uri={uri}
|
||||||
style={tooltipStyle}
|
|
||||||
title={title}
|
title={title}
|
||||||
claimId={claimId}
|
claimId={claimId}
|
||||||
claimName={claimName}
|
claimName={claimName}
|
||||||
|
|
39
src/ui/component/claimLink/index.js
Normal file
39
src/ui/component/claimLink/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 ClaimLink 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
|
||||||
|
)(ClaimLink);
|
78
src/ui/component/claimLink/view.jsx
Normal file
78
src/ui/component/claimLink/view.jsx
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import { parseURI } from 'lbry-redux';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import PreviewLink from 'component/common/preview-link';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: string,
|
||||||
|
title: ?string,
|
||||||
|
cover: ?string,
|
||||||
|
claim: StreamClaim,
|
||||||
|
children: React.Node,
|
||||||
|
thumbnail: ?string,
|
||||||
|
autoEmbed: ?boolean,
|
||||||
|
description: ?string,
|
||||||
|
isResolvingUri: boolean,
|
||||||
|
resolveUri: string => void,
|
||||||
|
blackListedOutpoints: Array<{
|
||||||
|
txid: string,
|
||||||
|
nout: number,
|
||||||
|
}>,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ClaimLink extends React.Component<Props> {
|
||||||
|
static defaultProps = {
|
||||||
|
href: null,
|
||||||
|
title: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
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, claim } = this.props;
|
||||||
|
if (!isResolvingUri && !claim) {
|
||||||
|
resolveUri(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const { isResolvingUri, resolveUri, claim, uri } = this.props;
|
||||||
|
if (!isResolvingUri && uri && !claim) {
|
||||||
|
resolveUri(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { uri, claim, title, description, autoEmbed, thumbnail, children, isResolvingUri } = this.props;
|
||||||
|
const { claimName } = parseURI(uri);
|
||||||
|
const blackListed = this.isClaimBlackListed();
|
||||||
|
const showPreview = autoEmbed && !blackListed && !isResolvingUri && claim !== null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Button title={title} button={'link'} label={children} navigate={uri} />
|
||||||
|
{showPreview && (
|
||||||
|
<PreviewLink uri={uri} title={title || claimName} thumbnail={thumbnail} description={description} />
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClaimLink;
|
49
src/ui/component/common/channel-tooltip.jsx
Normal file
49
src/ui/component/common/channel-tooltip.jsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import ToolTip from 'react-portal-tooltip';
|
||||||
|
|
||||||
|
type TooltipProps = {
|
||||||
|
uri: string,
|
||||||
|
title: ?string,
|
||||||
|
active: ?boolean,
|
||||||
|
parent: ?HTMLElement,
|
||||||
|
claimId: ?string,
|
||||||
|
thumbnail: ?string,
|
||||||
|
claimName: ?string,
|
||||||
|
channelName: ?string,
|
||||||
|
description: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChannelTooltip = (props: TooltipProps) => {
|
||||||
|
const { title, active, parent, claimId, thumbnail, claimName, channelName, description } = props;
|
||||||
|
|
||||||
|
const bgColor = '#32373b';
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
style: { background: bgColor },
|
||||||
|
arrowStyle: { color: bgColor },
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChannelTooltip;
|
|
@ -2,14 +2,14 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import remark from 'remark';
|
import remark from 'remark';
|
||||||
import remarkLBRY from 'util/remark-lbry';
|
import remarkLBRY from 'util/remark-lbry';
|
||||||
|
import remarkStrip from 'strip-markdown';
|
||||||
import remarkEmoji from 'remark-emoji';
|
import remarkEmoji from 'remark-emoji';
|
||||||
import reactRenderer from 'remark-react';
|
import reactRenderer from 'remark-react';
|
||||||
import ExternalLink from 'component/externalLink';
|
import ExternalLink from 'component/externalLink';
|
||||||
import defaultSchema from 'hast-util-sanitize/lib/github.json';
|
import defaultSchema from 'hast-util-sanitize/lib/github.json';
|
||||||
|
|
||||||
type MarkdownProps = {
|
type SimpleTextProps = {
|
||||||
content: ?string,
|
children?: React.Node,
|
||||||
promptLinks?: boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type SimpleLinkProps = {
|
type SimpleLinkProps = {
|
||||||
|
@ -18,6 +18,16 @@ type SimpleLinkProps = {
|
||||||
children?: React.Node,
|
children?: React.Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type MarkdownProps = {
|
||||||
|
strip?: boolean,
|
||||||
|
content: ?string,
|
||||||
|
promptLinks?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SimpleText = (props: SimpleTextProps) => {
|
||||||
|
return <span>{props.children}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
const SimpleLink = (props: SimpleLinkProps) => {
|
const SimpleLink = (props: SimpleLinkProps) => {
|
||||||
const { href, title, children } = props;
|
const { href, title, children } = props;
|
||||||
return (
|
return (
|
||||||
|
@ -32,15 +42,37 @@ const schema = { ...defaultSchema };
|
||||||
|
|
||||||
// Extend sanitation schema to support lbry protocol
|
// Extend sanitation schema to support lbry protocol
|
||||||
schema.protocols.href.push('lbry');
|
schema.protocols.href.push('lbry');
|
||||||
|
schema.attributes.a.push('data-preview');
|
||||||
|
|
||||||
const MarkdownPreview = (props: MarkdownProps) => {
|
const MarkdownPreview = (props: MarkdownProps) => {
|
||||||
const { content, promptLinks } = props;
|
const { content, strip, promptLinks } = props;
|
||||||
const remarkOptions = {
|
|
||||||
|
const remarkOptions: Object = {
|
||||||
sanitize: schema,
|
sanitize: schema,
|
||||||
|
fragment: React.Fragment,
|
||||||
remarkReactComponents: {
|
remarkReactComponents: {
|
||||||
a: promptLinks ? ExternalLink : SimpleLink,
|
a: promptLinks ? ExternalLink : SimpleLink,
|
||||||
|
// Workaraund of remarkOptions.Fragment
|
||||||
|
div: React.Fragment,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Strip all content and just render text
|
||||||
|
if (strip) {
|
||||||
|
// Remove new lines and extra space
|
||||||
|
remarkOptions.remarkReactComponents.p = SimpleText;
|
||||||
|
return (
|
||||||
|
<span className="markdown-preview">
|
||||||
|
{
|
||||||
|
remark()
|
||||||
|
.use(remarkStrip)
|
||||||
|
.use(reactRenderer, remarkOptions)
|
||||||
|
.processSync(content).contents
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="markdown-preview">
|
<div className="markdown-preview">
|
||||||
{
|
{
|
||||||
|
|
47
src/ui/component/common/preview-link.jsx
Normal file
47
src/ui/component/common/preview-link.jsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import DateTime from 'component/dateTime';
|
||||||
|
import UriIndicator from 'component/uriIndicator';
|
||||||
|
import TruncatedText from 'component/common/truncated-text';
|
||||||
|
import MarkdownPreview from 'component/common/markdown-preview';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: string,
|
||||||
|
title: ?string,
|
||||||
|
thumbnail: ?string,
|
||||||
|
description: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PreviewLink = (props: Props) => {
|
||||||
|
const { uri, title, description, thumbnail } = props;
|
||||||
|
const placeholder = 'static/img/placeholder.png';
|
||||||
|
|
||||||
|
const thumbnailStyle = {
|
||||||
|
backgroundImage: `url(${thumbnail || placeholder})`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={'preview-link'}>
|
||||||
|
<span className={'media-tile media-tile--small card--link'}>
|
||||||
|
<span style={thumbnailStyle} className={'preview-link--thumbnail media__thumb'} />
|
||||||
|
<span className={'preview-link--text media__info'}>
|
||||||
|
<span className={'preview-link--title media__title'}>
|
||||||
|
<TruncatedText text={title} lines={1} />
|
||||||
|
</span>
|
||||||
|
<span className={'preview-link--description media__subtext'}>
|
||||||
|
<span className={'truncated-text'}>
|
||||||
|
{__('Published to')} <UriIndicator uri={uri} link /> <DateTime timeAgo uri={uri} />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span className={'preview-link--description media__subtext'}>
|
||||||
|
<TruncatedText lines={2} showTooltip={false}>
|
||||||
|
<MarkdownPreview content={description} promptLinks strip />
|
||||||
|
</TruncatedText>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PreviewLink;
|
|
@ -2,14 +2,24 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
text: ?string,
|
text?: ?string,
|
||||||
lines: number,
|
lines: number,
|
||||||
|
showTooltip?: boolean,
|
||||||
|
children?: React.Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TruncatedText = (props: Props) => (
|
const TruncatedText = (props: Props) => {
|
||||||
<span title={props.text} className="truncated-text" style={{ WebkitLineClamp: props.lines }}>
|
const { text, children, lines, showTooltip } = props;
|
||||||
{props.text}
|
const tooltip = showTooltip ? children || text : '';
|
||||||
</span>
|
return (
|
||||||
);
|
<span title={tooltip} className="truncated-text" style={{ WebkitLineClamp: lines }}>
|
||||||
|
{children || text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
TruncatedText.defaultProps = {
|
||||||
|
showTooltip: true,
|
||||||
|
};
|
||||||
|
|
||||||
export default TruncatedText;
|
export default TruncatedText;
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { isURIValid } from 'lbry-redux';
|
import { isURIValid, parseURI } from 'lbry-redux';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import ClaimLink from 'component/claimLink';
|
||||||
import ChannelLink from 'component/channelLink';
|
import ChannelLink from 'component/channelLink';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -11,6 +12,7 @@ type Props = {
|
||||||
title?: string,
|
title?: string,
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
openModal: (id: string, { uri: string }) => void,
|
openModal: (id: string, { uri: string }) => void,
|
||||||
|
'data-preview'?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class ExternalLink extends React.PureComponent<Props> {
|
class ExternalLink extends React.PureComponent<Props> {
|
||||||
|
@ -41,7 +43,20 @@ 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 = <ChannelLink uri={href}>{children}</ChannelLink>;
|
try {
|
||||||
|
const uri = parseURI(href);
|
||||||
|
if (uri.isChannel && !uri.path) {
|
||||||
|
element = <ChannelLink uri={href}>{children}</ChannelLink>;
|
||||||
|
} else if (uri) {
|
||||||
|
element = (
|
||||||
|
<ClaimLink uri={href} autoEmbed={this.props['data-preview']}>
|
||||||
|
{children}
|
||||||
|
</ClaimLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Silent error: console.error(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
|
|
|
@ -116,4 +116,19 @@
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-link {
|
||||||
|
margin: var(--spacing-vertical-medium) 0;
|
||||||
|
padding: 1.2rem 0.8rem;
|
||||||
|
background-color: rgba($lbry-teal-5, 0.1);
|
||||||
|
border-left: 0.5rem solid $lbry-teal-5;
|
||||||
|
display: block;
|
||||||
|
align-items: center;
|
||||||
|
width: 40rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-link--description {
|
||||||
|
display: block;
|
||||||
|
margin: var(--spacing-vertical-medium) 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@ const locateURI = (value, fromIndex) => value.indexOf(protocol, fromIndex);
|
||||||
const locateMention = (value, fromIndex) => value.indexOf('@', fromIndex);
|
const locateMention = (value, fromIndex) => value.indexOf('@', fromIndex);
|
||||||
|
|
||||||
// Generate a valid markdown link
|
// Generate a valid markdown link
|
||||||
const createURI = (text, uri) => ({
|
const createURI = (text, uri, autoEmbed = false) => ({
|
||||||
type: 'link',
|
type: 'link',
|
||||||
url: (uri.startsWith(protocol) ? '' : protocol) + uri,
|
url: (uri.startsWith(protocol) ? '' : protocol) + uri,
|
||||||
|
data: { hProperties: { dataPreview: autoEmbed } },
|
||||||
children: [{ type: 'text', value: text }],
|
children: [{ type: 'text', value: text }],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,10 +19,10 @@ const validateURI = (match, eat) => {
|
||||||
const uri = parseURI(text);
|
const uri = parseURI(text);
|
||||||
// Create channel link
|
// Create channel link
|
||||||
if (uri.isChannel && !uri.path) {
|
if (uri.isChannel && !uri.path) {
|
||||||
return eat(text)(createURI(uri.claimName, text));
|
return eat(text)(createURI(uri.claimName, text, false));
|
||||||
}
|
}
|
||||||
// Create uri link
|
// Create uri link
|
||||||
return eat(text)(createURI(text, text));
|
return eat(text)(createURI(text, text, true));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Silent errors: console.error(err)
|
// Silent errors: console.error(err)
|
||||||
}
|
}
|
||||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -7063,6 +7063,13 @@ md5@^2.2.1:
|
||||||
crypt "~0.0.1"
|
crypt "~0.0.1"
|
||||||
is-buffer "~1.1.1"
|
is-buffer "~1.1.1"
|
||||||
|
|
||||||
|
mdast-squeeze-paragraphs@^3.0.0:
|
||||||
|
version "3.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-3.0.5.tgz#f428b6b944f8faef454db9b58f170c4183cb2e61"
|
||||||
|
integrity sha512-xX6Vbe348Y/rukQlG4W3xH+7v4ZlzUbSY4HUIQCuYrF2DrkcHx584mCaFxkWoDZKNUfyLZItHC9VAqX3kIP7XA==
|
||||||
|
dependencies:
|
||||||
|
unist-util-remove "^1.0.0"
|
||||||
|
|
||||||
mdast-util-compact@^1.0.0:
|
mdast-util-compact@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz#c12ebe16fffc84573d3e19767726de226e95f649"
|
resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz#c12ebe16fffc84573d3e19767726de226e95f649"
|
||||||
|
@ -9808,6 +9815,13 @@ remark-react@^4.0.3:
|
||||||
hast-util-sanitize "^1.0.0"
|
hast-util-sanitize "^1.0.0"
|
||||||
mdast-util-to-hast "^3.0.0"
|
mdast-util-to-hast "^3.0.0"
|
||||||
|
|
||||||
|
remark-squeeze-paragraphs@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-3.0.3.tgz#299d8db7d44008c9ae240dbf6d1f55b8b0f924ce"
|
||||||
|
integrity sha512-eDvjtwFa9eClqb7XgdF/1H9Pfs2LPnf/P3eRs9ucYAWUuv4WO8ZOVAUeT/1h66rQvghnfctz9au+HEmoKcdoqA==
|
||||||
|
dependencies:
|
||||||
|
mdast-squeeze-paragraphs "^3.0.0"
|
||||||
|
|
||||||
remark-stringify@^5.0.0:
|
remark-stringify@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-5.0.0.tgz#336d3a4d4a6a3390d933eeba62e8de4bd280afba"
|
resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-5.0.0.tgz#336d3a4d4a6a3390d933eeba62e8de4bd280afba"
|
||||||
|
@ -10840,6 +10854,11 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||||
|
|
||||||
|
strip-markdown@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-markdown/-/strip-markdown-3.0.3.tgz#93b4526abe32a1d69e5ca943f4d9ba25c01a4d48"
|
||||||
|
integrity sha512-G2DSM9wy3PWxY3miAibWpsTqZgXLXgRoq0yVyaVs9O7FDGEwPO6pmSE8CyzHhU88Z2w1dkFqFmWUklFNsQKwqg==
|
||||||
|
|
||||||
style-loader@^0.23.1:
|
style-loader@^0.23.1:
|
||||||
version "0.23.1"
|
version "0.23.1"
|
||||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925"
|
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925"
|
||||||
|
@ -11468,6 +11487,11 @@ unist-util-is@^2.0.0, unist-util-is@^2.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db"
|
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db"
|
||||||
integrity sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw==
|
integrity sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw==
|
||||||
|
|
||||||
|
unist-util-is@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd"
|
||||||
|
integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==
|
||||||
|
|
||||||
unist-util-position@^3.0.0:
|
unist-util-position@^3.0.0:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.0.2.tgz#80ad4a05efc4ab01a66886cc70493893ba73c5eb"
|
resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.0.2.tgz#80ad4a05efc4ab01a66886cc70493893ba73c5eb"
|
||||||
|
@ -11480,6 +11504,13 @@ unist-util-remove-position@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
unist-util-visit "^1.1.0"
|
unist-util-visit "^1.1.0"
|
||||||
|
|
||||||
|
unist-util-remove@^1.0.0:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-1.0.3.tgz#58ec193dfa84b52d5a055ffbc58e5444eb8031a3"
|
||||||
|
integrity sha512-mB6nCHCQK0pQffUAcCVmKgIWzG/AXs/V8qpS8K72tMPtOSCMSjDeMc5yN+Ye8rB0FhcE+JvW++o1xRNc0R+++g==
|
||||||
|
dependencies:
|
||||||
|
unist-util-is "^3.0.0"
|
||||||
|
|
||||||
unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1:
|
unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6"
|
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6"
|
||||||
|
|
Loading…
Add table
Reference in a new issue