Merge branch 'master' into protocol

This commit is contained in:
Baltazar Gomez 2021-08-03 16:43:28 -05:00 committed by GitHub
commit 429528608e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 376 additions and 99 deletions

View file

@ -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))
- 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))
- 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
- Use Canonical Url for copy link ([#6500](https://github.com/lbryio/lbry-desktop/pull/6500))

View file

@ -152,7 +152,7 @@
"imagesloaded": "^4.1.4",
"json-loader": "^0.5.4",
"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",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",

View file

@ -209,6 +209,7 @@
"Your comment": "Your comment",
"Post --[button to submit something]--": "Post",
"Post --[noun, markdown post tab button]--": "Post",
"Livestream --[noun, livestream tab button]--": "Livestream --[noun, livestream tab button]--",
"Posting...": "Posting...",
"Incompatible daemon": "Incompatible daemon",
"Incompatible daemon running": "Incompatible daemon running",
@ -2066,5 +2067,10 @@
"Open in Desktop": "Open in Desktop",
"Show %count% replies": "Show %count% replies",
"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--"
}

View file

@ -1,11 +1,17 @@
import { connect } from 'react-redux';
import { doOpenModal } from 'redux/actions/app';
import CollectionAddButton from './view';
import { makeSelectClaimForUri } from 'lbry-redux';
import { makeSelectClaimForUri, makeSelectClaimUrlInCollection } from 'lbry-redux';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
});
const select = (state, props) => {
const claim = makeSelectClaimForUri(props.uri)(state);
const permanentUrl = claim && claim.permanent_url;
return {
claim,
isSaved: makeSelectClaimUrlInCollection(permanentUrl)(state),
};
};
export default connect(select, {
doOpenModal,

View file

@ -11,10 +11,11 @@ type Props = {
fileAction?: boolean,
type?: boolean,
claim: Claim,
isSaved: boolean,
};
export default function CollectionAddButton(props: Props) {
const { doOpenModal, uri, fileAction, type = 'playlist', claim } = props;
const { doOpenModal, uri, fileAction, type = 'playlist', claim, isSaved } = props;
// $FlowFixMe
const streamType = (claim && claim.value && claim.value.stream_type) || '';
@ -25,9 +26,9 @@ export default function CollectionAddButton(props: Props) {
<Button
button={fileAction ? undefined : 'alt'}
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}
label={uri ? __('Save') : __('New List')}
label={uri ? (!isSaved ? __('Save') : __('Saved')) : __('New List')}
requiresAuth={IS_WEB}
title={__('Add this claim to a list')}
onClick={(e) => {

View file

@ -47,6 +47,7 @@ type Props = {
searchOptions?: any,
collectionId?: string,
showNoSourceClaims?: boolean,
onClick?: (e: any, index?: number) => void,
};
export default function ClaimList(props: Props) {
@ -55,6 +56,7 @@ export default function ClaimList(props: Props) {
uris,
headerAltControls,
loading,
id,
persistedStorageKey,
empty,
defaultSort,
@ -80,6 +82,7 @@ export default function ClaimList(props: Props) {
searchOptions,
collectionId,
showNoSourceClaims,
onClick,
} = props;
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);
}
function handleClaimClicked(e, index) {
if (onClick) {
onClick(e, index);
}
}
useEffect(() => {
const handleScroll = debounce((e) => {
if (page && pageSize && onScrollBottom) {
@ -191,6 +200,8 @@ export default function ClaimList(props: Props) {
{injectedItem && index === 4 && <li>{injectedItem}</li>}
<ClaimPreview
uri={uri}
containerId={id}
indexInContainer={index}
type={type}
active={activeUri && uri === activeUri}
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';
}}
live={resolveLive(index)}
onClick={handleClaimClicked}
/>
</React.Fragment>
))}

View file

@ -106,7 +106,7 @@ function ClaimListDiscover(props: Props) {
claimType,
pageSize,
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
freshness,
defaultFreshness = CS.FRESH_WEEK,
@ -319,7 +319,7 @@ function ClaimListDiscover(props: Props) {
}
if (streamTypeParam && streamTypeParam !== CS.CONTENT_ALL && claimType !== CS.CLAIM_CHANNEL) {
options.stream_types = [streamTypeParam];
options.stream_types = typeof streamTypeParam === 'string' ? [streamTypeParam] : streamTypeParam;
}
if (claimTypeParam) {

View file

@ -400,6 +400,13 @@ function ClaimMenuList(props: Props) {
)}
<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 && (
<MenuItem className="comment__menu-option" onSelect={handleCopyRssLink}>
<div className="menu__link">
@ -417,14 +424,7 @@ function ClaimMenuList(props: Props) {
</div>
</MenuItem>
)}
<MenuItem className="comment__menu-option" onSelect={handleCopyLink}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.SHARE} />
{__('Copy Link')}
</div>
</MenuItem>
{!claimIsMine && !isMyCollection && (
<MenuItem className="comment__menu-option" onSelect={handleReportContent}>
<div className="menu__link">

View file

@ -29,6 +29,7 @@ import ClaimPreviewNoContent from './claim-preview-no-content';
import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
import Button from 'component/button';
import * as ICONS from 'constants/icons';
import { CONTAINER_ID } from 'constants/navigation';
const AbandonedChannelPreview = lazyImport(() =>
import('component/abandonedChannelPreview' /* webpackChunkName: "abandonedChannelPreview" */)
@ -45,7 +46,7 @@ type Props = {
reflectingProgress?: any, // fxme
resolveUri: (string) => void,
isResolvingUri: boolean,
history: { push: (string) => void },
history: { push: (string | any) => void },
title: string,
nsfw: boolean,
placeholder: string,
@ -65,7 +66,7 @@ type Props = {
actions: boolean | Node | string | number,
properties: boolean | Node | string | number | ((Claim) => Node),
empty?: Node,
onClick?: (any) => any,
onClick?: (e: any, index?: number) => any,
streamingUrl: ?string,
getFile: (string) => void,
customShouldHide?: (Claim) => boolean,
@ -88,6 +89,8 @@ type Props = {
disableNavigation?: boolean,
mediaDuration?: string,
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) => {
@ -149,6 +152,8 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
isCollectionMine,
collectionUris,
disableNavigation,
containerId,
indexInContainer,
} = props;
const isCollection = claim && claim.value_type === 'collection';
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);
navigateUrl = navigateUrl + `?` + collectionParams.toString();
}
const handleNavLinkClick = (e) => {
if (onClick) {
onClick(e, indexInContainer);
}
e.stopPropagation();
};
const navLinkProps = {
to: navigateUrl,
onClick: (e) => e.stopPropagation(),
to: {
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
@ -250,11 +267,14 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
function handleOnClick(e) {
if (onClick) {
onClick(e);
onClick(e, indexInContainer);
}
if (claim && !pending && !disableNavigation) {
history.push(navigateUrl);
history.push({
pathname: navigateUrl,
state: containerId ? { [CONTAINER_ID]: containerId } : undefined,
});
}
}

View file

@ -159,6 +159,12 @@ function ClaimTilesDiscover(props: Props) {
new Set(mutedUris.concat(blockedUris).map((uri) => splitBySeparator(uri)[1]))
);
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([]);
@ -192,8 +198,7 @@ function ClaimTilesDiscover(props: Props) {
channel_ids: channelIds || [],
not_channel_ids: mutedAndBlockedChannelIds,
order_by: orderBy || ['trending_group', 'trending_mixed'],
stream_types:
streamTypes === null ? undefined : SIMPLE_SITE && !hasNoSource ? [CS.FILE_VIDEO, CS.FILE_AUDIO] : undefined,
stream_types: streamTypesParam,
};
if (ENABLE_NO_SOURCE_CLAIMS && hasNoSource) {

View file

@ -13,6 +13,7 @@ import usePersistedState from 'effects/use-persisted-state';
import { ENABLE_COMMENT_REACTIONS } from 'config';
import Empty from 'component/common/empty';
import debounce from 'util/debounce';
import { useIsMobile } from 'effects/use-screensize';
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 [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT);
const [page, setPage] = React.useState(0);
const isMobile = useIsMobile();
const [expandedComments, setExpandedComments] = React.useState(!isMobile);
const totalFetchedComments = allCommentIds ? allCommentIds.length : 0;
// Display comments immediately if not fetching reactions
@ -191,7 +194,7 @@ function CommentList(props: Props) {
}
const handleCommentScroll = debounce(() => {
if (shouldFetchNextPage(page, topLevelTotalPages, window, document)) {
if (!isMobile && shouldFetchNextPage(page, topLevelTotalPages, window, document)) {
setPage(page + 1);
}
}, DEBOUNCE_SCROLL_HANDLER_MS);
@ -205,6 +208,7 @@ function CommentList(props: Props) {
}
}
}, [
isMobile,
page,
moreBelow,
spinnerRef,
@ -279,7 +283,13 @@ function CommentList(props: Props) {
<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 &&
displayedComments &&
displayedComments.map((comment) => {
@ -307,7 +317,36 @@ function CommentList(props: Props) {
})}
</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}>
<Spinner type="small" />
</div>

View file

@ -31,7 +31,17 @@ export default function Logo(props: Props) {
if (LOGO_TEXT_LIGHT) {
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 {

View file

@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import { makeSelectClaimIsNsfw, makeSelectClaimForUri } from 'lbry-redux';
import { doRecommendationUpdate, doRecommendationClicked } from 'redux/actions/content';
import { doFetchRecommendedContent } from 'redux/actions/search';
import { makeSelectRecommendedContentForUri, selectIsSearching } from 'redux/selectors/search';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
@ -17,6 +18,9 @@ const select = (state, props) => ({
const perform = (dispatch) => ({
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);

View file

@ -1,6 +1,8 @@
// @flow
import { SHOW_ADS } from 'config';
import React from 'react';
import { useHistory } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';
import ClaimList from 'component/claimList';
import ClaimListDiscover from 'component/claimListDiscover';
import Ads from 'web/component/ads';
@ -8,6 +10,7 @@ import Card from 'component/common/card';
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
import Button from 'component/button';
import classnames from 'classnames';
import { CONTAINER_ID } from 'constants/navigation';
const VIEW_ALL_RELATED = 'view_all_related';
const VIEW_MORE_FROM = 'view_more_from';
@ -21,6 +24,8 @@ type Props = {
mature: boolean,
isAuthenticated: boolean,
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) {
@ -33,33 +38,70 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
isSearching,
isAuthenticated,
claim,
doRecommendationUpdate,
doRecommendationClicked,
} = props;
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 channelName = signingChannel ? signingChannel.name : null;
const isMobile = useIsMobile();
const isMedium = useIsMediumScreen();
function reorderList(recommendedContent) {
let newList = recommendedContent;
if (newList) {
const index = newList.indexOf(nextRecommendedUri);
if (index === -1) {
// 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];
newList[0] = nextRecommendedUri;
newList[index] = a;
React.useEffect(() => {
function moveAutoplayNextItemToTop(recommendedContent) {
let newList = recommendedContent;
if (newList) {
const index = newList.indexOf(nextRecommendedUri);
if (index > 0) {
const a = newList[0];
newList[0] = nextRecommendedUri;
newList[index] = a;
}
}
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;
}
}
return newList;
}
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(() => {
doFetchRecommendedContent(uri, mature);
}, [uri, mature, doFetchRecommendedContent]);
function handleRecommendationClicked(e: any, index: number) {
if (claim) {
doRecommendationClicked(claim.claim_id, index);
}
}
return (
<Card
isBodyList
@ -91,12 +133,14 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
<div>
{viewMode === VIEW_ALL_RELATED && (
<ClaimList
id={recommendationId}
type="small"
loading={isSearching}
uris={reorderList(recommendedContent)}
uris={recommendationUrls}
hideMenu={isMobile}
injectedItem={SHOW_ADS && IS_WEB && !isAuthenticated && <Ads small type={'video'} />}
empty={__('No related content found')}
onClick={handleRecommendationClicked}
/>
)}
{viewMode === VIEW_MORE_FROM && signingChannel && (

View file

@ -7,7 +7,7 @@ import { makeSelectContentPositionForUri } from 'redux/selectors/content';
import VideoViewer from './view';
import { withRouter } from 'react-router';
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 { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
@ -31,6 +31,7 @@ const select = (state, props) => {
homepageData: selectHomepageData(state),
authenticated: selectUserVerifiedEmail(state),
userId: userId,
shareTelemetry: IS_WEB || selectDaemonSettings(state).share_usage_data,
};
};

View file

@ -1,6 +1,12 @@
// Created by xander on 6/21/2021
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 recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
@ -17,23 +23,28 @@ const RecsysData = {
};
function createRecsys(claimId, userId, events, loadedAt, isEmbed) {
// TODO: use a UUID generator
const uuid = uuidV4();
const pageLoadedAt = loadedAt;
const pageExitedAt = Date.now();
return {
uuid: uuid,
parentUuid: null,
uid: userId,
claimId: claimId,
pageLoadedAt: pageLoadedAt,
pageExitedAt: pageExitedAt,
recsysId: recsysId,
recClaimIds: null,
recClickedVideoIdx: null,
events: events,
isEmbed: isEmbed,
};
if (window.store) {
const state = window.store.getState();
return {
uuid: makeSelectRecommendationId(claimId)(state),
parentUuid: makeSelectRecommendationParentId(claimId)(state),
uid: userId,
claimId: claimId,
pageLoadedAt: pageLoadedAt,
pageExitedAt: pageExitedAt,
recsysId: recsysId,
recClaimIds: makeSelectRecommendedClaimIds(claimId)(state),
recClickedVideoIdx: makeSelectRecommendationClicks(claimId)(state),
events: events,
isEmbed: isEmbed,
};
}
return undefined;
}
function newRecsysEvent(eventType, offset, arg) {
@ -130,7 +141,10 @@ class RecsysPlugin extends Component {
this.loadedAt,
false
);
sendRecsysEvents(event);
if (event) {
sendRecsysEvents(event);
}
}
onPlay(event) {
@ -226,7 +240,7 @@ const onPlayerReady = (player, options) => {
* @function plugin
* @param {Object} [options={}]
*/
const plugin = function(options) {
const plugin = function (options) {
this.ready(() => {
onPlayerReady(this, videojs.mergeOptions(defaults, options));
});

View file

@ -56,6 +56,7 @@ type Props = {
claimId: ?string,
userId: ?number,
allowPreRoll: ?boolean,
shareTelemetry: boolean,
};
// type VideoJSOptions = {
@ -193,6 +194,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
claimId,
userId,
// allowPreRoll,
shareTelemetry,
} = props;
const [reload, setReload] = useState('initial');
@ -564,11 +566,12 @@ export default React.memo<Props>(function VideoJs(props: Props) {
});
// Add recsys plugin
// TODO: Add an if(odysee.com) around this function to only use recsys on odysee
player.recsys({
videoId: claimId,
userId: userId,
});
if (shareTelemetry) {
player.recsys({
videoId: claimId,
userId: userId,
});
}
// set playsinline for mobile
// TODO: make this better

View file

@ -51,6 +51,7 @@ type Props = {
authenticated: boolean,
userId: number,
homepageData?: { [string]: HomepageCat },
shareTelemetry: boolean,
};
/*
@ -84,6 +85,7 @@ function VideoViewer(props: Props) {
homepageData,
authenticated,
userId,
shareTelemetry,
} = props;
const adApprovedChannelIds = homepageData ? getAllIds(homepageData) : [];
@ -319,6 +321,7 @@ function VideoViewer(props: Props) {
claimId={claimId}
userId={userId}
allowPreRoll={!embedded && !authenticated}
shareTelemetry={shareTelemetry}
/>
)}
</div>

View file

@ -98,6 +98,8 @@ export const CLEAR_CONTENT_POSITION = 'CLEAR_CONTENT_POSITION';
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_ALL = 'CLEAR_CONTENT_HISTORY_ALL';
export const RECOMMENDATION_UPDATED = 'RECOMMENDATION_UPDATED';
export const RECOMMENDATION_CLICKED = 'RECOMMENDATION_CLICKED';
// Files
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED';

View file

@ -0,0 +1 @@
export const CONTAINER_ID = 'CONTAINER_ID';

View file

@ -125,9 +125,11 @@ function DiscoverPage(props: Props) {
// Assume wild west page if no dynamicRouteProps
// Not a very good solution, but just doing it for now
// 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={
SIMPLE_SITE
? !dynamicRouteProps && `>${Math.floor(moment().subtract(1, 'day').startOf('week').unix())}`
? !dynamicRouteProps && !tags && `>${Math.floor(moment().subtract(1, 'day').startOf('week').unix())}`
: undefined
}
feeAmount={SIMPLE_SITE ? !dynamicRouteProps && CS.FEE_AMOUNT_ANY : undefined}

View file

@ -40,7 +40,9 @@ function TopPage(props: Props) {
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%

View file

@ -43,7 +43,7 @@ export function doUpdateLoadStatus(uri: string, outpoint: string) {
full_status: true,
page: 1,
page_size: 1,
}).then(result => {
}).then((result) => {
const { items: fileInfos } = result;
const fileInfo = fileInfos[0];
if (!fileInfo || fileInfo.written_bytes === 0) {
@ -261,3 +261,21 @@ export function doClearContentHistoryAll() {
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 },
});
}
};

View file

@ -7,6 +7,10 @@ const defaultState = {
channelClaimCounts: {},
positions: {},
history: [],
recommendationId: {}, // { "claimId": "recommendationId" }
recommendationParentId: {}, // { "claimId": "referrerId" }
recommendationUrls: {}, // { "claimId": [lbryUrls...] }
recommendationClicks: {}, // { "claimId": [clicked indices...] }
};
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 { history } = state;
const historyObj = { uri, lastViewed };
const index = history.findIndex(i => i.uri === uri);
const index = history.findIndex((i) => i.uri === uri);
const newHistory =
index === -1
? [historyObj].concat(history)
@ -84,7 +88,7 @@ reducers[ACTIONS.SET_CONTENT_LAST_VIEWED] = (state, action) => {
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_URI] = (state, action) => {
const { uri } = action.data;
const { history } = state;
const index = history.findIndex(i => i.uri === uri);
const index = history.findIndex((i) => i.uri === uri);
return index === -1
? 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) => {
// return {

View file

@ -13,6 +13,7 @@ import {
makeSelectFileNameForUri,
normalizeURI,
selectMyActiveClaims,
selectClaimIdsByUri,
} from 'lbry-redux';
import { makeSelectRecommendedContentForUri } from 'redux/selectors/search';
import { selectMutedChannels } from 'redux/selectors/blocked';
@ -247,19 +248,37 @@ export const makeSelectInsufficientCreditsForUri = (uri: string) =>
);
export const makeSelectSigningIsMine = (rawUri: string) => {
let uri;
let uri;
try {
uri = normalizeURI(rawUri);
} catch (e) {}
return createSelector(selectClaimsByUri, selectMyActiveClaims, (claims, myClaims) => {
try {
uri = normalizeURI(rawUri);
} catch (e) { }
parseURI(uri);
} catch (e) {
return false;
}
const signingChannel = claims && claims[uri] && (claims[uri].signing_channel || claims[uri]);
return createSelector(selectClaimsByUri, selectMyActiveClaims, (claims, myClaims) => {
try {
parseURI(uri);
} catch (e) {
return false;
}
const signingChannel = claims && claims[uri] && (claims[uri].signing_channel || claims[uri]);
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]);

View file

@ -391,6 +391,11 @@
align-items: center;
}
.card__bottom-actions--comments {
@extend .card__bottom-actions;
margin-top: var(--spacing-s);
}
.card__multi-pane {
display: flex;

View file

@ -165,7 +165,7 @@ $actions-z-index: 2;
}
.channel__title {
display: inline;
display: flex;
margin-right: var(--spacing-s);
overflow: hidden;
text-overflow: ellipsis;

View file

@ -7,6 +7,14 @@ $thumbnailWidthSmall: 1rem;
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 {
list-style-type: none;
margin-left: var(--spacing-s);

View file

@ -60,3 +60,8 @@
font-size: var(--font-large);
}
}
.embed__overlay-logo {
max-height: 3.5rem;
max-width: 12rem;
}

View file

@ -178,6 +178,7 @@ const sharedStateFilters = {
app_welcome_version: { source: 'app', property: 'welcomeVersion' },
sharing_3P: { source: 'app', property: 'allowAnalytics' },
builtinCollections: { source: 'collections', property: 'builtin' },
editedCollections: { source: 'collections', property: 'edited' },
// savedCollections: { source: 'collections', property: 'saved' },
unpublishedCollections: { source: 'collections', property: 'unpublished' },
};

View file

@ -286,7 +286,7 @@ export function GetLinksData(
? `>${Math.floor(moment().subtract(9, 'months').startOf('week').unix())}`
: `>${Math.floor(moment().subtract(1, 'year').startOf('week').unix())}`,
pageSize: getPageSize(subscribedChannels.length > 3 ? (subscribedChannels.length > 6 ? 16 : 8) : 4),
streamTypes: CS.FILE_TYPES,
streamTypes: null,
channelIds: subscribedChannels.map((subscription: Subscription) => {
const { channelClaimId } = parseURI(subscription.uri);
return channelClaimId;

View file

@ -38,7 +38,7 @@ function FileViewerEmbeddedEnded(props: Props) {
<div className="file-viewer__overlay">
<div className="file-viewer__overlay-secondary">
<Button className="file-viewer__overlay-logo" href={URL}>
<Logo type={'embed'} />
<Logo type={'embed-ended'} />
</Button>
</div>
<div className="file-viewer__overlay-title">{prompt}</div>

View file

@ -23,13 +23,17 @@ const { getJsBundleId } = require('../bundle-id.js');
const jsBundleId = getJsBundleId();
function insertToHead(fullHtml, htmlToInsert) {
return fullHtml.replace(
/<!-- VARIABLE_HEAD_BEGIN -->.*<!-- VARIABLE_HEAD_END -->/s,
`
${htmlToInsert || buildOgMetadata()}
<script src="/public/ui-${jsBundleId}.js" async></script>
`
);
const beginStr = '<!-- VARIABLE_HEAD_BEGIN -->';
const finalStr = '<!-- VARIABLE_HEAD_END -->';
const beginIndex = fullHtml.indexOf(beginStr);
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) {

View file

@ -10136,9 +10136,9 @@ lazy-val@^1.0.4:
yargs "^13.2.2"
zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#a327385cdf71568dbd15a17f3dcf5f4b83e0966d:
lbry-redux@lbryio/lbry-redux#7cc9923ed9ff1940b508842af6be44c8da906a60:
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:
proxy-polyfill "0.1.6"
reselect "^3.0.0"