embed functionality inside markdown posts
This commit is contained in:
parent
0738d186a5
commit
1a50e697ce
18 changed files with 343 additions and 186 deletions
|
@ -1,27 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
import { withRouter } from 'react-router';
|
||||
import { URL } from 'config';
|
||||
import * as ICONS from 'constants/icons';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
title: ?string,
|
||||
};
|
||||
|
||||
function fileViewerEmbeddedTitle(props: Props) {
|
||||
const { uri, title } = props;
|
||||
|
||||
const lbrytvLink = `${URL}${formatLbryUrlForWeb(uri)}?src=embed`;
|
||||
|
||||
return (
|
||||
<div className="file-viewer__embedded-title">
|
||||
<Button label={title} button="link" href={lbrytvLink} />
|
||||
<Button className="file-viewer__overlay-logo file-viewer__embedded-title-logo" icon={ICONS.LBRY} href={URL} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(fileViewerEmbeddedTitle);
|
|
@ -3,7 +3,7 @@ import type { ElementRef, Node } from 'react';
|
|||
import React from 'react';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import SimpleMDE from 'react-simplemde-editor';
|
||||
import MarkdownPreview from 'component/common/markdown-preview-internal';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
|
||||
import { MAX_CHARACTERS_IN_COMMENT as defaultTextAreaLimit } from 'constants/comments';
|
||||
import 'easymde/dist/easymde.min.css';
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import remark from 'remark';
|
||||
import remarkAttr from 'remark-attr';
|
||||
import remarkStrip from 'strip-markdown';
|
||||
import remarkEmoji from 'remark-emoji';
|
||||
import reactRenderer from 'remark-react';
|
||||
import ExternalLink from 'component/externalLink';
|
||||
import defaultSchema from 'hast-util-sanitize/lib/github.json';
|
||||
import { formatedLinks, inlineLinks } from 'util/remark-lbry';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
|
||||
type SimpleTextProps = {
|
||||
children?: React.Node,
|
||||
};
|
||||
|
||||
type SimpleLinkProps = {
|
||||
href?: string,
|
||||
title?: string,
|
||||
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 { title, children } = props;
|
||||
let { href } = props;
|
||||
if (IS_WEB && href && href.startsWith('lbry://')) {
|
||||
href = formatLbryUrlForWeb(href);
|
||||
// using Link after formatLbryUrl to handle "/" vs "#/"
|
||||
// for web and desktop scenarios respectively
|
||||
return (
|
||||
<Link
|
||||
title={title}
|
||||
to={href}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a href={href} title={title}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
// Use github sanitation schema
|
||||
const schema = { ...defaultSchema };
|
||||
|
||||
// Extend sanitation schema to support lbry protocol
|
||||
schema.protocols.href.push('lbry');
|
||||
schema.attributes.a.push('embed');
|
||||
|
||||
const MarkdownPreview = (props: MarkdownProps) => {
|
||||
const { content, strip, promptLinks } = props;
|
||||
|
||||
const remarkOptions: Object = {
|
||||
sanitize: schema,
|
||||
fragment: React.Fragment,
|
||||
remarkReactComponents: {
|
||||
a: promptLinks ? ExternalLink : SimpleLink,
|
||||
// Workaraund of remarkOptions.Fragment
|
||||
div: React.Fragment,
|
||||
},
|
||||
};
|
||||
|
||||
const remarkAttrOpts = {
|
||||
scope: 'extended',
|
||||
elements: ['link'],
|
||||
extend: { link: ['embed'] },
|
||||
defaultValue: true,
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<div className="markdown-preview">
|
||||
{
|
||||
remark()
|
||||
.use(remarkAttr, remarkAttrOpts)
|
||||
// Remark plugins for lbry urls
|
||||
// Note: The order is important
|
||||
.use(formatedLinks)
|
||||
.use(inlineLinks)
|
||||
// Emojis
|
||||
.use(remarkEmoji)
|
||||
.use(reactRenderer, remarkOptions)
|
||||
.processSync(content).contents
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownPreview;
|
|
@ -1,8 +1,144 @@
|
|||
import React, { Suspense } from 'react';
|
||||
import MarkDownPreview from './markdown-preview-internal';
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import remark from 'remark';
|
||||
import remarkAttr from 'remark-attr';
|
||||
import remarkStrip from 'strip-markdown';
|
||||
import remarkEmoji from 'remark-emoji';
|
||||
import reactRenderer from 'remark-react';
|
||||
import ExternalLink from 'component/externalLink';
|
||||
import defaultSchema from 'hast-util-sanitize/lib/github.json';
|
||||
import { formatedLinks, inlineLinks } from 'util/remark-lbry';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
import EmbedPlayButton from 'component/embedPlayButton';
|
||||
|
||||
const MarkdownPreview = props => {
|
||||
return <MarkDownPreview {...props} />;
|
||||
type SimpleTextProps = {
|
||||
children?: React.Node,
|
||||
};
|
||||
|
||||
type SimpleLinkProps = {
|
||||
href?: string,
|
||||
title?: string,
|
||||
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 { title, children } = props;
|
||||
const { href } = props;
|
||||
|
||||
if (!href) {
|
||||
return children || null;
|
||||
}
|
||||
|
||||
if (!href.startsWith('lbry://')) {
|
||||
return (
|
||||
<a href={href} title={title}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
const [uri, search] = href.split('?');
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const embed = urlParams.get('embed');
|
||||
|
||||
if (embed) {
|
||||
return <EmbedPlayButton uri={uri} />;
|
||||
}
|
||||
|
||||
const webLink = formatLbryUrlForWeb(uri);
|
||||
// using Link after formatLbryUrl to handle "/" vs "#/"
|
||||
// for web and desktop scenarios respectively
|
||||
|
||||
return (
|
||||
<Link
|
||||
title={title}
|
||||
to={webLink}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
// Use github sanitation schema
|
||||
const schema = { ...defaultSchema };
|
||||
|
||||
// Extend sanitation schema to support lbry protocol
|
||||
schema.protocols.href.push('lbry');
|
||||
schema.attributes.a.push('embed');
|
||||
|
||||
const REPLACE_REGEX = /(<iframe src=")(.*?(?=))("><\/iframe>)/g;
|
||||
|
||||
const MarkdownPreview = (props: MarkdownProps) => {
|
||||
const { content, strip, promptLinks } = props;
|
||||
const strippedContent = content
|
||||
? content.replace(REPLACE_REGEX, (x, y, iframeSrc) => {
|
||||
return `${iframeSrc}?embed=true`;
|
||||
})
|
||||
: '';
|
||||
|
||||
const remarkOptions: Object = {
|
||||
sanitize: schema,
|
||||
fragment: React.Fragment,
|
||||
remarkReactComponents: {
|
||||
a: promptLinks ? ExternalLink : linkProps => <SimpleLink {...linkProps} />,
|
||||
// Workaraund of remarkOptions.Fragment
|
||||
div: React.Fragment,
|
||||
},
|
||||
};
|
||||
|
||||
const remarkAttrOpts = {
|
||||
scope: 'extended',
|
||||
elements: ['link'],
|
||||
extend: { link: ['embed'] },
|
||||
defaultValue: true,
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<div className="markdown-preview">
|
||||
{
|
||||
remark()
|
||||
.use(remarkAttr, remarkAttrOpts)
|
||||
// Remark plugins for lbry urls
|
||||
// Note: The order is important
|
||||
.use(formatedLinks)
|
||||
.use(inlineLinks)
|
||||
// Emojis
|
||||
.use(remarkEmoji)
|
||||
.use(reactRenderer, remarkOptions)
|
||||
.processSync(strippedContent).contents
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownPreview;
|
||||
|
|
19
ui/component/embedPlayButton/index.js
Normal file
19
ui/component/embedPlayButton/index.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeSelectThumbnailForUri, doResolveUri, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { doFetchCostInfoForUri } from 'lbryinc';
|
||||
import { doSetFloatingUri } from 'redux/actions/content';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import ChannelThumbnail from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
floatingPlayerEnabled: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doResolveUri,
|
||||
doFetchCostInfoForUri,
|
||||
doSetFloatingUri,
|
||||
})(ChannelThumbnail);
|
60
ui/component/embedPlayButton/view.jsx
Normal file
60
ui/component/embedPlayButton/view.jsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
// @flow
|
||||
import React, { useEffect } from 'react';
|
||||
import Button from 'component/button';
|
||||
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
thumbnail: string,
|
||||
claim: ?Claim,
|
||||
doResolveUri: string => void,
|
||||
doFetchCostInfoForUri: string => void,
|
||||
doSetFloatingUri: string => void,
|
||||
floatingPlayerEnabled: boolean,
|
||||
};
|
||||
|
||||
export default function FileRenderFloating(props: Props) {
|
||||
const {
|
||||
uri,
|
||||
thumbnail = '',
|
||||
claim,
|
||||
doResolveUri,
|
||||
doFetchCostInfoForUri,
|
||||
doSetFloatingUri,
|
||||
floatingPlayerEnabled,
|
||||
} = props;
|
||||
const { push } = useHistory();
|
||||
const isMobile = useIsMobile();
|
||||
const hasResolvedUri = claim !== undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasResolvedUri) {
|
||||
doResolveUri(uri);
|
||||
doFetchCostInfoForUri(uri);
|
||||
}
|
||||
}, [uri, hasResolvedUri, doResolveUri, doFetchCostInfoForUri]);
|
||||
|
||||
function handleClick() {
|
||||
if (isMobile || !floatingPlayerEnabled) {
|
||||
const formattedUrl = formatLbryUrlForWeb(uri);
|
||||
push(formattedUrl);
|
||||
} else {
|
||||
doSetFloatingUri(uri);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
className="embed__inline-button"
|
||||
onClick={handleClick}
|
||||
style={{ backgroundImage: `url('${thumbnail.replace(/'/g, "\\'")}')` }}
|
||||
>
|
||||
<FileViewerEmbeddedTitle uri={uri} isInApp />
|
||||
<Button onClick={handleClick} iconSize={30} title={__('Play')} className={'button--icon button--play'} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -3,17 +3,20 @@ import { connect } from 'react-redux';
|
|||
import { makeSelectFileInfoForUri, makeSelectTitleForUri } from 'lbry-redux';
|
||||
import {
|
||||
makeSelectIsPlayerFloating,
|
||||
selectFloatingUri,
|
||||
selectPlayingUri,
|
||||
makeSelectFileRenderModeForUri,
|
||||
makeSelectStreamingUrlForUriWebProxy,
|
||||
} from 'redux/selectors/content';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { doCloseFloatingPlayer } from 'redux/actions/content';
|
||||
import { withRouter } from 'react-router';
|
||||
import FileRenderFloating from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
const uri = selectPlayingUri(state);
|
||||
const floatingUri = selectFloatingUri(state);
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const uri = floatingUri || playingUri;
|
||||
return {
|
||||
uri,
|
||||
title: makeSelectTitleForUri(uri)(state),
|
||||
|
@ -26,7 +29,7 @@ const select = (state, props) => {
|
|||
};
|
||||
|
||||
const perform = dispatch => ({
|
||||
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
|
||||
closeFloatingPlayer: () => dispatch(doCloseFloatingPlayer(null)),
|
||||
});
|
||||
|
||||
export default withRouter(connect(select, perform)(FileRenderFloating));
|
||||
|
|
|
@ -21,13 +21,21 @@ type Props = {
|
|||
streamingUrl?: string,
|
||||
title: ?string,
|
||||
floatingPlayerEnabled: boolean,
|
||||
clearPlayingUri: () => void,
|
||||
closeFloatingPlayer: () => void,
|
||||
renderMode: string,
|
||||
};
|
||||
|
||||
export default function FileRenderFloating(props: Props) {
|
||||
const { fileInfo, uri, streamingUrl, title, isFloating, clearPlayingUri, floatingPlayerEnabled, renderMode } = props;
|
||||
|
||||
const {
|
||||
fileInfo,
|
||||
uri,
|
||||
streamingUrl,
|
||||
title,
|
||||
isFloating,
|
||||
closeFloatingPlayer,
|
||||
floatingPlayerEnabled,
|
||||
renderMode,
|
||||
} = props;
|
||||
const isMobile = useIsMobile();
|
||||
const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect');
|
||||
const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
|
||||
|
@ -111,7 +119,7 @@ export default function FileRenderFloating(props: Props) {
|
|||
<Button navigate={uri} icon={ICONS.VIEW} button="primary" />
|
||||
</Tooltip>
|
||||
<Tooltip label={__('Close')}>
|
||||
<Button onClick={clearPlayingUri} icon={ICONS.REMOVE} button="primary" />
|
||||
<Button onClick={closeFloatingPlayer} icon={ICONS.REMOVE} button="primary" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
|
38
ui/component/fileViewerEmbeddedTitle/view.jsx
Normal file
38
ui/component/fileViewerEmbeddedTitle/view.jsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
import { withRouter } from 'react-router';
|
||||
import { URL } from 'config';
|
||||
import * as ICONS from 'constants/icons';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
title: ?string,
|
||||
isInApp: boolean,
|
||||
};
|
||||
|
||||
function FileViewerEmbeddedTitle(props: Props) {
|
||||
const { uri, title, isInApp } = props;
|
||||
|
||||
let contentLink = `${formatLbryUrlForWeb(uri)}`;
|
||||
|
||||
if (!isInApp) {
|
||||
contentLink = `${contentLink}?src=embed`;
|
||||
}
|
||||
|
||||
const lbryLinkProps = isInApp ? { navigate: '/' } : { href: URL };
|
||||
|
||||
return (
|
||||
<div className="file-viewer__embedded-title">
|
||||
<Button label={title} button="link" navigate={contentLink} />
|
||||
<Button
|
||||
className="file-viewer__overlay-logo file-viewer__embedded-title-logo"
|
||||
icon={ICONS.LBRY}
|
||||
{...lbryLinkProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(FileViewerEmbeddedTitle);
|
|
@ -11,7 +11,7 @@ import { FORCE_CONTENT_TYPE_PLAYER } from 'constants/claim';
|
|||
import AutoplayCountdown from 'component/autoplayCountdown';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import FileViewerEmbeddedEnded from 'lbrytv/component/fileViewerEmbeddedEnded';
|
||||
import FileViewerEmbeddedTitle from 'lbrytv/component/fileViewerEmbeddedTitle';
|
||||
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
|
||||
const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
|
||||
|
|
|
@ -82,6 +82,7 @@ export const PUBLISH_STARTED = 'PUBLISH_STARTED';
|
|||
export const PUBLISH_COMPLETED = 'PUBLISH_COMPLETED';
|
||||
export const PUBLISH_FAILED = 'PUBLISH_FAILED';
|
||||
export const SET_PLAYING_URI = 'SET_PLAYING_URI';
|
||||
export const SET_FLOATING_URI = 'SET_FLOATING_URI';
|
||||
export const SET_CONTENT_POSITION = 'SET_CONTENT_POSITION';
|
||||
export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED';
|
||||
export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import * as NOTIFICATION_TYPES from 'constants/subscriptions';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
|
@ -10,7 +11,6 @@ import { push } from 'connected-react-router';
|
|||
import { doUpdateUnreadSubscriptions } from 'redux/actions/subscriptions';
|
||||
import { makeSelectUnreadByChannel } from 'redux/selectors/subscriptions';
|
||||
import {
|
||||
ACTIONS,
|
||||
Lbry,
|
||||
makeSelectFileInfoForUri,
|
||||
selectFileInfosByOutpoint,
|
||||
|
@ -24,6 +24,7 @@ import {
|
|||
import { makeSelectCostInfoForUri, Lbryio } from 'lbryinc';
|
||||
import { makeSelectClientSetting, selectosNotificationsEnabled, selectDaemonSettings } from 'redux/selectors/settings';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
import { selectFloatingUri } from 'redux/selectors/content';
|
||||
|
||||
const DOWNLOAD_POLL_INTERVAL = 250;
|
||||
|
||||
|
@ -134,6 +135,28 @@ export function doSetPlayingUri(uri: ?string) {
|
|||
};
|
||||
}
|
||||
|
||||
export function doSetFloatingUri(uri: ?string) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_FLOATING_URI,
|
||||
data: { uri },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doCloseFloatingPlayer() {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const floatingUri = selectFloatingUri(state);
|
||||
|
||||
if (floatingUri) {
|
||||
dispatch(doSetFloatingUri(null));
|
||||
} else {
|
||||
dispatch(doSetPlayingUri(null));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doPurchaseUriWrapper(uri: string, cost: number, saveFile: boolean, cb: ?() => void) {
|
||||
return (dispatch: Dispatch, getState: () => any) => {
|
||||
function onSuccess(fileInfo) {
|
||||
|
@ -249,12 +272,3 @@ export function doClearContentHistoryAll() {
|
|||
dispatch({ type: ACTIONS.CLEAR_CONTENT_HISTORY_ALL });
|
||||
};
|
||||
}
|
||||
|
||||
export function doSetHistoryPage(page: string) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_CONTENT_HISTORY_PAGE,
|
||||
data: { page },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as ACTIONS from 'constants/action_types';
|
|||
const reducers = {};
|
||||
const defaultState = {
|
||||
playingUri: null,
|
||||
floatingUri: null,
|
||||
channelClaimCounts: {},
|
||||
positions: {},
|
||||
history: [],
|
||||
|
@ -13,6 +14,11 @@ reducers[ACTIONS.SET_PLAYING_URI] = (state, action) =>
|
|||
playingUri: action.data.uri,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SET_FLOATING_URI] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
floatingUri: action.data.uri,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SET_CONTENT_POSITION] = (state, action) => {
|
||||
const { claimId, outpoint, position } = action.data;
|
||||
return {
|
||||
|
|
|
@ -12,9 +12,9 @@ import {
|
|||
selectBalance,
|
||||
selectBlockedChannels,
|
||||
parseURI,
|
||||
buildURI,
|
||||
makeSelectContentTypeForUri,
|
||||
makeSelectFileNameForUri,
|
||||
buildURI,
|
||||
} from 'lbry-redux';
|
||||
import { selectAllCostInfoByUri, makeSelectCostInfoForUri } from 'lbryinc';
|
||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
|
@ -31,17 +31,28 @@ const HISTORY_ITEMS_PER_PAGE = 50;
|
|||
export const selectState = (state: any) => state.content || {};
|
||||
|
||||
export const selectPlayingUri = createSelector(selectState, state => state.playingUri);
|
||||
export const selectFloatingUri = createSelector(selectState, state => state.floatingUri);
|
||||
|
||||
export const makeSelectIsPlaying = (uri: string) => createSelector(selectPlayingUri, playingUri => playingUri === uri);
|
||||
|
||||
// below is dumb, some context: https://stackoverflow.com/questions/39622864/access-react-router-state-in-selector
|
||||
export const makeSelectIsPlayerFloating = location =>
|
||||
createSelector(selectPlayingUri, playingUri => {
|
||||
export const makeSelectIsPlayerFloating = (location: UrlLocation) =>
|
||||
createSelector(selectFloatingUri, selectPlayingUri, (floatingUri, playingUri) => {
|
||||
if (floatingUri) {
|
||||
if (!playingUri) {
|
||||
return true;
|
||||
} else {
|
||||
return floatingUri !== playingUri;
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no floatingPlayer explicitly set, see if the playingUri can float
|
||||
try {
|
||||
const { pathname, hash } = location;
|
||||
const newpath = buildURI(parseURI(pathname.slice(1).replace(/:/g, '#')));
|
||||
return playingUri && playingUri !== newpath + hash;
|
||||
} catch (e) {}
|
||||
|
||||
return !!playingUri;
|
||||
});
|
||||
|
||||
|
|
|
@ -8,3 +8,20 @@
|
|||
align-items: center;
|
||||
background-color: var(--color-black);
|
||||
}
|
||||
|
||||
.embed__inline-button {
|
||||
@include thumbnail;
|
||||
position: relative;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,6 +229,10 @@
|
|||
.button {
|
||||
color: var(--color-white);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
|
|
@ -46,18 +46,6 @@
|
|||
font-size: 1em;
|
||||
}
|
||||
|
||||
// Paragraphs
|
||||
p {
|
||||
svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
margin-left: 0.2rem;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
p,
|
||||
blockquote,
|
||||
dl,
|
||||
|
|
Loading…
Reference in a new issue