Merge branch 'master' into protocol
This commit is contained in:
commit
429528608e
34 changed files with 376 additions and 99 deletions
|
@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Add watch later to hover action for last used playlist on popup _community pr!_ ([#6274](https://github.com/lbryio/lbry-desktop/pull/6274))
|
- Add watch later to hover action for last used playlist on popup _community pr!_ ([#6274](https://github.com/lbryio/lbry-desktop/pull/6274))
|
||||||
- Open in desktop (web feature) _community pr!_ ([#6667](https://github.com/lbryio/lbry-desktop/pull/6667))
|
- Open in desktop (web feature) _community pr!_ ([#6667](https://github.com/lbryio/lbry-desktop/pull/6667))
|
||||||
- Add confirmation on comment removal _community pr!_ ([#6563](https://github.com/lbryio/lbry-desktop/pull/6563))
|
- Add confirmation on comment removal _community pr!_ ([#6563](https://github.com/lbryio/lbry-desktop/pull/6563))
|
||||||
|
- Show on content page if a file is part of a playlist already _community pr!_([#6393](https://github.com/lbryio/lbry-desktop/pull/6393))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Use Canonical Url for copy link ([#6500](https://github.com/lbryio/lbry-desktop/pull/6500))
|
- Use Canonical Url for copy link ([#6500](https://github.com/lbryio/lbry-desktop/pull/6500))
|
||||||
|
|
|
@ -152,7 +152,7 @@
|
||||||
"imagesloaded": "^4.1.4",
|
"imagesloaded": "^4.1.4",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#a327385cdf71568dbd15a17f3dcf5f4b83e0966d",
|
"lbry-redux": "lbryio/lbry-redux#7cc9923ed9ff1940b508842af6be44c8da906a60",
|
||||||
"lbryinc": "lbryio/lbryinc#8f9a58bfc8312a65614fd7327661cdcc502c4e59",
|
"lbryinc": "lbryio/lbryinc#8f9a58bfc8312a65614fd7327661cdcc502c4e59",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
|
|
|
@ -209,6 +209,7 @@
|
||||||
"Your comment": "Your comment",
|
"Your comment": "Your comment",
|
||||||
"Post --[button to submit something]--": "Post",
|
"Post --[button to submit something]--": "Post",
|
||||||
"Post --[noun, markdown post tab button]--": "Post",
|
"Post --[noun, markdown post tab button]--": "Post",
|
||||||
|
"Livestream --[noun, livestream tab button]--": "Livestream --[noun, livestream tab button]--",
|
||||||
"Posting...": "Posting...",
|
"Posting...": "Posting...",
|
||||||
"Incompatible daemon": "Incompatible daemon",
|
"Incompatible daemon": "Incompatible daemon",
|
||||||
"Incompatible daemon running": "Incompatible daemon running",
|
"Incompatible daemon running": "Incompatible daemon running",
|
||||||
|
@ -2066,5 +2067,10 @@
|
||||||
"Open in Desktop": "Open in Desktop",
|
"Open in Desktop": "Open in Desktop",
|
||||||
"Show %count% replies": "Show %count% replies",
|
"Show %count% replies": "Show %count% replies",
|
||||||
"Show reply": "Show reply",
|
"Show reply": "Show reply",
|
||||||
|
"Confirm Comment Deletion": "Confirm Comment Deletion",
|
||||||
|
"Remove Comment": "Remove Comment",
|
||||||
|
"Are you sure you want to remove this comment?": "Are you sure you want to remove this comment?",
|
||||||
|
"This comment has a tip associated with it which cannot be reverted.": "This comment has a tip associated with it which cannot be reverted.",
|
||||||
|
"Recent Comments": "Recent Comments",
|
||||||
"--end--": "--end--"
|
"--end--": "--end--"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import CollectionAddButton from './view';
|
import CollectionAddButton from './view';
|
||||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
import { makeSelectClaimForUri, makeSelectClaimUrlInCollection } from 'lbry-redux';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => {
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
const claim = makeSelectClaimForUri(props.uri)(state);
|
||||||
});
|
const permanentUrl = claim && claim.permanent_url;
|
||||||
|
|
||||||
|
return {
|
||||||
|
claim,
|
||||||
|
isSaved: makeSelectClaimUrlInCollection(permanentUrl)(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(select, {
|
export default connect(select, {
|
||||||
doOpenModal,
|
doOpenModal,
|
||||||
|
|
|
@ -11,10 +11,11 @@ type Props = {
|
||||||
fileAction?: boolean,
|
fileAction?: boolean,
|
||||||
type?: boolean,
|
type?: boolean,
|
||||||
claim: Claim,
|
claim: Claim,
|
||||||
|
isSaved: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CollectionAddButton(props: Props) {
|
export default function CollectionAddButton(props: Props) {
|
||||||
const { doOpenModal, uri, fileAction, type = 'playlist', claim } = props;
|
const { doOpenModal, uri, fileAction, type = 'playlist', claim, isSaved } = props;
|
||||||
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
const streamType = (claim && claim.value && claim.value.stream_type) || '';
|
const streamType = (claim && claim.value && claim.value.stream_type) || '';
|
||||||
|
@ -25,9 +26,9 @@ export default function CollectionAddButton(props: Props) {
|
||||||
<Button
|
<Button
|
||||||
button={fileAction ? undefined : 'alt'}
|
button={fileAction ? undefined : 'alt'}
|
||||||
className={classnames({ 'button--file-action': fileAction })}
|
className={classnames({ 'button--file-action': fileAction })}
|
||||||
icon={fileAction ? ICONS.ADD : ICONS.LIBRARY}
|
icon={fileAction ? (!isSaved ? ICONS.ADD : ICONS.STACK) : ICONS.LIBRARY}
|
||||||
iconSize={fileAction ? 22 : undefined}
|
iconSize={fileAction ? 22 : undefined}
|
||||||
label={uri ? __('Save') : __('New List')}
|
label={uri ? (!isSaved ? __('Save') : __('Saved')) : __('New List')}
|
||||||
requiresAuth={IS_WEB}
|
requiresAuth={IS_WEB}
|
||||||
title={__('Add this claim to a list')}
|
title={__('Add this claim to a list')}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
|
@ -47,6 +47,7 @@ type Props = {
|
||||||
searchOptions?: any,
|
searchOptions?: any,
|
||||||
collectionId?: string,
|
collectionId?: string,
|
||||||
showNoSourceClaims?: boolean,
|
showNoSourceClaims?: boolean,
|
||||||
|
onClick?: (e: any, index?: number) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ClaimList(props: Props) {
|
export default function ClaimList(props: Props) {
|
||||||
|
@ -55,6 +56,7 @@ export default function ClaimList(props: Props) {
|
||||||
uris,
|
uris,
|
||||||
headerAltControls,
|
headerAltControls,
|
||||||
loading,
|
loading,
|
||||||
|
id,
|
||||||
persistedStorageKey,
|
persistedStorageKey,
|
||||||
empty,
|
empty,
|
||||||
defaultSort,
|
defaultSort,
|
||||||
|
@ -80,6 +82,7 @@ export default function ClaimList(props: Props) {
|
||||||
searchOptions,
|
searchOptions,
|
||||||
collectionId,
|
collectionId,
|
||||||
showNoSourceClaims,
|
showNoSourceClaims,
|
||||||
|
onClick,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||||
|
@ -107,6 +110,12 @@ export default function ClaimList(props: Props) {
|
||||||
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW);
|
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleClaimClicked(e, index) {
|
||||||
|
if (onClick) {
|
||||||
|
onClick(e, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = debounce((e) => {
|
const handleScroll = debounce((e) => {
|
||||||
if (page && pageSize && onScrollBottom) {
|
if (page && pageSize && onScrollBottom) {
|
||||||
|
@ -191,6 +200,8 @@ export default function ClaimList(props: Props) {
|
||||||
{injectedItem && index === 4 && <li>{injectedItem}</li>}
|
{injectedItem && index === 4 && <li>{injectedItem}</li>}
|
||||||
<ClaimPreview
|
<ClaimPreview
|
||||||
uri={uri}
|
uri={uri}
|
||||||
|
containerId={id}
|
||||||
|
indexInContainer={index}
|
||||||
type={type}
|
type={type}
|
||||||
active={activeUri && uri === activeUri}
|
active={activeUri && uri === activeUri}
|
||||||
hideMenu={hideMenu}
|
hideMenu={hideMenu}
|
||||||
|
@ -209,6 +220,7 @@ export default function ClaimList(props: Props) {
|
||||||
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch';
|
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch';
|
||||||
}}
|
}}
|
||||||
live={resolveLive(index)}
|
live={resolveLive(index)}
|
||||||
|
onClick={handleClaimClicked}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -106,7 +106,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
claimType,
|
claimType,
|
||||||
pageSize,
|
pageSize,
|
||||||
defaultClaimType,
|
defaultClaimType,
|
||||||
streamType = SIMPLE_SITE ? CS.FILE_VIDEO : undefined,
|
streamType = SIMPLE_SITE ? [CS.FILE_VIDEO, CS.FILE_AUDIO] : undefined,
|
||||||
defaultStreamType = SIMPLE_SITE ? CS.FILE_VIDEO : undefined, // add param for DEFAULT_STREAM_TYPE
|
defaultStreamType = SIMPLE_SITE ? CS.FILE_VIDEO : undefined, // add param for DEFAULT_STREAM_TYPE
|
||||||
freshness,
|
freshness,
|
||||||
defaultFreshness = CS.FRESH_WEEK,
|
defaultFreshness = CS.FRESH_WEEK,
|
||||||
|
@ -319,7 +319,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamTypeParam && streamTypeParam !== CS.CONTENT_ALL && claimType !== CS.CLAIM_CHANNEL) {
|
if (streamTypeParam && streamTypeParam !== CS.CONTENT_ALL && claimType !== CS.CLAIM_CHANNEL) {
|
||||||
options.stream_types = [streamTypeParam];
|
options.stream_types = typeof streamTypeParam === 'string' ? [streamTypeParam] : streamTypeParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimTypeParam) {
|
if (claimTypeParam) {
|
||||||
|
|
|
@ -400,6 +400,13 @@ function ClaimMenuList(props: Props) {
|
||||||
)}
|
)}
|
||||||
<hr className="menu__separator" />
|
<hr className="menu__separator" />
|
||||||
|
|
||||||
|
<MenuItem className="comment__menu-option" onSelect={handleCopyLink}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={ICONS.COPY_LINK} />
|
||||||
|
{__('Copy Link')}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
{isChannelPage && IS_WEB && rssUrl && (
|
{isChannelPage && IS_WEB && rssUrl && (
|
||||||
<MenuItem className="comment__menu-option" onSelect={handleCopyRssLink}>
|
<MenuItem className="comment__menu-option" onSelect={handleCopyRssLink}>
|
||||||
<div className="menu__link">
|
<div className="menu__link">
|
||||||
|
@ -418,13 +425,6 @@ function ClaimMenuList(props: Props) {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MenuItem className="comment__menu-option" onSelect={handleCopyLink}>
|
|
||||||
<div className="menu__link">
|
|
||||||
<Icon aria-hidden icon={ICONS.SHARE} />
|
|
||||||
{__('Copy Link')}
|
|
||||||
</div>
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
{!claimIsMine && !isMyCollection && (
|
{!claimIsMine && !isMyCollection && (
|
||||||
<MenuItem className="comment__menu-option" onSelect={handleReportContent}>
|
<MenuItem className="comment__menu-option" onSelect={handleReportContent}>
|
||||||
<div className="menu__link">
|
<div className="menu__link">
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ClaimPreviewNoContent from './claim-preview-no-content';
|
||||||
import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
|
import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
import { CONTAINER_ID } from 'constants/navigation';
|
||||||
|
|
||||||
const AbandonedChannelPreview = lazyImport(() =>
|
const AbandonedChannelPreview = lazyImport(() =>
|
||||||
import('component/abandonedChannelPreview' /* webpackChunkName: "abandonedChannelPreview" */)
|
import('component/abandonedChannelPreview' /* webpackChunkName: "abandonedChannelPreview" */)
|
||||||
|
@ -45,7 +46,7 @@ type Props = {
|
||||||
reflectingProgress?: any, // fxme
|
reflectingProgress?: any, // fxme
|
||||||
resolveUri: (string) => void,
|
resolveUri: (string) => void,
|
||||||
isResolvingUri: boolean,
|
isResolvingUri: boolean,
|
||||||
history: { push: (string) => void },
|
history: { push: (string | any) => void },
|
||||||
title: string,
|
title: string,
|
||||||
nsfw: boolean,
|
nsfw: boolean,
|
||||||
placeholder: string,
|
placeholder: string,
|
||||||
|
@ -65,7 +66,7 @@ type Props = {
|
||||||
actions: boolean | Node | string | number,
|
actions: boolean | Node | string | number,
|
||||||
properties: boolean | Node | string | number | ((Claim) => Node),
|
properties: boolean | Node | string | number | ((Claim) => Node),
|
||||||
empty?: Node,
|
empty?: Node,
|
||||||
onClick?: (any) => any,
|
onClick?: (e: any, index?: number) => any,
|
||||||
streamingUrl: ?string,
|
streamingUrl: ?string,
|
||||||
getFile: (string) => void,
|
getFile: (string) => void,
|
||||||
customShouldHide?: (Claim) => boolean,
|
customShouldHide?: (Claim) => boolean,
|
||||||
|
@ -88,6 +89,8 @@ type Props = {
|
||||||
disableNavigation?: boolean,
|
disableNavigation?: boolean,
|
||||||
mediaDuration?: string,
|
mediaDuration?: string,
|
||||||
date?: any,
|
date?: any,
|
||||||
|
containerId?: string, // ID or name of the container (e.g. ClaimList, HOC, etc.) that this is in.
|
||||||
|
indexInContainer?: number, // The index order of this component within 'containerId'.
|
||||||
};
|
};
|
||||||
|
|
||||||
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
|
@ -149,6 +152,8 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
isCollectionMine,
|
isCollectionMine,
|
||||||
collectionUris,
|
collectionUris,
|
||||||
disableNavigation,
|
disableNavigation,
|
||||||
|
containerId,
|
||||||
|
indexInContainer,
|
||||||
} = props;
|
} = props;
|
||||||
const isCollection = claim && claim.value_type === 'collection';
|
const isCollection = claim && claim.value_type === 'collection';
|
||||||
const collectionClaimId = isCollection && claim && claim.claim_id;
|
const collectionClaimId = isCollection && claim && claim.claim_id;
|
||||||
|
@ -202,9 +207,21 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, listId);
|
collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, listId);
|
||||||
navigateUrl = navigateUrl + `?` + collectionParams.toString();
|
navigateUrl = navigateUrl + `?` + collectionParams.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleNavLinkClick = (e) => {
|
||||||
|
if (onClick) {
|
||||||
|
onClick(e, indexInContainer);
|
||||||
|
}
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
const navLinkProps = {
|
const navLinkProps = {
|
||||||
to: navigateUrl,
|
to: {
|
||||||
onClick: (e) => e.stopPropagation(),
|
pathname: navigateUrl,
|
||||||
|
state: containerId ? { [CONTAINER_ID]: containerId } : undefined,
|
||||||
|
},
|
||||||
|
onClick: (e) => handleNavLinkClick(e),
|
||||||
|
onAuxClick: (e) => handleNavLinkClick(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
// do not block abandoned and nsfw claims if showUserBlocked is passed
|
// do not block abandoned and nsfw claims if showUserBlocked is passed
|
||||||
|
@ -250,11 +267,14 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
|
|
||||||
function handleOnClick(e) {
|
function handleOnClick(e) {
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
onClick(e);
|
onClick(e, indexInContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claim && !pending && !disableNavigation) {
|
if (claim && !pending && !disableNavigation) {
|
||||||
history.push(navigateUrl);
|
history.push({
|
||||||
|
pathname: navigateUrl,
|
||||||
|
state: containerId ? { [CONTAINER_ID]: containerId } : undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,6 +159,12 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
new Set(mutedUris.concat(blockedUris).map((uri) => splitBySeparator(uri)[1]))
|
new Set(mutedUris.concat(blockedUris).map((uri) => splitBySeparator(uri)[1]))
|
||||||
);
|
);
|
||||||
const liveUris = [];
|
const liveUris = [];
|
||||||
|
let streamTypesParam;
|
||||||
|
if (streamTypes) {
|
||||||
|
streamTypesParam = streamTypes;
|
||||||
|
} else if (SIMPLE_SITE && !hasNoSource && streamTypes !== null) {
|
||||||
|
streamTypesParam = [CS.FILE_VIDEO, CS.FILE_AUDIO];
|
||||||
|
}
|
||||||
|
|
||||||
const [prevUris, setPrevUris] = React.useState([]);
|
const [prevUris, setPrevUris] = React.useState([]);
|
||||||
|
|
||||||
|
@ -192,8 +198,7 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
channel_ids: channelIds || [],
|
channel_ids: channelIds || [],
|
||||||
not_channel_ids: mutedAndBlockedChannelIds,
|
not_channel_ids: mutedAndBlockedChannelIds,
|
||||||
order_by: orderBy || ['trending_group', 'trending_mixed'],
|
order_by: orderBy || ['trending_group', 'trending_mixed'],
|
||||||
stream_types:
|
stream_types: streamTypesParam,
|
||||||
streamTypes === null ? undefined : SIMPLE_SITE && !hasNoSource ? [CS.FILE_VIDEO, CS.FILE_AUDIO] : undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ENABLE_NO_SOURCE_CLAIMS && hasNoSource) {
|
if (ENABLE_NO_SOURCE_CLAIMS && hasNoSource) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import usePersistedState from 'effects/use-persisted-state';
|
||||||
import { ENABLE_COMMENT_REACTIONS } from 'config';
|
import { ENABLE_COMMENT_REACTIONS } from 'config';
|
||||||
import Empty from 'component/common/empty';
|
import Empty from 'component/common/empty';
|
||||||
import debounce from 'util/debounce';
|
import debounce from 'util/debounce';
|
||||||
|
import { useIsMobile } from 'effects/use-screensize';
|
||||||
|
|
||||||
const DEBOUNCE_SCROLL_HANDLER_MS = 200;
|
const DEBOUNCE_SCROLL_HANDLER_MS = 200;
|
||||||
|
|
||||||
|
@ -74,6 +75,8 @@ function CommentList(props: Props) {
|
||||||
const DEFAULT_SORT = ENABLE_COMMENT_REACTIONS ? SORT_BY.POPULARITY : SORT_BY.NEWEST;
|
const DEFAULT_SORT = ENABLE_COMMENT_REACTIONS ? SORT_BY.POPULARITY : SORT_BY.NEWEST;
|
||||||
const [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT);
|
const [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT);
|
||||||
const [page, setPage] = React.useState(0);
|
const [page, setPage] = React.useState(0);
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const [expandedComments, setExpandedComments] = React.useState(!isMobile);
|
||||||
const totalFetchedComments = allCommentIds ? allCommentIds.length : 0;
|
const totalFetchedComments = allCommentIds ? allCommentIds.length : 0;
|
||||||
|
|
||||||
// Display comments immediately if not fetching reactions
|
// Display comments immediately if not fetching reactions
|
||||||
|
@ -191,7 +194,7 @@ function CommentList(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCommentScroll = debounce(() => {
|
const handleCommentScroll = debounce(() => {
|
||||||
if (shouldFetchNextPage(page, topLevelTotalPages, window, document)) {
|
if (!isMobile && shouldFetchNextPage(page, topLevelTotalPages, window, document)) {
|
||||||
setPage(page + 1);
|
setPage(page + 1);
|
||||||
}
|
}
|
||||||
}, DEBOUNCE_SCROLL_HANDLER_MS);
|
}, DEBOUNCE_SCROLL_HANDLER_MS);
|
||||||
|
@ -205,6 +208,7 @@ function CommentList(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
isMobile,
|
||||||
page,
|
page,
|
||||||
moreBelow,
|
moreBelow,
|
||||||
spinnerRef,
|
spinnerRef,
|
||||||
|
@ -279,7 +283,13 @@ function CommentList(props: Props) {
|
||||||
<Empty padded text={__('That was pretty deep. What do you think?')} />
|
<Empty padded text={__('That was pretty deep. What do you think?')} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ul className="comments" ref={commentRef}>
|
<ul
|
||||||
|
className={classnames({
|
||||||
|
comments: expandedComments,
|
||||||
|
'comments--contracted': !expandedComments,
|
||||||
|
})}
|
||||||
|
ref={commentRef}
|
||||||
|
>
|
||||||
{topLevelComments &&
|
{topLevelComments &&
|
||||||
displayedComments &&
|
displayedComments &&
|
||||||
displayedComments.map((comment) => {
|
displayedComments.map((comment) => {
|
||||||
|
@ -307,7 +317,36 @@ function CommentList(props: Props) {
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{(isFetchingComments || moreBelow) && (
|
{isMobile && (
|
||||||
|
<div className="card__bottom-actions--comments">
|
||||||
|
{moreBelow && (
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
title={!expandedComments ? __('Expand Comments') : __('Load More')}
|
||||||
|
label={!expandedComments ? __('Expand') : __('More')}
|
||||||
|
onClick={() => {
|
||||||
|
if (!expandedComments) {
|
||||||
|
setExpandedComments(true);
|
||||||
|
} else {
|
||||||
|
setPage(page + 1);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{expandedComments && (
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
title={__('Collapse Thread')}
|
||||||
|
label={__('Collapse')}
|
||||||
|
onClick={() => {
|
||||||
|
setExpandedComments(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(isFetchingComments || (!isMobile && moreBelow)) && (
|
||||||
<div className="main--empty" ref={spinnerRef}>
|
<div className="main--empty" ref={spinnerRef}>
|
||||||
<Spinner type="small" />
|
<Spinner type="small" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -31,7 +31,17 @@ export default function Logo(props: Props) {
|
||||||
if (LOGO_TEXT_LIGHT) {
|
if (LOGO_TEXT_LIGHT) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<img className={'header__navigation-logo'} src={LOGO_TEXT_LIGHT} />
|
<img className={'embed__overlay-logo'} src={LOGO_TEXT_LIGHT} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return defaultWithLabel;
|
||||||
|
}
|
||||||
|
} else if (type === 'embed-ended') {
|
||||||
|
if (LOGO_TEXT_LIGHT) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img className={'embed__overlay-logo'} src={LOGO_TEXT_LIGHT} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectClaimIsNsfw, makeSelectClaimForUri } from 'lbry-redux';
|
import { makeSelectClaimIsNsfw, makeSelectClaimForUri } from 'lbry-redux';
|
||||||
|
import { doRecommendationUpdate, doRecommendationClicked } from 'redux/actions/content';
|
||||||
import { doFetchRecommendedContent } from 'redux/actions/search';
|
import { doFetchRecommendedContent } from 'redux/actions/search';
|
||||||
import { makeSelectRecommendedContentForUri, selectIsSearching } from 'redux/selectors/search';
|
import { makeSelectRecommendedContentForUri, selectIsSearching } from 'redux/selectors/search';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
@ -17,6 +18,9 @@ const select = (state, props) => ({
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
doFetchRecommendedContent: (uri, mature) => dispatch(doFetchRecommendedContent(uri, mature)),
|
doFetchRecommendedContent: (uri, mature) => dispatch(doFetchRecommendedContent(uri, mature)),
|
||||||
|
doRecommendationUpdate: (claimId, urls, id, parentId) =>
|
||||||
|
dispatch(doRecommendationUpdate(claimId, urls, id, parentId)),
|
||||||
|
doRecommendationClicked: (claimId, index) => dispatch(doRecommendationClicked(claimId, index)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(RecommendedContent);
|
export default connect(select, perform)(RecommendedContent);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { SHOW_ADS } from 'config';
|
import { SHOW_ADS } from 'config';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import ClaimListDiscover from 'component/claimListDiscover';
|
import ClaimListDiscover from 'component/claimListDiscover';
|
||||||
import Ads from 'web/component/ads';
|
import Ads from 'web/component/ads';
|
||||||
|
@ -8,6 +10,7 @@ import Card from 'component/common/card';
|
||||||
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
|
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import { CONTAINER_ID } from 'constants/navigation';
|
||||||
|
|
||||||
const VIEW_ALL_RELATED = 'view_all_related';
|
const VIEW_ALL_RELATED = 'view_all_related';
|
||||||
const VIEW_MORE_FROM = 'view_more_from';
|
const VIEW_MORE_FROM = 'view_more_from';
|
||||||
|
@ -21,6 +24,8 @@ type Props = {
|
||||||
mature: boolean,
|
mature: boolean,
|
||||||
isAuthenticated: boolean,
|
isAuthenticated: boolean,
|
||||||
claim: ?StreamClaim,
|
claim: ?StreamClaim,
|
||||||
|
doRecommendationUpdate: (claimId: string, urls: Array<string>, id: string, parentId: string) => void,
|
||||||
|
doRecommendationClicked: (claimId: string, index: number) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo<Props>(function RecommendedContent(props: Props) {
|
export default React.memo<Props>(function RecommendedContent(props: Props) {
|
||||||
|
@ -33,21 +38,24 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
|
||||||
isSearching,
|
isSearching,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
claim,
|
claim,
|
||||||
|
doRecommendationUpdate,
|
||||||
|
doRecommendationClicked,
|
||||||
} = props;
|
} = props;
|
||||||
const [viewMode, setViewMode] = React.useState(VIEW_ALL_RELATED);
|
const [viewMode, setViewMode] = React.useState(VIEW_ALL_RELATED);
|
||||||
|
const [recommendationId, setRecommendationId] = React.useState('');
|
||||||
|
const [recommendationUrls, setRecommendationUrls] = React.useState();
|
||||||
|
const history = useHistory();
|
||||||
const signingChannel = claim && claim.signing_channel;
|
const signingChannel = claim && claim.signing_channel;
|
||||||
const channelName = signingChannel ? signingChannel.name : null;
|
const channelName = signingChannel ? signingChannel.name : null;
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const isMedium = useIsMediumScreen();
|
const isMedium = useIsMediumScreen();
|
||||||
|
|
||||||
function reorderList(recommendedContent) {
|
React.useEffect(() => {
|
||||||
|
function moveAutoplayNextItemToTop(recommendedContent) {
|
||||||
let newList = recommendedContent;
|
let newList = recommendedContent;
|
||||||
if (newList) {
|
if (newList) {
|
||||||
const index = newList.indexOf(nextRecommendedUri);
|
const index = newList.indexOf(nextRecommendedUri);
|
||||||
if (index === -1) {
|
if (index > 0) {
|
||||||
// This would be weird. Shouldn't happen since it is derived from the same list.
|
|
||||||
} else if (index !== 0) {
|
|
||||||
// Swap the "next" item to the top of the list
|
|
||||||
const a = newList[0];
|
const a = newList[0];
|
||||||
newList[0] = nextRecommendedUri;
|
newList[0] = nextRecommendedUri;
|
||||||
newList[index] = a;
|
newList[index] = a;
|
||||||
|
@ -56,10 +64,44 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
|
||||||
return newList;
|
return newList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listEq(prev, next) {
|
||||||
|
if (prev && next) {
|
||||||
|
return prev.length === next.length && prev.every((value, index) => value === next[index]);
|
||||||
|
} else {
|
||||||
|
return prev === next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRecommendationUrls = moveAutoplayNextItemToTop(recommendedContent);
|
||||||
|
|
||||||
|
if (claim && !listEq(recommendationUrls, newRecommendationUrls)) {
|
||||||
|
const parentId = (history.location.state && history.location.state[CONTAINER_ID]) || '';
|
||||||
|
const id = uuidv4();
|
||||||
|
setRecommendationId(id);
|
||||||
|
setRecommendationUrls(newRecommendationUrls);
|
||||||
|
|
||||||
|
doRecommendationUpdate(claim.claim_id, newRecommendationUrls, id, parentId);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
recommendedContent,
|
||||||
|
nextRecommendedUri,
|
||||||
|
recommendationUrls,
|
||||||
|
setRecommendationUrls,
|
||||||
|
claim,
|
||||||
|
doRecommendationUpdate,
|
||||||
|
history.location.state,
|
||||||
|
]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
doFetchRecommendedContent(uri, mature);
|
doFetchRecommendedContent(uri, mature);
|
||||||
}, [uri, mature, doFetchRecommendedContent]);
|
}, [uri, mature, doFetchRecommendedContent]);
|
||||||
|
|
||||||
|
function handleRecommendationClicked(e: any, index: number) {
|
||||||
|
if (claim) {
|
||||||
|
doRecommendationClicked(claim.claim_id, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
isBodyList
|
isBodyList
|
||||||
|
@ -91,12 +133,14 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
|
||||||
<div>
|
<div>
|
||||||
{viewMode === VIEW_ALL_RELATED && (
|
{viewMode === VIEW_ALL_RELATED && (
|
||||||
<ClaimList
|
<ClaimList
|
||||||
|
id={recommendationId}
|
||||||
type="small"
|
type="small"
|
||||||
loading={isSearching}
|
loading={isSearching}
|
||||||
uris={reorderList(recommendedContent)}
|
uris={recommendationUrls}
|
||||||
hideMenu={isMobile}
|
hideMenu={isMobile}
|
||||||
injectedItem={SHOW_ADS && IS_WEB && !isAuthenticated && <Ads small type={'video'} />}
|
injectedItem={SHOW_ADS && IS_WEB && !isAuthenticated && <Ads small type={'video'} />}
|
||||||
empty={__('No related content found')}
|
empty={__('No related content found')}
|
||||||
|
onClick={handleRecommendationClicked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{viewMode === VIEW_MORE_FROM && signingChannel && (
|
{viewMode === VIEW_MORE_FROM && signingChannel && (
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { makeSelectContentPositionForUri } from 'redux/selectors/content';
|
||||||
import VideoViewer from './view';
|
import VideoViewer from './view';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||||
import { makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
|
import { selectDaemonSettings, makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
|
||||||
import { toggleVideoTheaterMode, doSetClientSetting } from 'redux/actions/settings';
|
import { toggleVideoTheaterMode, doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ const select = (state, props) => {
|
||||||
homepageData: selectHomepageData(state),
|
homepageData: selectHomepageData(state),
|
||||||
authenticated: selectUserVerifiedEmail(state),
|
authenticated: selectUserVerifiedEmail(state),
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
shareTelemetry: IS_WEB || selectDaemonSettings(state).share_usage_data,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
// Created by xander on 6/21/2021
|
// Created by xander on 6/21/2021
|
||||||
import videojs from 'video.js';
|
import videojs from 'video.js';
|
||||||
import { v4 as uuidV4 } from 'uuid';
|
import {
|
||||||
|
makeSelectRecommendationId,
|
||||||
|
makeSelectRecommendationParentId,
|
||||||
|
makeSelectRecommendedClaimIds,
|
||||||
|
makeSelectRecommendationClicks,
|
||||||
|
} from 'redux/selectors/content';
|
||||||
|
|
||||||
const VERSION = '0.0.1';
|
const VERSION = '0.0.1';
|
||||||
|
|
||||||
const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
|
const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
|
||||||
|
@ -17,23 +23,28 @@ const RecsysData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function createRecsys(claimId, userId, events, loadedAt, isEmbed) {
|
function createRecsys(claimId, userId, events, loadedAt, isEmbed) {
|
||||||
// TODO: use a UUID generator
|
|
||||||
const uuid = uuidV4();
|
|
||||||
const pageLoadedAt = loadedAt;
|
const pageLoadedAt = loadedAt;
|
||||||
const pageExitedAt = Date.now();
|
const pageExitedAt = Date.now();
|
||||||
|
|
||||||
|
if (window.store) {
|
||||||
|
const state = window.store.getState();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uuid: uuid,
|
uuid: makeSelectRecommendationId(claimId)(state),
|
||||||
parentUuid: null,
|
parentUuid: makeSelectRecommendationParentId(claimId)(state),
|
||||||
uid: userId,
|
uid: userId,
|
||||||
claimId: claimId,
|
claimId: claimId,
|
||||||
pageLoadedAt: pageLoadedAt,
|
pageLoadedAt: pageLoadedAt,
|
||||||
pageExitedAt: pageExitedAt,
|
pageExitedAt: pageExitedAt,
|
||||||
recsysId: recsysId,
|
recsysId: recsysId,
|
||||||
recClaimIds: null,
|
recClaimIds: makeSelectRecommendedClaimIds(claimId)(state),
|
||||||
recClickedVideoIdx: null,
|
recClickedVideoIdx: makeSelectRecommendationClicks(claimId)(state),
|
||||||
events: events,
|
events: events,
|
||||||
isEmbed: isEmbed,
|
isEmbed: isEmbed,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function newRecsysEvent(eventType, offset, arg) {
|
function newRecsysEvent(eventType, offset, arg) {
|
||||||
|
@ -130,8 +141,11 @@ class RecsysPlugin extends Component {
|
||||||
this.loadedAt,
|
this.loadedAt,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
sendRecsysEvents(event);
|
sendRecsysEvents(event);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onPlay(event) {
|
onPlay(event) {
|
||||||
const recsysEvent = newRecsysEvent(RecsysData.event.start, this.player.currentTime());
|
const recsysEvent = newRecsysEvent(RecsysData.event.start, this.player.currentTime());
|
||||||
|
@ -226,7 +240,7 @@ const onPlayerReady = (player, options) => {
|
||||||
* @function plugin
|
* @function plugin
|
||||||
* @param {Object} [options={}]
|
* @param {Object} [options={}]
|
||||||
*/
|
*/
|
||||||
const plugin = function(options) {
|
const plugin = function (options) {
|
||||||
this.ready(() => {
|
this.ready(() => {
|
||||||
onPlayerReady(this, videojs.mergeOptions(defaults, options));
|
onPlayerReady(this, videojs.mergeOptions(defaults, options));
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,6 +56,7 @@ type Props = {
|
||||||
claimId: ?string,
|
claimId: ?string,
|
||||||
userId: ?number,
|
userId: ?number,
|
||||||
allowPreRoll: ?boolean,
|
allowPreRoll: ?boolean,
|
||||||
|
shareTelemetry: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
// type VideoJSOptions = {
|
// type VideoJSOptions = {
|
||||||
|
@ -193,6 +194,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
claimId,
|
claimId,
|
||||||
userId,
|
userId,
|
||||||
// allowPreRoll,
|
// allowPreRoll,
|
||||||
|
shareTelemetry,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [reload, setReload] = useState('initial');
|
const [reload, setReload] = useState('initial');
|
||||||
|
@ -564,11 +566,12 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add recsys plugin
|
// Add recsys plugin
|
||||||
// TODO: Add an if(odysee.com) around this function to only use recsys on odysee
|
if (shareTelemetry) {
|
||||||
player.recsys({
|
player.recsys({
|
||||||
videoId: claimId,
|
videoId: claimId,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// set playsinline for mobile
|
// set playsinline for mobile
|
||||||
// TODO: make this better
|
// TODO: make this better
|
||||||
|
|
|
@ -51,6 +51,7 @@ type Props = {
|
||||||
authenticated: boolean,
|
authenticated: boolean,
|
||||||
userId: number,
|
userId: number,
|
||||||
homepageData?: { [string]: HomepageCat },
|
homepageData?: { [string]: HomepageCat },
|
||||||
|
shareTelemetry: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -84,6 +85,7 @@ function VideoViewer(props: Props) {
|
||||||
homepageData,
|
homepageData,
|
||||||
authenticated,
|
authenticated,
|
||||||
userId,
|
userId,
|
||||||
|
shareTelemetry,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const adApprovedChannelIds = homepageData ? getAllIds(homepageData) : [];
|
const adApprovedChannelIds = homepageData ? getAllIds(homepageData) : [];
|
||||||
|
@ -319,6 +321,7 @@ function VideoViewer(props: Props) {
|
||||||
claimId={claimId}
|
claimId={claimId}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
allowPreRoll={!embedded && !authenticated}
|
allowPreRoll={!embedded && !authenticated}
|
||||||
|
shareTelemetry={shareTelemetry}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -98,6 +98,8 @@ export const CLEAR_CONTENT_POSITION = 'CLEAR_CONTENT_POSITION';
|
||||||
export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED';
|
export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED';
|
||||||
export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI';
|
export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI';
|
||||||
export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL';
|
export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL';
|
||||||
|
export const RECOMMENDATION_UPDATED = 'RECOMMENDATION_UPDATED';
|
||||||
|
export const RECOMMENDATION_CLICKED = 'RECOMMENDATION_CLICKED';
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED';
|
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED';
|
||||||
|
|
1
ui/constants/navigation.js
Normal file
1
ui/constants/navigation.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const CONTAINER_ID = 'CONTAINER_ID';
|
|
@ -125,9 +125,11 @@ function DiscoverPage(props: Props) {
|
||||||
// Assume wild west page if no dynamicRouteProps
|
// Assume wild west page if no dynamicRouteProps
|
||||||
// Not a very good solution, but just doing it for now
|
// Not a very good solution, but just doing it for now
|
||||||
// until we are sure this page will stay around
|
// until we are sure this page will stay around
|
||||||
|
// TODO: find a better way to determine discover / wild west vs other modes release times
|
||||||
|
// for now including && !tags so that
|
||||||
releaseTime={
|
releaseTime={
|
||||||
SIMPLE_SITE
|
SIMPLE_SITE
|
||||||
? !dynamicRouteProps && `>${Math.floor(moment().subtract(1, 'day').startOf('week').unix())}`
|
? !dynamicRouteProps && !tags && `>${Math.floor(moment().subtract(1, 'day').startOf('week').unix())}`
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
feeAmount={SIMPLE_SITE ? !dynamicRouteProps && CS.FEE_AMOUNT_ANY : undefined}
|
feeAmount={SIMPLE_SITE ? !dynamicRouteProps && CS.FEE_AMOUNT_ANY : undefined}
|
||||||
|
|
|
@ -40,7 +40,9 @@ function TopPage(props: Props) {
|
||||||
label={__('Repost Here')}
|
label={__('Repost Here')}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
publish: <Button button="secondary" onClick={() => beginPublish(queryName)} label={'Publish Here'} />,
|
publish: (
|
||||||
|
<Button button="secondary" onClick={() => beginPublish(queryName)} label={__('Publish Here')} />
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
%repost% %publish%
|
%repost% %publish%
|
||||||
|
|
|
@ -43,7 +43,7 @@ export function doUpdateLoadStatus(uri: string, outpoint: string) {
|
||||||
full_status: true,
|
full_status: true,
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 1,
|
page_size: 1,
|
||||||
}).then(result => {
|
}).then((result) => {
|
||||||
const { items: fileInfos } = result;
|
const { items: fileInfos } = result;
|
||||||
const fileInfo = fileInfos[0];
|
const fileInfo = fileInfos[0];
|
||||||
if (!fileInfo || fileInfo.written_bytes === 0) {
|
if (!fileInfo || fileInfo.written_bytes === 0) {
|
||||||
|
@ -261,3 +261,21 @@ export function doClearContentHistoryAll() {
|
||||||
dispatch({ type: ACTIONS.CLEAR_CONTENT_HISTORY_ALL });
|
dispatch({ type: ACTIONS.CLEAR_CONTENT_HISTORY_ALL });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const doRecommendationUpdate = (claimId: string, urls: Array<string>, id: string, parentId: string) => (
|
||||||
|
dispatch: Dispatch
|
||||||
|
) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.RECOMMENDATION_UPDATED,
|
||||||
|
data: { claimId, urls, id, parentId },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const doRecommendationClicked = (claimId: string, index: number) => (dispatch: Dispatch) => {
|
||||||
|
if (index !== undefined && index !== null) {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.RECOMMENDATION_CLICKED,
|
||||||
|
data: { claimId, index },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -7,6 +7,10 @@ const defaultState = {
|
||||||
channelClaimCounts: {},
|
channelClaimCounts: {},
|
||||||
positions: {},
|
positions: {},
|
||||||
history: [],
|
history: [],
|
||||||
|
recommendationId: {}, // { "claimId": "recommendationId" }
|
||||||
|
recommendationParentId: {}, // { "claimId": "referrerId" }
|
||||||
|
recommendationUrls: {}, // { "claimId": [lbryUrls...] }
|
||||||
|
recommendationClicks: {}, // { "claimId": [clicked indices...] }
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.SET_PRIMARY_URI] = (state, action) =>
|
reducers[ACTIONS.SET_PRIMARY_URI] = (state, action) =>
|
||||||
|
@ -73,7 +77,7 @@ reducers[ACTIONS.SET_CONTENT_LAST_VIEWED] = (state, action) => {
|
||||||
const { uri, lastViewed } = action.data;
|
const { uri, lastViewed } = action.data;
|
||||||
const { history } = state;
|
const { history } = state;
|
||||||
const historyObj = { uri, lastViewed };
|
const historyObj = { uri, lastViewed };
|
||||||
const index = history.findIndex(i => i.uri === uri);
|
const index = history.findIndex((i) => i.uri === uri);
|
||||||
const newHistory =
|
const newHistory =
|
||||||
index === -1
|
index === -1
|
||||||
? [historyObj].concat(history)
|
? [historyObj].concat(history)
|
||||||
|
@ -84,7 +88,7 @@ reducers[ACTIONS.SET_CONTENT_LAST_VIEWED] = (state, action) => {
|
||||||
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_URI] = (state, action) => {
|
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_URI] = (state, action) => {
|
||||||
const { uri } = action.data;
|
const { uri } = action.data;
|
||||||
const { history } = state;
|
const { history } = state;
|
||||||
const index = history.findIndex(i => i.uri === uri);
|
const index = history.findIndex((i) => i.uri === uri);
|
||||||
return index === -1
|
return index === -1
|
||||||
? state
|
? state
|
||||||
: {
|
: {
|
||||||
|
@ -93,7 +97,44 @@ reducers[ACTIONS.CLEAR_CONTENT_HISTORY_URI] = (state, action) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_ALL] = state => ({ ...state, history: [] });
|
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_ALL] = (state) => ({ ...state, history: [] });
|
||||||
|
|
||||||
|
reducers[ACTIONS.RECOMMENDATION_UPDATED] = (state, action) => {
|
||||||
|
const { claimId, urls, id, parentId } = action.data;
|
||||||
|
const recommendationId = Object.assign({}, state.recommendationId);
|
||||||
|
const recommendationParentId = Object.assign({}, state.recommendationParentId);
|
||||||
|
const recommendationUrls = Object.assign({}, state.recommendationUrls);
|
||||||
|
const recommendationClicks = Object.assign({}, state.recommendationClicks);
|
||||||
|
|
||||||
|
if (urls && urls.length > 0) {
|
||||||
|
recommendationId[claimId] = id;
|
||||||
|
recommendationParentId[claimId] = parentId;
|
||||||
|
recommendationUrls[claimId] = urls;
|
||||||
|
recommendationClicks[claimId] = [];
|
||||||
|
} else {
|
||||||
|
delete recommendationId[claimId];
|
||||||
|
delete recommendationParentId[claimId];
|
||||||
|
delete recommendationUrls[claimId];
|
||||||
|
delete recommendationClicks[claimId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...state, recommendationId, recommendationParentId, recommendationUrls, recommendationClicks };
|
||||||
|
};
|
||||||
|
|
||||||
|
reducers[ACTIONS.RECOMMENDATION_CLICKED] = (state, action) => {
|
||||||
|
const { claimId, index } = action.data;
|
||||||
|
const recommendationClicks = Object.assign({}, state.recommendationClicks);
|
||||||
|
|
||||||
|
if (state.recommendationUrls[claimId] && index >= 0 && index < state.recommendationUrls[claimId].length) {
|
||||||
|
if (recommendationClicks[claimId]) {
|
||||||
|
recommendationClicks[claimId].push(index);
|
||||||
|
} else {
|
||||||
|
recommendationClicks[claimId] = [index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...state, recommendationClicks };
|
||||||
|
};
|
||||||
|
|
||||||
// reducers[LBRY_REDUX_ACTIONS.PURCHASE_URI_FAILED] = (state, action) => {
|
// reducers[LBRY_REDUX_ACTIONS.PURCHASE_URI_FAILED] = (state, action) => {
|
||||||
// return {
|
// return {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
makeSelectFileNameForUri,
|
makeSelectFileNameForUri,
|
||||||
normalizeURI,
|
normalizeURI,
|
||||||
selectMyActiveClaims,
|
selectMyActiveClaims,
|
||||||
|
selectClaimIdsByUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { makeSelectRecommendedContentForUri } from 'redux/selectors/search';
|
import { makeSelectRecommendedContentForUri } from 'redux/selectors/search';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
|
@ -250,7 +251,7 @@ export const makeSelectSigningIsMine = (rawUri: string) => {
|
||||||
let uri;
|
let uri;
|
||||||
try {
|
try {
|
||||||
uri = normalizeURI(rawUri);
|
uri = normalizeURI(rawUri);
|
||||||
} catch (e) { }
|
} catch (e) {}
|
||||||
|
|
||||||
return createSelector(selectClaimsByUri, selectMyActiveClaims, (claims, myClaims) => {
|
return createSelector(selectClaimsByUri, selectMyActiveClaims, (claims, myClaims) => {
|
||||||
try {
|
try {
|
||||||
|
@ -262,4 +263,22 @@ export const makeSelectSigningIsMine = (rawUri: string) => {
|
||||||
|
|
||||||
return signingChannel && myClaims.has(signingChannel.claim_id);
|
return signingChannel && myClaims.has(signingChannel.claim_id);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const makeSelectRecommendationId = (claimId: string) =>
|
||||||
|
createSelector(selectState, (state) => state.recommendationId[claimId]);
|
||||||
|
|
||||||
|
export const makeSelectRecommendationParentId = (claimId: string) =>
|
||||||
|
createSelector(selectState, (state) => state.recommendationParentId[claimId]);
|
||||||
|
|
||||||
|
export const makeSelectRecommendedClaimIds = (claimId: string) =>
|
||||||
|
createSelector(selectState, selectClaimIdsByUri, (state, claimIdsByUri) => {
|
||||||
|
const recommendationUrls = state.recommendationUrls[claimId];
|
||||||
|
if (recommendationUrls) {
|
||||||
|
return recommendationUrls.map((url) => claimIdsByUri[url]);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const makeSelectRecommendationClicks = (claimId: string) =>
|
||||||
|
createSelector(selectState, (state) => state.recommendationClicks[claimId]);
|
||||||
|
|
|
@ -391,6 +391,11 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card__bottom-actions--comments {
|
||||||
|
@extend .card__bottom-actions;
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
.card__multi-pane {
|
.card__multi-pane {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
|
|
@ -165,7 +165,7 @@ $actions-z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel__title {
|
.channel__title {
|
||||||
display: inline;
|
display: flex;
|
||||||
margin-right: var(--spacing-s);
|
margin-right: var(--spacing-s);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
|
@ -7,6 +7,14 @@ $thumbnailWidthSmall: 1rem;
|
||||||
margin-top: var(--spacing-l);
|
margin-top: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comments--contracted {
|
||||||
|
@extend .comments;
|
||||||
|
max-height: 5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-mask-image: -webkit-gradient(linear, left 30%, left bottom, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0)));
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
.comments--replies {
|
.comments--replies {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin-left: var(--spacing-s);
|
margin-left: var(--spacing-s);
|
||||||
|
|
|
@ -60,3 +60,8 @@
|
||||||
font-size: var(--font-large);
|
font-size: var(--font-large);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.embed__overlay-logo {
|
||||||
|
max-height: 3.5rem;
|
||||||
|
max-width: 12rem;
|
||||||
|
}
|
||||||
|
|
|
@ -178,6 +178,7 @@ const sharedStateFilters = {
|
||||||
app_welcome_version: { source: 'app', property: 'welcomeVersion' },
|
app_welcome_version: { source: 'app', property: 'welcomeVersion' },
|
||||||
sharing_3P: { source: 'app', property: 'allowAnalytics' },
|
sharing_3P: { source: 'app', property: 'allowAnalytics' },
|
||||||
builtinCollections: { source: 'collections', property: 'builtin' },
|
builtinCollections: { source: 'collections', property: 'builtin' },
|
||||||
|
editedCollections: { source: 'collections', property: 'edited' },
|
||||||
// savedCollections: { source: 'collections', property: 'saved' },
|
// savedCollections: { source: 'collections', property: 'saved' },
|
||||||
unpublishedCollections: { source: 'collections', property: 'unpublished' },
|
unpublishedCollections: { source: 'collections', property: 'unpublished' },
|
||||||
};
|
};
|
||||||
|
|
|
@ -286,7 +286,7 @@ export function GetLinksData(
|
||||||
? `>${Math.floor(moment().subtract(9, 'months').startOf('week').unix())}`
|
? `>${Math.floor(moment().subtract(9, 'months').startOf('week').unix())}`
|
||||||
: `>${Math.floor(moment().subtract(1, 'year').startOf('week').unix())}`,
|
: `>${Math.floor(moment().subtract(1, 'year').startOf('week').unix())}`,
|
||||||
pageSize: getPageSize(subscribedChannels.length > 3 ? (subscribedChannels.length > 6 ? 16 : 8) : 4),
|
pageSize: getPageSize(subscribedChannels.length > 3 ? (subscribedChannels.length > 6 ? 16 : 8) : 4),
|
||||||
streamTypes: CS.FILE_TYPES,
|
streamTypes: null,
|
||||||
channelIds: subscribedChannels.map((subscription: Subscription) => {
|
channelIds: subscribedChannels.map((subscription: Subscription) => {
|
||||||
const { channelClaimId } = parseURI(subscription.uri);
|
const { channelClaimId } = parseURI(subscription.uri);
|
||||||
return channelClaimId;
|
return channelClaimId;
|
||||||
|
|
|
@ -38,7 +38,7 @@ function FileViewerEmbeddedEnded(props: Props) {
|
||||||
<div className="file-viewer__overlay">
|
<div className="file-viewer__overlay">
|
||||||
<div className="file-viewer__overlay-secondary">
|
<div className="file-viewer__overlay-secondary">
|
||||||
<Button className="file-viewer__overlay-logo" href={URL}>
|
<Button className="file-viewer__overlay-logo" href={URL}>
|
||||||
<Logo type={'embed'} />
|
<Logo type={'embed-ended'} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="file-viewer__overlay-title">{prompt}</div>
|
<div className="file-viewer__overlay-title">{prompt}</div>
|
||||||
|
|
|
@ -23,13 +23,17 @@ const { getJsBundleId } = require('../bundle-id.js');
|
||||||
const jsBundleId = getJsBundleId();
|
const jsBundleId = getJsBundleId();
|
||||||
|
|
||||||
function insertToHead(fullHtml, htmlToInsert) {
|
function insertToHead(fullHtml, htmlToInsert) {
|
||||||
return fullHtml.replace(
|
const beginStr = '<!-- VARIABLE_HEAD_BEGIN -->';
|
||||||
/<!-- VARIABLE_HEAD_BEGIN -->.*<!-- VARIABLE_HEAD_END -->/s,
|
const finalStr = '<!-- VARIABLE_HEAD_END -->';
|
||||||
`
|
|
||||||
${htmlToInsert || buildOgMetadata()}
|
const beginIndex = fullHtml.indexOf(beginStr);
|
||||||
<script src="/public/ui-${jsBundleId}.js" async></script>
|
const finalIndex = fullHtml.indexOf(finalStr);
|
||||||
`
|
|
||||||
);
|
if (beginIndex > -1 && finalIndex > -1 && finalIndex > beginIndex) {
|
||||||
|
return `${fullHtml.slice(0, beginIndex)}${
|
||||||
|
htmlToInsert || buildOgMetadata()
|
||||||
|
}<script src="/public/ui-${jsBundleId}.js" async></script>${fullHtml.slice(finalIndex + finalStr.length)}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function truncateDescription(description) {
|
function truncateDescription(description) {
|
||||||
|
|
|
@ -10136,9 +10136,9 @@ lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#a327385cdf71568dbd15a17f3dcf5f4b83e0966d:
|
lbry-redux@lbryio/lbry-redux#7cc9923ed9ff1940b508842af6be44c8da906a60:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/a327385cdf71568dbd15a17f3dcf5f4b83e0966d"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/7cc9923ed9ff1940b508842af6be44c8da906a60"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
Loading…
Reference in a new issue