Merge branch 'master' into accessibility

This commit is contained in:
Baltazar Gomez 2021-07-28 23:47:42 -05:00 committed by GitHub
commit 74087d2b86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 604 additions and 350 deletions

View file

@ -46,6 +46,7 @@ YRBL_SAD_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649
#LOGO_TEXT_LIGHT=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
#LOGO_TEXT_DARK=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
#AVATAR_DEFAULT=
#FAVICON=
# LOCALE
DEFAULT_LANGUAGE=en
@ -77,6 +78,8 @@ CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS=4
CHANNEL_STAKED_LEVEL_LIVESTREAM=5
WEB_PUBLISH_SIZE_LIMIT_GB=4
LOADING_BAR_COLOR=#2bbb90
LIGHTHOUSE_DEFAULT_TYPES=audio,video,text,image,application
SHOW_ADS=true
## SIMPLE_SITE REPLACEMENTS

View file

@ -25,9 +25,10 @@ const config = {
// LOGO
LOGO_TITLE: process.env.LOGO_TITLE,
FAVICON: process.env.FAVICON,
LOGO_URL: process.env.LOGO_URL,
LOGO_TEXT_LIGHT_URL: process.env.LOGO_TEXT_LIGHT_URL,
LOGO_TEXT_DARK_URL: process.env.LOGO_TEXT_DARK_URL,
LOGO: process.env.LOGO,
LOGO_TEXT_LIGHT: process.env.LOGO_TEXT_LIGHT,
LOGO_TEXT_DARK: process.env.LOGO_TEXT_DARK,
AVATAR_DEFAULT: process.env.AVATAR_DEFAULT,
// OG
OG_TITLE_SUFFIX: process.env.OG_TITLE_SUFFIX,
OG_HOMEPAGE_TITLE: process.env.OG_HOMEPAGE_TITLE,
@ -40,6 +41,7 @@ const config = {
DEFAULT_LANGUAGE: process.env.DEFAULT_LANGUAGE,
AUTO_FOLLOW_CHANNELS: process.env.AUTO_FOLLOW_CHANNELS,
UNSYNCED_SETTINGS: process.env.UNSYNCED_SETTINGS,
AVATAR_DEFAULT: process.env.AVATAR_DEFAULT,
// ENABLE FEATURES
ENABLE_COMMENT_REACTIONS: process.env.ENABLE_COMMENT_REACTIONS === 'true',
@ -63,6 +65,7 @@ const config = {
ENABLE_MATURE: process.env.ENABLE_MATURE === 'true',
CUSTOM_HOMEPAGE: process.env.CUSTOM_HOMEPAGE === 'true',
SHOW_TAGS_INTRO: process.env.SHOW_TAGS_INTRO === 'true',
LIGHTHOUSE_DEFAULT_TYPES: process.env.LIGHTHOUSE_DEFAULT_TYPES,
};
config.URL_LOCAL = `http://localhost:${config.WEB_SERVER_PORT}`;

View file

@ -17,7 +17,7 @@ declare type RowDataItem = {
help?: any,
icon?: string,
extra?: any,
pinUrls?: Array<string>,
pinnedUrls?: Array<string>,
options?: {
channelIds?: Array<string>,
limitClaimsPerChannel?: number,

4
homepages/meme/index.js Normal file
View file

@ -0,0 +1,4 @@
module.exports = {
text: `This is LBRY`,
url: 'https://odysee.com/@Odysee:8?view=discussion',
};

View file

@ -375,6 +375,10 @@
"Got it": "Got it",
"View your channels": "View your channels",
"Unfollow": "Unfollow",
"Follow @%channelName%": "Follow @%channelName%",
"Unfollow @%channelName%": "Unfollow @%channelName%",
"Item removed from %name%": "Item removed from %name%",
"Item added to %name%": "Item added to %name%",
"These LBRY Credits remain yours. It is a deposit to reserve the name and can be undone at any time.": "These LBRY Credits remain yours. It is a deposit to reserve the name and can be undone at any time.",
"Create channel": "Create channel",
"Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.": "Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.",
@ -390,7 +394,6 @@
"Share on Telegram": "Share on Telegram",
"Share via...": "Share via...",
"View on lbry.tv": "View on lbry.tv",
"You will receive an SMS text message confirming your phone number is valid. Does not work for Canada and possibly other regions.": "You will receive an SMS text message confirming your phone number is valid. Does not work for Canada and possibly other regions.",
"Standard messaging rates apply. LBRY will not text or call you otherwise. Having trouble?": "Standard messaging rates apply. LBRY will not text or call you otherwise. Having trouble?",
"You currently have the highest bid for this name.": "You currently have the highest bid for this name.",
"You can generate a new address at any time, and any previous addresses will continue to work.": "You can generate a new address at any time, and any previous addresses will continue to work.",
@ -623,7 +626,7 @@
"If Sync is on, LBRY will backup your wallet and preferences. If disabled, you are responsible for keeping a backup.": "If Sync is on, LBRY will backup your wallet and preferences. If disabled, you are responsible for keeping a backup.",
"Your update is now pending on LBRY. It will take a few minutes to appear for other users.": "Your update is now pending on LBRY. It will take a few minutes to appear for other users.",
"Your livestream is now pending. You will be able to start shortly at the streaming dashboard.": "Your livestream is now pending. You will be able to start shortly at the streaming dashboard.",
"Your file is now pending on LBRY. It will take a few minutes to appear for other users.": "Your file is now pending on LBRY. It will take a few minutes to appear for other users.",
"Your content will be live shortly.": "Your content will be live shortly.",
"Your video will appear on Odysee shortly.": "Your video will appear on Odysee shortly.",
"Upload will continue in the background, please do not shut down immediately. Leaving the app running helps the network, thank you!": "Upload will continue in the background, please do not shut down immediately. Leaving the app running helps the network, thank you!",
"No results for %query%": "No results for %query%",
@ -740,7 +743,6 @@
"Livestream Created": "Livestream Created",
"Delete this file from my computer": "Delete this file from my computer",
"Are you sure you'd like to remove %title%?": "Are you sure you'd like to remove %title%?",
"reclaim %amount%": "reclaim %amount%",
"Remove from blockchain (%lbc%)": "Remove from blockchain (%lbc%)",
"Abandon on blockchain (reclaim %lbc%)": "Abandon on blockchain (reclaim %lbc%)",
"This will increase the overall bid amount for this content, which will boost its ability to be discovered while active.": "This will increase the overall bid amount for this content, which will boost its ability to be discovered while active.",
@ -859,7 +861,7 @@
"This will permanently remove your channel. Content published under this channel will be orphaned.": "This will permanently remove your channel. Content published under this channel will be orphaned.",
"Are you sure you'd like to remove \"%title%\"?": "Are you sure you'd like to remove \"%title%\"?",
"You are signed into lbry.tv which automatically shares data with LBRY inc. %signout_button%.": "You are signed into lbry.tv which automatically shares data with LBRY inc. %signout_button%.",
"LBRY works better if you find and follow a couple creators you like. You can also block channels you never want to see.": "LBRY works better if you find and follow a couple creators you like. You can also block channels you never want to see.",
"%SITE_NAME% works better if you find and follow a couple creators you like. You can also block channels you never want to see.": "%SITE_NAME% works better if you find and follow a couple creators you like. You can also block channels you never want to see.",
"Nice! You are currently following %followingCount% creator": "Nice! You are currently following %followingCount% creator",
"Nice! You are currently following %followingCount% creators": "Nice! You are currently following %followingCount% creators",
"Get Validated": "Get Validated",
@ -997,7 +999,7 @@
"You sent %amount% LBRY Credits as a tip, Mahalo!": "You sent %amount% LBRY Credits as a tip, Mahalo!",
"You sent %amount% LBRY Credits": "You sent %amount% LBRY Credits",
"No stats found": "No stats found",
"Sorry about that. Try refreshing or something else.": "Sorry about that. Try refreshing or something else.",
"Sorry about that. Contact %SITE_HELP_EMAIL% if you continue to have issues.": "Sorry about that. Contact %SITE_HELP_EMAIL% if you continue to have issues.",
"You are not able to see this channel's stats. Make sure you are signed in with the correct email and have data sharing turned on.": "You are not able to see this channel's stats. Make sure you are signed in with the correct email and have data sharing turned on.",
"%follower_count% followers": "%follower_count% followers",
"Sign Up": "Sign Up",
@ -1414,15 +1416,13 @@
"Confirm Edit": "Confirm Edit",
"Create Livestream": "Create Livestream",
"File": "File",
"Video/audio file": "Video/audio file",
"Transcode": "Transcode",
"Estimated transaction fee:": "Estimated transaction fee:",
"Est. transaction fee:": "Est. transaction fee:",
"Skip preview and confirmation": "Skip preview and confirmation",
"Upload settings": "Upload settings",
"Currently uploading": "Currently uploading",
"Your files are currently uploading.": "Your files are currently uploading.",
"Your file is currently uploading.": "Your file is currently uploading.",
"Currently Uploading": "Currently Uploading",
"Leave the app running until upload is complete": "Leave the app running until upload is complete",
"Enable Sync": "Enable Sync",
"Disable Sync": "Disable Sync",
"Getting your profiles...": "Getting your profiles...",
@ -1453,6 +1453,7 @@
"Unable to comment. This channel has blocked you.": "Unable to comment. This channel has blocked you.",
"Unable to comment. Your channel has been blocked by an admin.": "Unable to comment. Your channel has been blocked by an admin.",
"Unable to comment. The content owner has disabled comments.": "Unable to comment. The content owner has disabled comments.",
"Please do not spam.": "Please do not spam.",
"Slow mode is on. Please wait up to %value% seconds before commenting again.": "Slow mode is on. Please wait up to %value% seconds before commenting again.",
"The comment contains contents that are blocked by %author%": "The comment contains contents that are blocked by %author%",
"Your channel is still being setup, try again in a few moments.": "Your channel is still being setup, try again in a few moments.",
@ -1495,7 +1496,7 @@
"Earn a random reward of at least %lbc% for watching anything at all.": "Earn a random reward of at least %lbc% for watching anything at all.",
"You have already claimed this reward within the last 24 hours.": "You have already claimed this reward within the last 24 hours.",
"Please watch some content to earn this reward.": "Please watch some content to earn this reward.",
"You will receive an SMS text message confirming your phone number is valid.": "You will receive an SMS text message confirming your phone number is valid.",
"You will receive an SMS text message confirming your phone number is valid. May not be available in all regions.": "You will receive an SMS text message confirming your phone number is valid. May not be available in all regions.",
"Verified accounts are eligible to earn LBRY Credits for views, watching and reposting videos, sharing invite links etc. Verifying also helps us keep the %SITE_NAME% community safe too! %Refresh% or %Skip%.": "Verified accounts are eligible to earn LBRY Credits for views, watching and reposting videos, sharing invite links etc. Verifying also helps us keep the %SITE_NAME% community safe too! %Refresh% or %Skip%.",
"Your First Nickel": "Your First Nickel",
"Your First Credit": "Your First Credit",
@ -1735,11 +1736,14 @@
"Moderator Block": "Moderator Block",
"Block this channel on behalf of %creator%": "Block this channel on behalf of %creator%",
"creator": "creator",
"Create a channel to change this setting.": "Create a channel to change this setting.",
"Invalid channel URL \"%url%\"": "Invalid channel URL \"%url%\"",
"Delegation": "Delegation",
"Add moderator": "Add moderator",
"Add moderators": "Add moderators",
"Add moderator channel URL (e.g. \"@lbry#3f\")": "Add moderator channel URL (e.g. \"@lbry#3f\")",
"Enter a @username or URL": "Enter a @username or URL",
"examples: @channel, @channel#3, https://odysee.com/@Odysee:8, lbry://@Odysee#8": "examples: @channel, @channel#3, https://odysee.com/@Odysee:8, lbry://@Odysee#8",
"Moderators": "Moderators",
"Add as moderator": "Add as moderator",
"Mute (m)": "Mute (m)",
"Playback Rate (<, >)": "Playback Rate (<, >)",
"Fullscreen (f)": "Fullscreen (f)",
@ -1763,9 +1767,10 @@
"Stay up to date on the latest content from your favorite creators.": "Stay up to date on the latest content from your favorite creators.",
"Receive tutorial emails related to LBRY ": "Receive tutorial emails related to LBRY ",
"Create A LiveStream": "Create A LiveStream",
"This channel": "This channel",
"%channel% has disabled chat for this stream. Enjoy the stream!": "%channel% has disabled chat for this stream. Enjoy the stream!",
"This channel has disabled chat for this stream. Enjoy the stream!": "This channel has disabled chat for this stream. Enjoy the stream!",
"%channel% isn't live right now, but the chat is! Check back later to watch the stream.": "%channel% isn't live right now, but the chat is! Check back later to watch the stream.",
"This channel isn't live right now, but the chat is! Check back later to watch the stream.": "This channel isn't live right now, but the chat is! Check back later to watch the stream.",
"Right now": "Right now",
"%viewer_count% currently watching": "%viewer_count% currently watching",
"Slowmode is on. You can comment again in %time% seconds.": "Slowmode is on. You can comment again in %time% seconds.",
@ -1781,6 +1786,9 @@
"Nothing here yet. %check_again%": "Nothing here yet. %check_again%",
"Check again": "Check again",
"Check again...": "Check again...",
"Only visible to you": "Only visible to you",
"People who view this link will be redirected to your livestream. Make sure to use this for sharing so your title and thumbnail are displayed properly.": "People who view this link will be redirected to your livestream. Make sure to use this for sharing so your title and thumbnail are displayed properly.",
"View livestream": "View livestream",
"You need to upload your livestream details before you can go live. If you already created one in this channel, it should appear soon.": "You need to upload your livestream details before you can go live. If you already created one in this channel, it should appear soon.",
"Create A Livestream": "Create A Livestream",
"Create a Livestream by first submitting your livestream details and waiting for approval confirmation. This can be done well in advance and will take a few minutes.": "Create a Livestream by first submitting your livestream details and waiting for approval confirmation. This can be done well in advance and will take a few minutes.",
@ -1803,7 +1811,7 @@
"Update your livestream": "Update your livestream",
"Prepare an upcoming livestream": "Prepare an upcoming livestream",
"Edit your post": "Edit your post",
"Update your video": "Update your video",
"Update your content": "Update your content",
"Go Live on Odysee": "Go Live on Odysee",
"You're invited to try out our new livestreaming service while in beta!": "You're invited to try out our new livestreaming service while in beta!",
"lbry.tv is being retired in favor of %odysee% and new sign ups are disabled. Sign up on %odysee% instead": "lbry.tv is being retired in favor of %odysee% and new sign ups are disabled. Sign up on %odysee% instead",
@ -1951,7 +1959,6 @@
"Failed to process fetched data.": "Failed to process fetched data.",
"More from %claim_name%": "More from %claim_name%",
"Update your video/audio": "Update your video/audio",
"Upload that unlabeled video you found behind the TV in 1991": "Upload that unlabeled video you found behind the TV in 1991",
"Upload that unlabeled video or cassette you found behind the TV in 1991": "Upload that unlabeled video or cassette you found behind the TV in 1991",
"Select Replay": "Select Replay",
"Craft an epic post clearly explaining... whatever.": "Craft an epic post clearly explaining... whatever.",
@ -2049,7 +2056,6 @@
"Tip Creators": "Tip Creators",
"Only select creators can receive tips at this time": "Only select creators can receive tips at this time",
"The payment will be made from your saved card": "The payment will be made from your saved card",
"A channel is required to comment on lbry.tv": "A channel is required to comment on lbry.tv",
"Commenting...": "Commenting...",
"Show %count% replies": "Show %count% replies",
"Show reply": "Show reply",

View file

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<link rel="icon" type="image/png" href="/public/favicon.png" />
<link rel="preload" href="/public/font/v1/300.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/public/font/v1/300i.woff" as="font" type="font/woff" crossorigin />

View file

@ -12,7 +12,7 @@ import { generateInitialUrl } from 'util/url';
import { MATOMO_ID, MATOMO_URL, LBRY_WEB_BUFFER_API } from 'config';
const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL;
const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev');
export const SHARE_INTERNAL = 'shareInternal';
const SHARE_THIRD_PARTY = 'shareThirdParty';

View file

@ -1,5 +1,5 @@
// @flow
import { SHOW_ADS, ENABLE_NO_SOURCE_CLAIMS, SIMPLE_SITE } from 'config';
import { SHOW_ADS, SIMPLE_SITE } from 'config';
import * as CS from 'constants/claim_search';
import * as ICONS from 'constants/icons';
import React, { Fragment } from 'react';
@ -136,7 +136,7 @@ function ChannelContent(props: Props) {
{!channelIsMine && claimsInChannel > 0 && <HiddenNsfwClaims uri={uri} />}
<ClaimListDiscover
showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS}
hasSource
defaultFreshness={CS.FRESH_ALL}
showHiddenByUser={viewHiddenChannels}
forceShowReposts

View file

@ -6,6 +6,7 @@ import Gerbil from './gerbil.png';
import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper';
import ChannelStakedIndicator from 'component/channelStakedIndicator';
import OptimizedImage from 'component/optimizedImage';
import { AVATAR_DEFAULT } from 'config';
type Props = {
thumbnail: ?string,
@ -48,6 +49,7 @@ function ChannelThumbnail(props: Props) {
const channelThumbnail = thumbnail || thumbnailPreview;
const isGif = channelThumbnail && channelThumbnail.endsWith('gif');
const showThumb = (!obscure && !!thumbnail) || thumbnailPreview;
const defaultAvater = AVATAR_DEFAULT || Gerbil;
// Generate a random color class based on the first letter of the channel name
const { channelName } = parseURI(uri);
@ -86,7 +88,7 @@ function ChannelThumbnail(props: Props) {
<OptimizedImage
alt={__('Channel profile picture')}
className="channel-thumbnail__default"
src={!thumbError && channelThumbnail ? channelThumbnail : Gerbil}
src={!thumbError && channelThumbnail ? channelThumbnail : defaultAvater}
loading={noLazyLoad ? undefined : 'lazy'}
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
/>
@ -99,7 +101,7 @@ function ChannelThumbnail(props: Props) {
<OptimizedImage
alt={__('Channel profile picture')}
className="channel-thumbnail__custom"
src={!thumbError && channelThumbnail ? channelThumbnail : Gerbil}
src={!thumbError && channelThumbnail ? channelThumbnail : defaultAvater}
loading={noLazyLoad ? undefined : 'lazy'}
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
/>

View file

@ -98,10 +98,13 @@ function ClaimMenuList(props: Props) {
const isChannel = !incognitoClaim && !contentSigningChannel;
const { channelName } = parseURI(contentChannelUri);
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0));
const subscriptionLabel = __('%action%' + '%user%', {
action: isSubscribed ? __('Unfollow') : __('Follow'),
user: repostedClaim ? __(' @' + channelName) : '',
});
const subscriptionLabel = repostedClaim
? isSubscribed
? __('Unfollow @%channelName%', { channelName })
: __('Follow @%channelName%', { channelName })
: isSubscribed
? __('Unfollow')
: __('Follow');
const lastCollectionName = 'Favorites';
const lastCollectionId = COLLECTIONS_CONSTS.FAVORITES_ID;

View file

@ -14,6 +14,7 @@ function ClaimPreviewLoading(props: Props) {
className={classnames('claim-preview__wrapper', {
'claim-preview__wrapper--channel': isChannel && type !== 'inline',
'claim-preview__wrapper--inline': type === 'inline',
'claim-preview__wrapper--small': type === 'small',
})}
>
<div className={classnames('claim-preview', { 'claim-preview--large': type === 'large' })}>

View file

@ -114,7 +114,6 @@ type Props = {
renderProperties?: (Claim) => ?Node,
liveLivestreamsFirst?: boolean,
livestreamMap?: { [string]: any },
pin?: boolean,
pinUrls?: Array<string>,
showNoSourceClaims?: boolean,
};
@ -147,7 +146,6 @@ function ClaimTilesDiscover(props: Props) {
mutedUris,
liveLivestreamsFirst,
livestreamMap,
pin,
pinUrls,
prefixUris,
showNoSourceClaims,
@ -291,9 +289,9 @@ function ClaimTilesDiscover(props: Props) {
};
const modifiedUris = uris ? uris.slice() : [];
const fixUris = pinUrls || ['lbry://@AlisonMorrow#6/LBRY#8'];
const fixUris = pinUrls || [];
if (pin && modifiedUris && modifiedUris.length > 2 && window.location.pathname === '/') {
if (pinUrls && modifiedUris && modifiedUris.length > 2 && window.location.pathname === '/') {
fixUris.forEach((fixUri) => {
if (modifiedUris.indexOf(fixUri) !== -1) {
modifiedUris.splice(modifiedUris.indexOf(fixUri), 1);
@ -308,7 +306,13 @@ function ClaimTilesDiscover(props: Props) {
<ul className="claim-grid">
{modifiedUris && modifiedUris.length
? modifiedUris.map((uri, index) => (
<ClaimPreviewTile key={uri} uri={uri} properties={renderProperties} live={resolveLive(index)} />
<ClaimPreviewTile
showNoSourceClaims={hasNoSource || showNoSourceClaims}
key={uri}
uri={uri}
properties={renderProperties}
live={resolveLive(index)}
/>
))
: new Array(pageSize)
.fill(1)

View file

@ -42,7 +42,9 @@ type Props = {
isReply: boolean,
activeChannel: string,
activeChannelClaim: ?ChannelClaim,
bottom: boolean,
livestream?: boolean,
embed?: boolean,
toast: (string) => void,
claimIsMine: boolean,
sendTip: ({}, (any) => void, (any) => void) => void,
@ -63,7 +65,9 @@ export function CommentCreate(props: Props) {
isReply,
parentId,
activeChannelClaim,
bottom,
livestream,
embed,
claimIsMine,
sendTip,
doToast,
@ -106,7 +110,7 @@ export function CommentCreate(props: Props) {
function altEnterListener(e: SyntheticKeyboardEvent<*>) {
const KEYCODE_ENTER = 13;
if ((e.ctrlKey || e.metaKey) && e.keyCode === KEYCODE_ENTER) {
if ((livestream || e.ctrlKey || e.metaKey) && e.keyCode === KEYCODE_ENTER) {
e.preventDefault();
buttonref.current.click();
}
@ -147,8 +151,6 @@ export function CommentCreate(props: Props) {
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id;
console.log(activeChannelClaim);
setIsSubmitting(true);
if (activeTab === TAB_LBC) {
@ -280,6 +282,11 @@ export function CommentCreate(props: Props) {
<div
role="button"
onClick={() => {
if (embed) {
window.open(`https://odysee.com/$/${PAGES.AUTH}?redirect=/$/${PAGES.LIVESTREAM}`);
return;
}
const pathPlusRedirect = `/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`;
if (livestream) {
window.open(pathPlusRedirect);
@ -344,6 +351,7 @@ export function CommentCreate(props: Props) {
className={classnames('comment__create', {
'comment__create--reply': isReply,
'comment__create--nested-reply': isNested,
'comment__create--bottom': bottom,
})}
>
<FormField

View file

@ -2323,4 +2323,12 @@ export const icons = {
<path d="M12.729 1.2l3.346 6.629 6.44.638a.805.805 0 01.5 1.374l-5.3 5.253 1.965 7.138a.813.813 0 01-1.151.935L12 19.934l-6.52 3.229a.813.813 0 01-1.151-.935l1.965-7.138L.99 9.837a.805.805 0 01.5-1.374l6.44-.638L11.271 1.2a.819.819 0 011.458 0z" />
</g>
),
[ICONS.MUSIC]: buildIcon(
<g>
<path d="M19.8 6.267a1 1 0 01-1.414 0l-1.411-1.414a1 1 0 010-1.415l.186-.186a1 1 0 01.391-.242l4.536-1.51a.927.927 0 01.949 1.535z" />
<path d="M16.975 4.853L9.55 12.277l1.414 1.414 7.425-7.424" />
<path d="M11.187 10.64a2.881 2.881 0 01-.8-2.538 6.278 6.278 0 01.738-1.99A1.15 1.15 0 009.3 4.749a6.56 6.56 0 00-1.91 3.406c-.22 1.038-1 2.463-2.1 2.485a4.638 4.638 0 00-4.6 4.746 5.927 5.927 0 001.812 4.249l1.1 1.1a5.93 5.93 0 004.249 1.812 4.639 4.639 0 004.746-4.6c0-1.1 1.235-1.789 2.286-1.755a4.13 4.13 0 003.324-1.269 1.1 1.1 0 00-.719-1.846c-3.306-.254-4-.141-4.891-1.029M7.782 13.338l2.122 2.121" />
<path d="M4.954 14.753l3.535 3.535-1.768 1.768-3.535-3.535z" />
</g>
),
};

View file

@ -1,10 +1,12 @@
// @flow
import * as REACTION_TYPES from 'constants/reactions';
import * as ICONS from 'constants/icons';
import React from 'react';
import classnames from 'classnames';
import Button from 'component/button';
import { formatNumberWithCommas } from 'util/number';
import NudgeFloating from 'component/nudgeFloating';
import { SIMPLE_SITE } from 'config';
type Props = {
claim: StreamClaim,
@ -15,18 +17,51 @@ type Props = {
likeCount: number,
dislikeCount: number,
myReaction: ?string,
livestream?: boolean,
};
function FileReactions(props: Props) {
const { claim, uri, doFetchReactions, doReactionLike, doReactionDislike, likeCount, dislikeCount } = props;
const {
claim,
uri,
doFetchReactions,
doReactionLike,
doReactionDislike,
myReaction,
likeCount,
dislikeCount,
livestream,
} = props;
const claimId = claim && claim.claim_id;
const channel = claim && claim.signing_channel && claim.signing_channel.name;
const isCollection = claim && claim.value_type === 'collection'; // hack because nudge gets cut off by card on cols.
const likeIcon = SIMPLE_SITE ? (myReaction === REACTION_TYPES.LIKE ? ICONS.FIRE_ACTIVE : ICONS.FIRE) : ICONS.UPVOTE;
const dislikeIcon = SIMPLE_SITE
? myReaction === REACTION_TYPES.DISLIKE
? ICONS.SLIME_ACTIVE
: ICONS.SLIME
: ICONS.DOWNVOTE;
React.useEffect(() => {
if (claimId) {
function fetchReactions() {
doFetchReactions(claimId);
}
}, [claimId, doFetchReactions]);
let fetchInterval;
if (claimId) {
fetchReactions();
if (livestream) {
fetchInterval = setInterval(fetchReactions, 45000);
}
}
return () => {
if (fetchInterval) {
clearInterval(fetchInterval);
}
};
}, [claimId, doFetchReactions, livestream]);
return (
<>
@ -41,20 +76,46 @@ function FileReactions(props: Props) {
title={__('I like this')}
requiresAuth={IS_WEB}
authSrc="filereaction_like"
className={classnames('button--file-action')}
label={formatNumberWithCommas(likeCount, 0)}
className={classnames('button--file-action', { 'button--fire': myReaction === REACTION_TYPES.LIKE })}
label={
<>
{myReaction === REACTION_TYPES.LIKE && SIMPLE_SITE && (
<>
<div className="button__fire-glow" />
<div className="button__fire-particle1" />
<div className="button__fire-particle2" />
<div className="button__fire-particle3" />
<div className="button__fire-particle4" />
<div className="button__fire-particle5" />
<div className="button__fire-particle6" />
</>
)}
{formatNumberWithCommas(likeCount, 0)}
</>
}
iconSize={18}
icon={ICONS.UPVOTE}
icon={likeIcon}
onClick={() => doReactionLike(uri)}
/>
<Button
requiresAuth={IS_WEB}
authSrc={'filereaction_dislike'}
title={__('I dislike this')}
className={classnames('button--file-action')}
label={formatNumberWithCommas(dislikeCount, 0)}
className={classnames('button--file-action', { 'button--slime': myReaction === REACTION_TYPES.DISLIKE })}
label={
<>
{myReaction === REACTION_TYPES.DISLIKE && SIMPLE_SITE && (
<>
<div className="button__slime-stain" />
<div className="button__slime-drop1" />
<div className="button__slime-drop2" />
</>
)}
{formatNumberWithCommas(dislikeCount, 0)}
</>
}
iconSize={18}
icon={ICONS.DOWNVOTE}
icon={dislikeIcon}
onClick={() => doReactionDislike(uri)}
/>
</>

View file

@ -22,7 +22,7 @@ function FileSubtitle(props: Props) {
<FileViewCount uri={uri} livestream={livestream} activeViewers={activeViewers} isLive={isLive} />
</div>
<FileActions uri={uri} />
<FileActions uri={uri} hideRepost={livestream} livestream={livestream} />
</div>
);
}

View file

@ -5,7 +5,7 @@ import FilePrice from 'component/filePrice';
import { formatLbryUrlForWeb } from 'util/url';
import { withRouter } from 'react-router';
import { URL } from 'config';
import * as ICONS from 'constants/icons';
import Logo from 'component/logo';
type Props = {
uri: string,
@ -36,7 +36,9 @@ function FileViewerEmbeddedTitle(props: Props) {
{...contentLinkProps}
/>
<div className="file-viewer__embedded-info">
<Button className="file-viewer__overlay-logo" icon={ICONS.LBRY} aria-label={__('Home')} {...lbryLinkProps} />
<Button className="file-viewer__overlay-logo" aria-label={__('Home')} {...lbryLinkProps}>
<Logo type={'embed'} />
</Button>
{isInApp && <FilePrice uri={uri} />}
</div>
</div>

View file

@ -1,5 +1,5 @@
// @flow
import { LOGO_TITLE, ENABLE_NO_SOURCE_CLAIMS, CHANNEL_STAKED_LEVEL_LIVESTREAM, ENABLE_UI_NOTIFICATIONS } from 'config';
import { ENABLE_NO_SOURCE_CLAIMS, CHANNEL_STAKED_LEVEL_LIVESTREAM, ENABLE_UI_NOTIFICATIONS } from 'config';
import * as ICONS from 'constants/icons';
import { SETTINGS } from 'lbry-redux';
import * as PAGES from 'constants/pages';
@ -16,6 +16,7 @@ import NotificationBubble from 'component/notificationBubble';
import NotificationHeaderButton from 'component/notificationHeaderButton';
import ChannelThumbnail from 'component/channelThumbnail';
import SkipNavigationButton from 'component/skipNavigationButton';
import Logo from 'component/logo';
// @if TARGET='app'
import { remote } from 'electron';
import { IS_MAC } from 'component/app/view';
@ -254,15 +255,8 @@ const Header = (props: Props) => {
</span>
)}
<Button
className="header__navigation-item header__navigation-item--lbry"
// @if TARGET='app'
aria-label={__('Home')}
label={'LBRY'}
// @endif
// @if TARGET='web'
label={LOGO_TITLE} // eslint-disable-line
// @endif
icon={ICONS.LBRY}
className="header__navigation-item header__navigation-item--lbry"
onClick={() => {
if (history.location.pathname === '/') window.location.reload();
}}
@ -272,8 +266,9 @@ const Header = (props: Props) => {
}}
// @endif
{...homeButtonNavigationProps}
/>
>
<Logo />
</Button>
{!authHeader && (
<div className="header__center">
{/* @if TARGET='app' */}

View file

@ -1,5 +1,5 @@
// @flow
import { BITWAVE_EMBED_URL } from 'constants/livestream';
import { LIVESTREAM_EMBED_URL } from 'constants/livestream';
import React from 'react';
import FileTitleSection from 'component/fileTitleSection';
import LivestreamComments from 'component/livestreamComments';
@ -29,7 +29,7 @@ export default function LivestreamLayout(props: Props) {
<div className="file-render file-render--video livestream">
<div className="file-viewer">
<iframe
src={`${BITWAVE_EMBED_URL}/${channelClaimId}?skin=odysee&autoplay=1`}
src={`${LIVESTREAM_EMBED_URL}/${channelClaimId}?skin=odysee&autoplay=1`}
scrolling="no"
allowFullScreen
/>
@ -38,17 +38,19 @@ export default function LivestreamLayout(props: Props) {
{Boolean(chatDisabled) && (
<div className="help--notice">
{__('%channel% has disabled chat for this stream. Enjoy the stream!', {
channel: channelName || __('This channel'),
})}
{channelName
? __('%channelName% has disabled chat for this stream. Enjoy the stream!', { channelName })
: __('This channel has disabled chat for this stream. Enjoy the stream!')}
</div>
)}
{!isLive && (
<div className="help--notice">
{__("%channel% isn't live right now, but the chat is! Check back later to watch the stream.", {
channel: channelName || __('This channel'),
})}
{channelName
? __("%channelName% isn't live right now, but the chat is! Check back later to watch the stream.", {
channelName,
})
: __("This channel isn't live right now, but the chat is! Check back later to watch the stream.")}
</div>
)}

View file

@ -1,5 +1,5 @@
// @flow
import { BITWAVE_LIVE_API } from 'constants/livestream';
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
import * as CS from 'constants/claim_search';
import React from 'react';
import Card from 'component/common/card';
@ -39,8 +39,8 @@ export default function LivestreamLink(props: Props) {
React.useEffect(() => {
function fetchIsStreaming() {
// $FlowFixMe Bitwave's API can handle garbage
fetch(`${BITWAVE_LIVE_API}/${livestreamChannelId}`)
// $FlowFixMe livestream API can handle garbage
fetch(`${LIVESTREAM_LIVE_API}/${livestreamChannelId}`)
.then((res) => res.json())
.then((res) => {
if (res && res.success && res.data && res.data.live) {

View file

@ -1,6 +1,6 @@
// @flow
import * as ICONS from 'constants/icons';
import { BITWAVE_LIVE_API } from 'constants/livestream';
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
import React from 'react';
import Icon from 'component/common/icon';
import Spinner from 'component/spinner';
@ -14,7 +14,7 @@ export default function LivestreamList() {
React.useEffect(() => {
function checkCurrentLivestreams() {
fetch(BITWAVE_LIVE_API)
fetch(LIVESTREAM_LIVE_API)
.then((res) => res.json())
.then((res) => {
setLoading(false);

View file

@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import Logo from './view';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { SETTINGS } from 'lbry-redux';
const select = (state, props) => ({
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
});
export default connect(select)(Logo);

View file

@ -0,0 +1,51 @@
// @flow
import React from 'react';
import * as ICONS from 'constants/icons';
import { LOGO_TITLE, LOGO, LOGO_TEXT_LIGHT, LOGO_TEXT_DARK } from 'config';
import Icon from 'component/common/icon';
import { useIsMobile } from 'effects/use-screensize';
type Props = {
type: string,
currentTheme: string,
};
export default function Logo(props: Props) {
const { type, currentTheme } = props;
const isMobile = useIsMobile();
const defaultWithLabel = (
<>
<Icon icon={ICONS.LBRY} />
{/* @if TARGET='app' */}
<div className={'button__label'}>{'LBRY'}</div>
{/* @endif */}
{/* @if TARGET='web' */}
<div className={'button__label'}>{LOGO_TITLE}</div>
{/* @endif */}
</>
);
if (type === 'small' || (isMobile && type !== 'embed')) {
return LOGO ? <img src={LOGO} /> : <Icon icon={ICONS.LBRY} />;
} else if (type === 'embed') {
if (LOGO_TEXT_LIGHT) {
return (
<>
<img src={LOGO_TEXT_LIGHT} />
</>
);
} else {
return defaultWithLabel;
}
} else {
if (LOGO_TEXT_LIGHT && LOGO_TEXT_DARK) {
return (
<>
<img src={currentTheme === 'light' ? LOGO_TEXT_DARK : LOGO_TEXT_LIGHT} />
</>
);
} else {
return defaultWithLabel;
}
}
}

View file

@ -104,8 +104,10 @@ function PostViewer(props: Props) {
<ClaimAuthor uri={uri} />
<FileRenderInitiator uri={uri} />
<FileRenderInline uri={uri} />
<div className="file-render--post-container">
<FileRenderInitiator uri={uri} />
<FileRenderInline uri={uri} />
</div>
<FileActions uri={uri} />
</div>
);

View file

@ -28,7 +28,7 @@ import * as PUBLISH_MODES from 'constants/publish_types';
import { useHistory } from 'react-router';
import Spinner from 'component/spinner';
import { toHex } from 'util/hex';
import { BITWAVE_REPLAY_API } from 'constants/livestream';
import { LIVESTREAM_REPLAY_API } from 'constants/livestream';
// @if TARGET='app'
import fs from 'fs';
@ -182,9 +182,9 @@ function PublishForm(props: Props) {
}
} else {
if (editingURI) {
customSubtitle = __('Update your video');
customSubtitle = __('Update your content');
} else {
customSubtitle = __('Upload that unlabeled video you found behind the TV in 1991');
customSubtitle = __('Upload that unlabeled video or cassette you found behind the TV in 1991');
}
}
@ -274,7 +274,7 @@ function PublishForm(props: Props) {
// move this to lbryinc OR to a file under ui, and/or provide a standardized livestreaming config.
function fetchLivestreams(channelId, signature, timestamp) {
setCheckingLivestreams(true);
fetch(`${BITWAVE_REPLAY_API}/${channelId}?signature=${signature || ''}&signing_ts=${timestamp || ''}`) // claimChannelId
fetch(`${LIVESTREAM_REPLAY_API}/${channelId}?signature=${signature || ''}&signing_ts=${timestamp || ''}`) // claimChannelId
.then((res) => res.json())
.then((res) => {
if (!res || !res.data) {
@ -566,6 +566,7 @@ function PublishForm(props: Props) {
<Button
key={String(modeName)}
icon={modeName}
iconSize={18}
label={__(MODE_TO_I18N_STR[String(modeName)] || '---')}
button="alt"
onClick={() => {

View file

@ -6,12 +6,11 @@ import Nag from 'component/common/nag';
import { parseURI } from 'lbry-redux';
import Button from 'component/button';
import Card from 'component/common/card';
import { AUTO_FOLLOW_CHANNELS, CUSTOM_HOMEPAGE } from 'config';
import { AUTO_FOLLOW_CHANNELS, CUSTOM_HOMEPAGE, SIMPLE_SITE, SITE_NAME } from 'config';
type Props = {
subscribedChannels: Array<Subscription>,
onContinue: () => void,
onBack: () => void,
channelSubscribe: (sub: Subscription) => void,
homepageData: any,
prefsReady: boolean,
@ -22,7 +21,7 @@ const channelsToSubscribe = AUTO_FOLLOW_CHANNELS.trim()
.filter((x) => x !== '');
function UserChannelFollowIntro(props: Props) {
const { subscribedChannels, channelSubscribe, onContinue, onBack, homepageData, prefsReady } = props;
const { subscribedChannels, channelSubscribe, onContinue, homepageData, prefsReady } = props;
const { PRIMARY_CONTENT } = homepageData;
let channelIds;
if (PRIMARY_CONTENT && CUSTOM_HOMEPAGE) {
@ -49,25 +48,27 @@ function UserChannelFollowIntro(props: Props) {
<Card
title={__('Find channels to follow')}
subtitle={__(
'LBRY works better if you find and follow a couple creators you like. You can also block channels you never want to see.'
'%SITE_NAME% works better if you find and follow a couple creators you like. You can also block channels you never want to see.',
{ SITE_NAME }
)}
actions={
<React.Fragment>
<div className="section__actions--between">
<Button button="secondary" onClick={onBack} label={__('Back')} />
<Button
button={subscribedChannels.length < 1 ? 'alt' : 'primary'}
onClick={onContinue}
label={subscribedChannels.length < 1 ? __('Skip') : __('Continue')}
/>
</div>
<div className="section__body">
<ClaimListDiscover
defaultOrderBy={CS.ORDER_BY_TOP}
hideFilters={SIMPLE_SITE}
meta={
<Button
button={subscribedChannels.length < 1 ? 'alt' : 'primary'}
onClick={onContinue}
label={subscribedChannels.length < 1 ? __('Skip') : __('Continue')}
/>
}
defaultOrderBy={SIMPLE_SITE ? CS.ORDER_BY_TOP : CS.ORDER_BY_TRENDING}
defaultFreshness={CS.FRESH_ALL}
claimType="channel"
claimIds={CUSTOM_HOMEPAGE && channelIds ? channelIds : undefined}
defaultTags={followingCount > 3 ? CS.TAGS_FOLLOWED : undefined}
maxPages={SIMPLE_SITE ? 3 : undefined}
/>
{followingCount > 0 && (
<Nag

View file

@ -86,13 +86,12 @@ class DocumentViewer extends React.PureComponent<Props, State> {
}
render() {
const { error, loading, content } = this.state;
const { error, content } = this.state;
const isReady = content && !error;
const errorMessage = __("Sorry, looks like we can't load the document.");
return (
<div className="file-viewer file-viewer--document">
{loading && !error && <div className="placeholder--text-document" />}
{error && <LoadingScreen status={errorMessage} spinner={!error} />}
{isReady && this.renderDocument()}
</div>

View file

@ -497,7 +497,6 @@ export default React.memo<Props>(function VideoJs(props: Props) {
}
function detectFileType() {
console.log(`Detecting file type via pre-fetch...`);
return new Promise(async (res, rej) => {
try {
const response = await fetch(source, { method: 'HEAD', cache: 'no-store' });
@ -514,8 +513,6 @@ export default React.memo<Props>(function VideoJs(props: Props) {
finalSource = response.url;
}
console.log(`File type is: ${finalType}`);
// Modify video source in options
videoJsOptions.sources = [
{
@ -526,7 +523,6 @@ export default React.memo<Props>(function VideoJs(props: Props) {
return res(videoJsOptions);
} catch (error) {
console.error(`Failed to pre-fetch video!`);
return rej(error);
}
});

View file

@ -25,6 +25,8 @@ if (STRIPE_PUBLIC_KEY.indexOf('pk_live') > -1) {
}
const DEFAULT_TIP_AMOUNTS = [1, 5, 25, 100];
const MINIMUM_FIAT_TIP = 1;
const MAXIMUM_FIAT_TIP = 1000;
const TAB_BOOST = 'TabBoost';
const TAB_FIAT = 'TabFiat';
@ -186,8 +188,7 @@ function WalletSendTip(props: Props) {
React.useEffect(() => {
// Regex for number up to 8 decimal places
const regexp = RegExp(/^(\d*([.]\d{0,8})?)$/);
const validTipInput = regexp.test(String(tipAmount));
let regexp;
let tipError;
if (tipAmount === 0) {
@ -198,8 +199,13 @@ function WalletSendTip(props: Props) {
// if it's not fiat, aka it's boost or lbc tip
else if (activeTab !== TAB_FIAT) {
regexp = RegExp(/^(\d*([.]\d{0,8})?)$/);
const validTipInput = regexp.test(String(tipAmount));
if (!validTipInput) {
tipError = __('Amount must have no more than 8 decimal places');
} else if (!validTipInput) {
tipError = __('Amount must have no more than 8 decimal places');
} else if (tipAmount === balance) {
tipError = __('Please decrease the amount to account for transaction fees');
} else if (tipAmount > balance) {
@ -209,9 +215,14 @@ function WalletSendTip(props: Props) {
}
// if tip fiat tab
} else {
if (tipAmount < 1) {
regexp = RegExp(/^(\d*([.]\d{0,2})?)$/);
const validTipInput = regexp.test(String(tipAmount));
if (!validTipInput) {
tipError = __('Amount must have no more than 2 decimal places');
} else if (tipAmount < MINIMUM_FIAT_TIP) {
tipError = __('Amount must be at least one dollar');
} else if (tipAmount > 1000) {
} else if (tipAmount > MAXIMUM_FIAT_TIP) {
tipError = __('Amount cannot be over 1000 dollars');
}
}
@ -544,7 +555,7 @@ function WalletSendTip(props: Props) {
</React.Fragment>
}
className="form-field--price-amount"
error={tipError && activeTab !== TAB_FIAT}
error={tipError}
min="0"
step="any"
type="number"
@ -565,7 +576,7 @@ function WalletSendTip(props: Props) {
disabled={
fetchingChannels ||
isPending ||
(tipError && activeTab !== TAB_FIAT) ||
tipError ||
!tipAmount ||
(activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip))
}

View file

@ -34,7 +34,7 @@ type Props = {
uri: string,
onTipErrorChange: (string) => void,
activeTab: string,
shouldDisableReviewButton: (boolean) => void
shouldDisableReviewButton: (boolean) => void,
};
function WalletTipAmountSelector(props: Props) {
@ -46,7 +46,7 @@ function WalletTipAmountSelector(props: Props) {
const [hasCardSaved, setHasSavedCard] = usePersistedState('comment-support:hasCardSaved', false);
// if it's fiat but there's no card saved OR the creator can't receive fiat tips
const shouldDisableFiatSelectors = (activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip));
const shouldDisableFiatSelectors = activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip);
/**
* whether tip amount selection/review functionality should be disabled
@ -120,9 +120,8 @@ function WalletTipAmountSelector(props: Props) {
// setHasSavedCard(false);
// setCanReceiveFiatTip(true);
const regexp = RegExp(/^(\d*([.]\d{0,8})?)$/);
const validTipInput = regexp.test(String(amount));
let tipError = '';
let regexp,
tipError = '';
if (amount === 0) {
tipError = __('Amount must be a positive number');
@ -132,6 +131,9 @@ function WalletTipAmountSelector(props: Props) {
// if it's not fiat, aka it's boost or lbc tip
else if (activeTab !== TAB_FIAT) {
regexp = RegExp(/^(\d*([.]\d{0,8})?)$/);
const validTipInput = regexp.test(String(amount));
if (!validTipInput) {
tipError = __('Amount must have no more than 8 decimal places');
} else if (amount === balance) {
@ -143,7 +145,12 @@ function WalletTipAmountSelector(props: Props) {
}
// if tip fiat tab
} else {
if (amount < 1) {
regexp = RegExp(/^(\d*([.]\d{0,2})?)$/);
const validTipInput = regexp.test(String(amount));
if (!validTipInput) {
tipError = __('Amount must have no more than 2 decimal places');
} else if (amount < 1) {
tipError = __('Amount must be at least one dollar');
} else if (amount > 1000) {
tipError = __('Amount cannot be over 1000 dollars');
@ -154,8 +161,10 @@ function WalletTipAmountSelector(props: Props) {
onTipErrorChange(tipError);
}, [amount, balance, setTipError, activeTab]);
// parse number as float and sets it in the parent component
function handleCustomPriceChange(amount: number) {
const tipAmount = parseFloat(amount);
onChange(tipAmount);
}
@ -229,6 +238,7 @@ function WalletTipAmountSelector(props: Props) {
</>
)}
{/* custom number input form */}
{useCustomTip && (
<div className="comment__tip-input">
<FormField

View file

@ -164,3 +164,4 @@ export const TIME = 'time';
export const GLOBE = 'globe';
export const RSS = 'rss';
export const STAR = 'star';
export const MUSIC = 'MusicCategory';

View file

@ -1,3 +1,4 @@
export const BITWAVE_EMBED_URL = 'https://bitwave.tv/odysee';
export const BITWAVE_LIVE_API = 'https://api.bitwave.tv/v1/odysee/live';
export const BITWAVE_REPLAY_API = 'https://api.bitwave.tv/v1/replays/odysee';
export const LIVESTREAM_EMBED_URL = 'https://player.live.odysee.com/odysee';
export const LIVESTREAM_LIVE_API = 'https://api.live.odysee.com/v1/odysee/live';
export const LIVESTREAM_REPLAY_API = 'https://api.live.odysee.com/v1/replays/odysee';
export const LIVESTREAM_RTMP_URL = 'rtmp://stream.odysee.com/live';

View file

@ -1,6 +1,6 @@
// @flow
import React from 'react';
import { BITWAVE_LIVE_API } from 'constants/livestream';
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
/**
* Gets latest livestream info list. Returns null (instead of a blank object)
@ -16,7 +16,7 @@ export default function useGetLivestreams(minViewers: number = 0, refreshMs: num
React.useEffect(() => {
function checkCurrentLivestreams() {
fetch(BITWAVE_LIVE_API)
fetch(LIVESTREAM_LIVE_API)
.then((res) => res.json())
.then((res) => {
setLoading(false);

View file

@ -29,11 +29,15 @@ import TruncatedText from 'component/common/truncated-text';
import PlaceholderTx from 'static/img/placeholderTx.gif';
export const PAGE_VIEW_QUERY = `view`;
const CONTENT_PAGE = 'content';
const LISTS_PAGE = 'lists';
const ABOUT_PAGE = `about`;
export const DISCUSSION_PAGE = `discussion`;
const EDIT_PAGE = 'edit';
const PAGE = {
CONTENT: 'content',
LISTS: 'lists',
ABOUT: 'about',
DISCUSSION: DISCUSSION_PAGE,
EDIT: 'edit',
};
type Props = {
uri: string,
@ -84,9 +88,9 @@ function ChannelPage(props: Props) {
} = useHistory();
const [viewBlockedChannel, setViewBlockedChannel] = React.useState(false);
const urlParams = new URLSearchParams(search);
const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
const currentView = urlParams.get(PAGE_VIEW_QUERY) || PAGE.CONTENT;
const [discussionWasMounted, setDiscussionWasMounted] = React.useState(false);
const editing = urlParams.get(PAGE_VIEW_QUERY) === EDIT_PAGE;
const editing = urlParams.get(PAGE_VIEW_QUERY) === PAGE.EDIT;
const { channelName } = parseURI(uri);
const { permanent_url: permanentUrl } = claim;
const claimId = claim.claim_id;
@ -144,16 +148,16 @@ function ChannelPage(props: Props) {
// would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers.
let tabIndex;
switch (currentView) {
case CONTENT_PAGE:
case PAGE.CONTENT:
tabIndex = 0;
break;
case LISTS_PAGE:
case PAGE.LISTS:
tabIndex = 1;
break;
case ABOUT_PAGE:
case PAGE.ABOUT:
tabIndex = 2;
break;
case DISCUSSION_PAGE:
case PAGE.DISCUSSION:
tabIndex = 3;
break;
default:
@ -166,20 +170,20 @@ function ChannelPage(props: Props) {
let search = '?';
if (newTabIndex === 0) {
search += `${PAGE_VIEW_QUERY}=${CONTENT_PAGE}`;
search += `${PAGE_VIEW_QUERY}=${PAGE.CONTENT}`;
} else if (newTabIndex === 1) {
search += `${PAGE_VIEW_QUERY}=${LISTS_PAGE}`;
search += `${PAGE_VIEW_QUERY}=${PAGE.LISTS}`;
} else if (newTabIndex === 2) {
search += `${PAGE_VIEW_QUERY}=${ABOUT_PAGE}`;
search += `${PAGE_VIEW_QUERY}=${PAGE.ABOUT}`;
} else {
search += `${PAGE_VIEW_QUERY}=${DISCUSSION_PAGE}`;
search += `${PAGE_VIEW_QUERY}=${PAGE.DISCUSSION}`;
}
push(`${url}${search}`);
}
React.useEffect(() => {
if (currentView === DISCUSSION_PAGE) {
if (currentView === PAGE.DISCUSSION) {
setDiscussionWasMounted(true);
}
}, [currentView]);
@ -244,7 +248,7 @@ function ChannelPage(props: Props) {
<Button
button="alt"
title={__('Edit')}
onClick={() => push(`?${PAGE_VIEW_QUERY}=${EDIT_PAGE}`)}
onClick={() => push(`?${PAGE_VIEW_QUERY}=${PAGE.EDIT}`)}
icon={ICONS.EDIT}
iconSize={18}
disabled={pending}
@ -287,27 +291,31 @@ function ChannelPage(props: Props) {
</TabList>
<TabPanels>
<TabPanel>
<ChannelContent
uri={uri}
channelIsBlackListed={channelIsBlackListed}
viewHiddenChannels
empty={<section className="main--empty">{__('No Content Found')}</section>}
/>
{currentView === PAGE.CONTENT && (
<ChannelContent
uri={uri}
channelIsBlackListed={channelIsBlackListed}
viewHiddenChannels
empty={<section className="main--empty">{__('No Content Found')}</section>}
/>
)}
</TabPanel>
<TabPanel>
<ChannelContent
claimType={'collection'}
uri={uri}
channelIsBlackListed={channelIsBlackListed}
viewHiddenChannels
empty={collectionEmpty}
/>
{currentView === PAGE.LISTS && (
<ChannelContent
claimType={'collection'}
uri={uri}
channelIsBlackListed={channelIsBlackListed}
viewHiddenChannels
empty={collectionEmpty}
/>
)}
</TabPanel>
<TabPanel>
<ChannelAbout uri={uri} />
</TabPanel>
<TabPanel>
{(discussionWasMounted || currentView === DISCUSSION_PAGE) && <ChannelDiscussion uri={uri} />}
{(discussionWasMounted || currentView === PAGE.DISCUSSION) && <ChannelDiscussion uri={uri} />}
</TabPanel>
</TabPanels>
</Tabs>

View file

@ -127,6 +127,9 @@ function ChannelsFollowingDiscover(props: Props) {
claimType={CS.CLAIM_CHANNEL}
claimIds={CUSTOM_HOMEPAGE && channelIds ? channelIds : undefined}
scrollAnchor={MORE_CHANNELS_ANCHOR}
maxPages={SIMPLE_SITE ? 3 : undefined}
hideFilters={SIMPLE_SITE}
header={SIMPLE_SITE ? <h1 className="section__title">{__('Moon cheese is an acquired taste')}</h1> : undefined}
/>
</Page>
);

View file

@ -2,6 +2,7 @@
import { SHOW_ADS, DOMAIN, SIMPLE_SITE, ENABLE_NO_SOURCE_CLAIMS } from 'config';
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import * as CS from 'constants/claim_search';
import React, { useRef } from 'react';
import Page from 'component/page';
import ClaimListDiscover from 'component/claimListDiscover';
@ -11,11 +12,11 @@ import { useIsMobile } from 'effects/use-screensize';
import analytics from 'analytics';
import HiddenNsfw from 'component/common/hidden-nsfw';
import Icon from 'component/common/icon';
import * as CS from 'constants/claim_search';
import Ads from 'web/component/ads';
import LbcSymbol from 'component/common/lbc-symbol';
import I18nMessage from 'component/i18nMessage';
import useGetLivestreams from 'effects/use-get-livestreams';
import moment from 'moment';
type Props = {
location: { search: string },
@ -52,6 +53,8 @@ function DiscoverPage(props: Props) {
const tags = tagsQuery ? tagsQuery.split(',') : null;
const repostedClaimIsResolved = repostedUri && repostedClaim;
const discoverIcon = SIMPLE_SITE ? ICONS.WILD_WEST : ICONS.DISCOVER;
const discoverLabel = SIMPLE_SITE ? __('Wild West') : __('All Content');
// Eventually allow more than one tag on this page
// Restricting to one to make follow/unfollow simpler
const tag = (tags && tags[0]) || null;
@ -97,8 +100,8 @@ function DiscoverPage(props: Props) {
} else {
headerLabel = (
<span>
<Icon icon={(dynamicRouteProps && dynamicRouteProps.icon) || ICONS.DISCOVER} size={10} />
{(dynamicRouteProps && dynamicRouteProps.title) || __('All Content')}
<Icon icon={(dynamicRouteProps && dynamicRouteProps.icon) || discoverIcon} size={10} />
{(dynamicRouteProps && dynamicRouteProps.title) || discoverLabel}
</span>
);
}
@ -106,9 +109,11 @@ function DiscoverPage(props: Props) {
return (
<Page noFooter fullWidthPage={tileLayout}>
<ClaimListDiscover
limitClaimsPerChannel={3}
hideAdvancedFilter={SIMPLE_SITE}
hideFilters={SIMPLE_SITE ? !dynamicRouteProps : undefined}
header={repostedUri ? <span /> : undefined}
tileLayout={repostedUri ? false : tileLayout}
defaultOrderBy={SIMPLE_SITE ? (dynamicRouteProps ? undefined : CS.ORDER_BY_TRENDING) : undefined}
claimType={claimType ? [claimType] : undefined}
headerLabel={headerLabel}
tags={tags}
@ -117,9 +122,24 @@ function DiscoverPage(props: Props) {
injectedItem={
SHOW_ADS && IS_WEB ? (SIMPLE_SITE ? false : !isAuthenticated && <Ads small type={'video'} />) : false
}
// 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
releaseTime={
SIMPLE_SITE
? !dynamicRouteProps && `>${Math.floor(moment().subtract(1, 'day').startOf('week').unix())}`
: undefined
}
feeAmount={SIMPLE_SITE ? !dynamicRouteProps && CS.FEE_AMOUNT_ANY : undefined}
channelIds={
(dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.channelIds) || undefined
}
limitClaimsPerChannel={
SIMPLE_SITE
? (dynamicRouteProps && dynamicRouteProps.options && dynamicRouteProps.options.limitClaimsPerChannel) ||
undefined
: 3
}
meta={
!dynamicRouteProps ? (
<a

View file

@ -1,4 +1,5 @@
// @flow
import * as PAGES from 'constants/pages';
import * as React from 'react';
import classnames from 'classnames';
import { lazyImport } from 'util/lazyImport';
@ -11,6 +12,9 @@ import FileRenderDownload from 'component/fileRenderDownload';
import RecommendedContent from 'component/recommendedContent';
import CollectionContent from 'component/collectionContentSidebar';
import CommentsList from 'component/commentsList';
import { Redirect } from 'react-router';
import Button from 'component/button';
import I18nMessage from 'component/i18nMessage';
import Empty from 'component/common/empty';
const PostViewer = lazyImport(() => import('component/postViewer' /* webpackChunkName: "postViewer" */));
@ -32,7 +36,9 @@ type Props = {
collection?: Collection,
collectionId: string,
videoTheaterMode: boolean,
claimIsMine: boolean,
commentsDisabled: boolean,
isLivestream: boolean,
};
function FilePage(props: Props) {
@ -49,9 +55,12 @@ function FilePage(props: Props) {
linkedCommentId,
setPrimaryUri,
videoTheaterMode,
claimIsMine,
commentsDisabled,
collection,
collectionId,
isLivestream,
} = props;
const cost = costInfo ? costInfo.cost : null;
const hasFileInfo = fileInfo !== undefined;
@ -136,6 +145,10 @@ function FilePage(props: Props) {
);
}
if (!claimIsMine && isLivestream) {
return <Redirect to={`/$/${PAGES.LIVESTREAM}`} />;
}
if (obscureNsfw && isMature) {
return (
<Page className="file-page" filePage isMarkdown={isMarkdown}>
@ -156,6 +169,18 @@ function FilePage(props: Props) {
{!isMarkdown && (
<div className="file-page__secondary-content">
<div>
{claimIsMine && isLivestream && (
<div className="livestream__creator-message">
<h4>{__('Only visible to you')}</h4>
<I18nMessage>
People who view this link will be redirected to your livestream. Make sure to use this for sharing
so your title and thumbnail are displayed properly.
</I18nMessage>
<div className="section__actions">
<Button button="primary" navigate={`/$/${PAGES.LIVESTREAM}`} label={__('View livestream')} />
</div>
</div>
)}
{RENDER_MODES.FLOATING_MODES.includes(renderMode) && <FileTitleSection uri={uri} />}
{commentsDisabled && <Empty text={__('The creator of this content has disabled comments.')} />}
{!commentsDisabled && <CommentsList uri={uri} linkedCommentId={linkedCommentId} />}

View file

@ -62,7 +62,6 @@ function HomePage(props: Props) {
showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS}
hasSource
pinUrls={pinUrls}
pin={route === `/$/${PAGES.GENERAL}`} // use pinUrls here
/>
);
@ -145,12 +144,12 @@ function HomePage(props: Props) {
{/* @if TARGET='web' */}
{SIMPLE_SITE && <Meme />}
{/* @endif */}
{rowData.map(({ title, route, link, icon, help, pinUrls, options = {} }, index) => {
{rowData.map(({ title, route, link, icon, help, pinnedUrls: pinUrls, options = {} }, index) => {
// add pins here
return getRowElements(title, route, link, icon, help, options, index, pinUrls);
})}
{/* @if TARGET='web' */}
<Pixel type={'retargeting'} />
<Pixel type={'retargeting'} />
{/* @endif */}
</Page>
);

View file

@ -1,5 +1,5 @@
// @flow
import { BITWAVE_LIVE_API } from 'constants/livestream';
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
import React from 'react';
import Page from 'component/page';
import LivestreamLayout from 'component/livestreamLayout';
@ -56,8 +56,9 @@ export default function LivestreamPage(props: Props) {
React.useEffect(() => {
let interval;
function checkIsLive() {
// $FlowFixMe Bitwave's API can handle garbage
fetch(`${BITWAVE_LIVE_API}/${livestreamChannelId}`)
// TODO: duplicate code below
// $FlowFixMe livestream API can handle garbage
fetch(`${LIVESTREAM_LIVE_API}/${livestreamChannelId}`)
.then((res) => res.json())
.then((res) => {
if (!res || !res.data) {
@ -105,7 +106,7 @@ export default function LivestreamPage(props: Props) {
React.useEffect(() => {
// Set playing uri to null so the popout player doesnt start playing the dummy claim if a user navigates back
// This can be removed when we start using the app video player, not a bitwave iframe
// This can be removed when we start using the app video player, not a LIVESTREAM iframe
doSetPlayingUri({ uri: null });
}, [doSetPlayingUri]);

View file

@ -16,6 +16,7 @@ import CopyableText from 'component/copyableText';
import Card from 'component/common/card';
import ClaimList from 'component/claimList';
import usePersistedState from 'effects/use-persisted-state';
import { LIVESTREAM_RTMP_URL } from 'constants/livestream';
type Props = {
channels: Array<ChannelClaim>,
@ -182,7 +183,7 @@ export default function LivestreamSetupPage(props: Props) {
primaryButton
name="stream-server"
label={__('Stream server')}
copyable="rtmp://stream.odysee.com/live"
copyable={LIVESTREAM_RTMP_URL}
snackMessage={__('Copied')}
/>
<CopyableText

View file

@ -15,6 +15,7 @@ import Card from 'component/common/card';
import SettingAccountPassword from 'component/settingAccountPassword';
import classnames from 'classnames';
import { getPasswordFromCookie } from 'util/saved-passwords';
import { SIMPLE_SITE } from 'config';
// $FlowFixMe
import homepages from 'homepages';
import { Lbryio } from 'lbryinc';
@ -208,37 +209,41 @@ class SettingsPage extends React.PureComponent<Props, State> {
className="card-stack"
>
{/* @if TARGET='web' */}
{user && user.fiat_enabled && <Card
title={__('Bank Accounts')}
subtitle={__('Connect a bank account to receive tips and compensation in your local currency')}
actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
navigate={`/$/${PAGES.SETTINGS_STRIPE_ACCOUNT}`}
/>
</div>
}
/>}
{user && user.fiat_enabled && (
<Card
title={__('Bank Accounts')}
subtitle={__('Connect a bank account to receive tips and compensation in your local currency')}
actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
navigate={`/$/${PAGES.SETTINGS_STRIPE_ACCOUNT}`}
/>
</div>
}
/>
)}
{/* @endif */}
{/* @if TARGET='web' */}
<Card
title={__('Payment Methods')}
subtitle={__('Add a credit card to tip creators in their local currency')}
actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`}
/>
</div>
}
/>
{isAuthenticated && (
<Card
title={__('Payment Methods')}
subtitle={__('Add a credit card to tip creators in their local currency')}
actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`}
/>
</div>
}
/>
)}
{/* @endif */}
<Card title={__('Language')} actions={<SettingLanguage />} />
@ -393,46 +398,53 @@ class SettingsPage extends React.PureComponent<Props, State> {
'Autoplay video and audio files when navigating to a file, as well as the next related item when a file finishes playing.'
)}
/>
{!SIMPLE_SITE && (
<>
<FormField
type="checkbox"
name="hide_reposts"
onChange={(e) => {
if (isAuthenticated) {
let param = e.target.checked ? { add: 'noreposts' } : { remove: 'noreposts' };
Lbryio.call('user_tag', 'edit', param);
}
<FormField
type="checkbox"
name="hide_reposts"
onChange={(e) => {
if (isAuthenticated) {
let param = e.target.checked ? { add: 'noreposts' } : { remove: 'noreposts' };
Lbryio.call('user_tag', 'edit', param);
}
setClientSetting(SETTINGS.HIDE_REPOSTS, !hideReposts);
}}
checked={hideReposts}
label={__('Hide reposts')}
helper={__(
'You will not see reposts by people you follow or receive email notifying about them.'
)}
/>
setClientSetting(SETTINGS.HIDE_REPOSTS, !hideReposts);
}}
checked={hideReposts}
label={__('Hide reposts')}
helper={__('You will not see reposts by people you follow or receive email notifying about them.')}
/>
{/*
<FormField
type="checkbox"
name="show_anonymous"
onChange={() => setClientSetting(SETTINGS.SHOW_ANONYMOUS, !showAnonymous)}
checked={showAnonymous}
label={__('Show anonymous content')}
helper={__('Anonymous content is published without a channel.')}
/>
*/}
{/* <FormField
type="checkbox"
name="show_anonymous"
onChange={() => setClientSetting(SETTINGS.SHOW_ANONYMOUS, !showAnonymous)}
checked={showAnonymous}
label={__('Show anonymous content')}
helper={__('Anonymous content is published without a channel.')}
/> */}
<FormField
type="checkbox"
name="show_nsfw"
onChange={() =>
!IS_WEB || showNsfw
? setClientSetting(SETTINGS.SHOW_MATURE, !showNsfw)
: openModal(MODALS.CONFIRM_AGE)
}
checked={showNsfw}
label={__('Show mature content')}
helper={__(
'Mature content may include nudity, intense sexuality, profanity, or other adult content. By displaying mature content, you are affirming you are of legal age to view mature content in your country or jurisdiction. '
)}
/>
<FormField
type="checkbox"
name="show_nsfw"
onChange={() =>
!IS_WEB || showNsfw
? setClientSetting(SETTINGS.SHOW_MATURE, !showNsfw)
: openModal(MODALS.CONFIRM_AGE)
}
checked={showNsfw}
label={__('Show mature content')}
helper={__(
'Mature content may include nudity, intense sexuality, profanity, or other adult content. By displaying mature content, you are affirming you are of legal age to view mature content in your country or jurisdiction. '
)}
/>
</>
)}
</React.Fragment>
}
/>

View file

@ -380,7 +380,7 @@ export default function SettingsCreatorPage(props: Props) {
)}
<TagsSearch
label={__('Moderators')}
labelAddNew={__('Add moderators')}
labelAddNew={__('Add moderator')}
onRemove={removeModerator}
onSelect={addModerator}
tagsPassedIn={moderatorTags}

View file

@ -163,7 +163,7 @@ class StripeAccountConnection extends React.Component<Props, State> {
// if it's beamer's error indicating the account is not linked yet
if (error.message.indexOf(errorString) > -1) {
// get stripe link and set it on the frontend
getAndSetAccountLink();
getAndSetAccountLink(true);
} else {
// not an error from Beamer, throw it
throw new Error(error);

View file

@ -436,7 +436,6 @@ export function doCommentCreate(
...(environment ? { environment } : {}), // add environment for stripe if it exists
})
.then((result: CommentCreateResponse) => {
console.log(result);
dispatch({
type: ACTIONS.COMMENT_CREATE_COMPLETED,
data: {
@ -449,7 +448,6 @@ export function doCommentCreate(
return result;
})
.catch((error) => {
console.log(error);
dispatch({
type: ACTIONS.COMMENT_CREATE_FAILED,
data: error,
@ -472,6 +470,9 @@ export function doCommentCreate(
case 'comments are disabled by the creator':
toastMessage = __('Unable to comment. The content owner has disabled comments.');
break;
case 'duplicate comment!':
toastMessage = __('Please do not spam.');
break;
default:
const BLOCKED_WORDS_ERR_MSG = 'the comment contents are blocked by';
const SLOW_MODE_PARTIAL_ERR_MSG = 'Slow mode is on. Please wait at most';
@ -1390,7 +1391,9 @@ export const doFetchCreatorSettings = (channelClaimIds: Array<string> = []) => {
const channelId = channelSignatures[i].claim_id;
settingsByChannelId[channelId] = settings[i];
settingsByChannelId[channelId].words = settingsByChannelId[channelId].words.split(',');
if (settings[i].words) {
settingsByChannelId[channelId].words = settings[i].words.split(',');
}
delete settingsByChannelId[channelId].channel_name;
delete settingsByChannelId[channelId].channel_id;

View file

@ -986,6 +986,11 @@ export default handleActions(
fetchingSettings: false,
}),
[ACTIONS.COMMENT_FETCH_SETTINGS_COMPLETED]: (state: CommentsState, action: any) => {
// TODO: This is incorrect, as it could make 'settingsByChannelId' store
// only 1 channel with other channel's data purged. It works for now
// because the GUI only shows 1 channel's setting at a time, and *always*
// re-fetches to get latest data before displaying. Either rename this to
// 'activeChannelCreatorSettings', or append the new data properly.
return {
...state,
settingsByChannelId: action.data,

View file

@ -3,17 +3,19 @@ import * as ACTIONS from 'constants/action_types';
import { handleActions } from 'util/redux-utils';
import { SEARCH_OPTIONS, SEARCH_PAGE_SIZE } from 'constants/search';
import { createNormalizedSearchKey } from 'util/search';
import { LIGHTHOUSE_DEFAULT_TYPES } from 'config';
const defaultSearchTypes = LIGHTHOUSE_DEFAULT_TYPES && LIGHTHOUSE_DEFAULT_TYPES.split(',');
const defaultState: SearchState = {
// $FlowFixMe
options: {
[SEARCH_OPTIONS.RESULT_COUNT]: SEARCH_PAGE_SIZE,
[SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
[SEARCH_OPTIONS.MEDIA_AUDIO]: true,
[SEARCH_OPTIONS.MEDIA_VIDEO]: true,
[SEARCH_OPTIONS.MEDIA_TEXT]: true,
[SEARCH_OPTIONS.MEDIA_IMAGE]: true,
[SEARCH_OPTIONS.MEDIA_APPLICATION]: true,
[SEARCH_OPTIONS.MEDIA_AUDIO]: defaultSearchTypes.includes(SEARCH_OPTIONS.MEDIA_AUDIO),
[SEARCH_OPTIONS.MEDIA_VIDEO]: defaultSearchTypes.includes(SEARCH_OPTIONS.MEDIA_VIDEO),
[SEARCH_OPTIONS.MEDIA_TEXT]: defaultSearchTypes.includes(SEARCH_OPTIONS.MEDIA_TEXT),
[SEARCH_OPTIONS.MEDIA_IMAGE]: defaultSearchTypes.includes(SEARCH_OPTIONS.MEDIA_IMAGE),
[SEARCH_OPTIONS.MEDIA_APPLICATION]: defaultSearchTypes.includes(SEARCH_OPTIONS.MEDIA_APPLICATION),
},
urisByQuery: {},
hasReachedMaxResultsLength: {},

View file

@ -33,6 +33,10 @@
min-width: 15rem;
}
.ads__injected-video {
aspect-ratio: 16 / 9;
}
.avp-p-gui {
z-index: 1 !important;
}

View file

@ -105,6 +105,10 @@
aspect-ratio: 16 / 9;
}
.file-render--post-container {
min-height: 30vh;
}
.file-render__header {
display: flex;
justify-content: space-between;

View file

@ -77,7 +77,6 @@
justify-content: center;
align-items: center;
border-radius: var(--border-radius);
color: var(--color-text);
position: relative;
font-weight: var(--font-weight-bold);
@ -151,6 +150,7 @@
align-items: center;
margin-left: var(--spacing-m);
margin-right: var(--spacing-m);
color: var(--color-text);
// move to lbry theme?
.lbry-icon {
height: var(--height-button);

View file

@ -95,9 +95,23 @@
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.file-page__post-comments {
margin-top: var(--spacing-l);
opacity: 0;
animation: fadeIn 2s;
animation-delay: 2s;
animation-fill-mode: forwards;
@media (min-width: $breakpoint-small) {
padding: var(--spacing-m);
}

View file

@ -106,6 +106,7 @@ export const getHomepageRowForCat = (cat: HomepageCat) => {
route: cat.name ? `/$/${cat.name}` : undefined,
icon: cat.icon || '', // some default
title: cat.label,
pinnedUrls: cat.pinnedUrls,
options: {
claimType: cat.claimType || 'stream',
channelIds: cat.channelIds,
@ -336,6 +337,9 @@ export function GetLinksData(
rowData.push(LATEST_FROM_LBRY);
if (!showPersonalizedChannels) rowData.push(TOP_CHANNELS);
}
(Object.values(all): any).map((row) => rowData.push(getHomepageRowForCat(row)));
// TODO: provide better method for exempting from homepage
(Object.values(all): any)
.filter((row) => !(isHomepage && row.name === 'news'))
.map((row) => rowData.push(getHomepageRowForCat(row)));
return rowData;
}

View file

@ -1,19 +1,30 @@
import React from 'react';
let localStorageAvailable;
try {
localStorageAvailable = Boolean(window.localStorage);
} catch (e) {
localStorageAvailable = false;
}
export const lazyImport = (componentImport) =>
React.lazy(async () => {
const pageHasAlreadyBeenForceRefreshed = JSON.parse(
window.localStorage.getItem('page-has-been-force-refreshed') || 'false'
);
const pageHasAlreadyBeenForceRefreshed = localStorageAvailable
? JSON.parse(window.localStorage.getItem('page-has-been-force-refreshed') || 'false')
: false;
try {
const component = await componentImport();
window.localStorage.setItem('page-has-been-force-refreshed', 'false');
if (localStorageAvailable) {
window.localStorage.setItem('page-has-been-force-refreshed', 'false');
}
return component;
} catch (error) {
if (!pageHasAlreadyBeenForceRefreshed) {
// It's highly likely that the user's session is old. Try reloading once.
window.localStorage.setItem('page-has-been-force-refreshed', 'true');
if (localStorageAvailable) {
window.localStorage.setItem('page-has-been-force-refreshed', 'true');
}
return window.location.reload();
}

View file

@ -82,7 +82,7 @@ function Ads(props: Props) {
const videoAd = (
<div className="ads__claim-item">
<div id="62d1eb10-e362-4873-99ed-c64a4052b43b" />
<div id="62d1eb10-e362-4873-99ed-c64a4052b43b" className="ads__injected-video" />
<div
className={classnames('ads__claim-text', {
'ads__claim-text--small': small,

View file

@ -4,7 +4,7 @@ import Button from 'component/button';
import { formatLbryUrlForWeb } from 'util/url';
import { withRouter } from 'react-router';
import { URL, SITE_NAME } from 'config';
import * as ICONS from 'constants/icons';
import Logo from 'component/logo';
type Props = {
uri: string,
@ -37,7 +37,9 @@ function FileViewerEmbeddedEnded(props: Props) {
return (
<div className="file-viewer__overlay">
<div className="file-viewer__overlay-secondary">
<Button className="file-viewer__overlay-logo" label="LBRY" icon={ICONS.LBRY} href={URL} />
<Button className="file-viewer__overlay-logo" href={URL}>
<Logo type={'embed'} />
</Button>
</div>
<div className="file-viewer__overlay-title">{prompt}</div>
<div className="file-viewer__overlay-actions">

View file

@ -1,101 +1,38 @@
import * as PAGES from 'constants/pages';
import React from 'react';
import Button from 'component/button';
const sections = [
{
name: 'Community',
links: [
{
label: 'Twitter',
link: 'https://twitter.com/lbrycom',
},
{
label: 'Reddit',
link: 'https://reddit.com/r/lbry',
},
{
label: 'Chat (Discord)',
link: 'https://chat.lbry.org/',
},
{
label: 'Telegram',
link: 'https://t.me/lbryofficial',
},
{
label: 'Facebook',
link: 'https://www.facebook.com/lbryio',
},
],
},
{
name: 'Resources',
links: [
{
label: 'FAQ',
link: 'https://lbry.com/faq',
},
{
label: 'Support --[used in footer; general help/support]--',
link: 'https://lbry.com/faq/support',
},
{
label: 'YouTube Partner Program',
link: 'https://lbry.com/youtube',
},
{
label: 'lbry.com',
link: 'https://lbry.com',
},
{
label: 'lbry.tech',
link: 'https://lbry.tech',
},
{
label: 'GitHub',
link: 'https://github.com/lbryio',
},
],
},
{
name: 'Policies',
links: [
{
label: 'Terms of Service',
link: 'https://www.lbry.com/termsofservice',
},
{
label: 'Privacy Policy',
link: 'https://lbry.com/privacypolicy',
},
{
label: '2257',
navigate: `/$/${PAGES.CODE_2257}`,
},
],
},
];
import I18nMessage from 'component/i18nMessage';
import { SIMPLE_SITE } from 'config';
export default function Footer() {
if (!SIMPLE_SITE) {
return null;
}
return (
<footer className="footer">
<ul className="navigation__tertiary footer__links ul--no-style">
{sections.map(({ name, links }) => {
return (
<li key={name} className="footer__section">
<ul className="ul--no-style">
<div className="footer__section-title">{__(name)}</div>
{links.map(({ label, link, navigate }) => {
return (
<li key={label}>
<Button className="footer__link" label={__(label)} href={link} navigate={navigate} />
</li>
);
})}
</ul>
</li>
);
})}
<span className="footer__section-title">
<I18nMessage tokens={{ lbry_link: <Button button="link" label={'LBRY'} href="https://lbry.com" /> }}>
POWERED BY %lbry_link%
</I18nMessage>
</span>
<ul className="navigation__tertiary footer__links">
<li className="footer__link">
<Button label={__('About --[link title in Sidebar or Footer]--')} href="https://lbry.com/about" />
</li>
<li className="footer__link">
<Button label={__('Community Guidelines')} href="https://odysee.com/@OdyseeHelp:b/Community-Guidelines:c" />
</li>
<li className="footer__link">
<Button label={__('FAQ')} href="https://odysee.com/@OdyseeHelp:b" />
</li>
<li className="footer__link">
<Button label={__('Support --[used in footer; general help/support]--')} href="https://lbry.com/support" />
</li>
<li className="footer__link">
<Button label={__('Terms')} href="https://lbry.com/termsofservice" />
</li>
<li className="footer__link">
<Button label={__('Privacy Policy')} href="https://lbry.com/privacy" />
</li>
</ul>
</footer>
);

View file

@ -1,11 +1,12 @@
import React from 'react';
import Button from 'component/button';
const memes = require('memes');
export default function Meme() {
return (
<h1 className="home__meme">
<Button button="link" href="https://odysee.com/@Odysee:8?view=discussion">
{'big gulps, huh?'}
<Button button="link" href={memes.url}>
{memes.text}
</Button>
</h1>
);

View file

@ -48,11 +48,11 @@ module.exports.CATEGORY_METADATA = {
description: `Do you love B rated movies? We've got you covered on Odysee`,
image: 'https://spee.ch/category-movies:2.jpg?quality=80&height=1200&width=630',
},
// [PAGES.MUSIC]: {
// title: 'Music',
// description: 'All the music you love on Odysee',
// image: 'https://spee.ch/category-music:8.jpg?quality=80&height=1200&width=630',
// },
[PAGES.MUSIC]: {
title: 'Music',
description: 'All the songs, reviews, covers, and how-tos you love on Odysee',
image: 'https://spee.ch/category-music:8.jpg?quality=80&height=1200&width=630',
},
[PAGES.TECHNOLOGY]: {
title: 'Tech',
description: 'Hardware, software, startups, photography on Odysee',

View file

@ -8,6 +8,7 @@ const {
OG_IMAGE_URL,
SITE_DESCRIPTION,
SITE_NAME,
FAVICON,
} = require('../../config.js');
const { generateEmbedUrl, generateStreamUrl, generateDirectUrl } = require('../../ui/util/web');
const PAGES = require('../../ui/constants/pages');
@ -82,20 +83,31 @@ function buildOgMetadata(overrideOptions = {}) {
function conditionallyAddPWA() {
let head = '';
if (DOMAIN === 'odysee.com') {
head += '<link rel="manifest" href="./public/pwa/manifest.json"/>';
head += '<link rel="manifest" href="./public/pwa/manifest.json"/>';
head += '<link rel="apple-touch-icon" sizes="180x180" href="./public/pwa/icon-180.png">';
head += '<script src="./serviceWorker.js"></script>';
}
return head;
}
function addFavicon() {
let head = '';
head += `<link rel="icon" type="image/png" href="${FAVICON || './public/favicon.png'}" />`;
return head;
}
function buildHead() {
const head = '<!-- VARIABLE_HEAD_BEGIN -->' + conditionallyAddPWA() + buildOgMetadata() + '<!-- VARIABLE_HEAD_END -->';
const head =
'<!-- VARIABLE_HEAD_BEGIN -->' +
addFavicon() +
conditionallyAddPWA() +
buildOgMetadata() +
'<!-- VARIABLE_HEAD_END -->';
return head;
}
function buildBasicOgMetadata() {
const head = '<!-- VARIABLE_HEAD_BEGIN -->' + buildOgMetadata() + '<!-- VARIABLE_HEAD_END -->';
const head = '<!-- VARIABLE_HEAD_BEGIN -->' + addFavicon() + buildOgMetadata() + '<!-- VARIABLE_HEAD_END -->';
return head;
}
@ -128,6 +140,7 @@ function buildClaimOgMetadata(uri, claim, overrideOptions = {}) {
let head = '';
head += `${addFavicon()}`;
head += '<meta charset="utf8"/>';
head += `<title>${title}</title>`;
head += `<meta name="description" content="${cleanDescription}"/>`;

View file

@ -1,5 +1,5 @@
const { generateDownloadUrl } = require('../../ui/util/web');
const { URL, SITE_NAME, LBRY_WEB_API } = require('../../config.js');
const { URL, SITE_NAME, LBRY_WEB_API, FAVICON } = require('../../config.js');
const { Lbry } = require('lbry-redux');
const Feed = require('feed').Feed;
@ -82,7 +82,7 @@ async function getFeed(channelClaim, feedLink) {
const title = value ? value.title : channelClaim.name;
const options = {
favicon: URL + '/public/favicon.png',
favicon: FAVICON || URL + '/public/favicon.png',
generator: SITE_NAME + ' RSS Feed',
title: title + ' on ' + SITE_NAME,
description: fmtDescription(value && value.description ? value.description : ''),

View file

@ -1,11 +1,11 @@
const { URL, SITE_TITLE } = require('../../config.js');
const { URL, SITE_TITLE, FAVICON } = require('../../config.js');
const favicon = FAVICON || `${URL}/public/favicon.png`;
function getOpenSearchXml() {
return (
`<ShortName>${SITE_TITLE}</ShortName>` +
`<Description>Search ${SITE_TITLE}</Description>` +
'<InputEncoding>UTF-8</InputEncoding>' +
`<Image width="32" height="32" type="image/png">${URL}/public/favicon.png</Image>` +
`<Image width="32" height="32" type="image/png">${favicon}</Image>` +
`<Url type="text/html" method="get" template="${URL}/$/search?q={searchTerms}"/>` +
`<moz:SearchForm>${URL}</moz:SearchForm>`
);

View file

@ -77,6 +77,7 @@ let baseConfig = {
config: path.resolve(__dirname, 'config.js'),
homepage: 'util/homepage.js',
homepages: process.env.CUSTOM_HOMEPAGE === 'true' ? path.resolve(__dirname, 'custom/homepages/v2/index.js') : ('homepages/index.js'),
memes: process.env.CUSTOM_HOMEPAGE === 'true' ? path.resolve(__dirname, 'custom/homepages/meme/index.js') : path.resolve(__dirname, 'homepages/meme/index.js'),
lbryinc: 'lbryinc/dist/bundle.es.js',
// Build optimizations for 'redux-persist-transform-filter'
'redux-persist-transform-filter': 'redux-persist-transform-filter/index.js',