Merge branch 'master' into protocol

This commit is contained in:
Baltazar Gomez 2021-07-29 20:46:13 -05:00 committed by GitHub
commit 3f3be5feb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
104 changed files with 4334 additions and 464 deletions

View file

@ -11,7 +11,7 @@ WEB_SERVER_PORT=1337
LBRY_WEB_API=https://api.na-backend.odysee.com
LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
COMMENT_SERVER_API=https://comments.lbry.com/api/v2
COMMENT_SERVER_API=https://comments.odysee.com/api/v2
THUMBNAIL_CDN_URL=https://image-processor.vanwanet.com/optimize/
WELCOME_VERSION=1.0
@ -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
@ -67,6 +68,7 @@ AUTO_FOLLOW_CHANNELS=lbry://@lbry#3fda836a92faaceedfe398225fb9b2ee2ed1f01a
## FEATURES AND LIMITS
SIMPLE_SITE=false
#BRANDED_SITE
ENABLE_COMMENT_REACTIONS=true
ENABLE_FILE_REACTIONS=false
@ -77,6 +79,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

@ -3,7 +3,7 @@
"ui/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
"web/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
"ui/**/*.{js,jsx}": ["eslint", "flow focus-check --color always", "git add"],
"web/**/*.{js,jsx,scss}": ["eslint", "git add"]
"web/**/*.{js,jsx}": ["eslint", "git add"]
},
"ignore": ["node_modules", "web/dist/**/*", "dist/**/*", "package-lock.json"]
}

View file

@ -8,17 +8,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- Show currently active playing item on playlist _community pr!_ ([#6453](https://github.com/lbryio/lbry-desktop/pull/6453))
- 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))
### Changed
- Use Canonical Url for copy link ([#6500](https://github.com/lbryio/lbry-desktop/pull/6500))
- Use better icon for copy link ([#6485](https://github.com/lbryio/lbry-desktop/pull/6485))
- Comments load paginated ([#6390](https://github.com/lbryio/lbry-desktop/pull/6390))
- Update lighthouse search api _community pr!_ ([#6731](https://github.com/lbryio/lbry-desktop/pull/6731))
- Improve twitter share _community pr!_ ([#6690](https://github.com/lbryio/lbry-desktop/pull/6690))
### Fixed
- App now supports '#' and ':' for claimId separator ([#6496](https://github.com/lbryio/lbry-desktop/pull/6496))
- Fix "exact match" being applied to Recommended ([#6460](https://github.com/lbryio/lbry-desktop/pull/6460))
- Fix upload button on creator analytics _community pr!_ ([#6458](https://github.com/lbryio/lbry-desktop/pull/6458))
- Prevent sidebar shortcut activation on textarea _community pr!_ ([#6454](https://github.com/lbryio/lbry-desktop/pull/6454))
- Improve accessibility and some minor css fixes _community pr!_ ([#6470](https:/github.com/lbryio/lbry-desktop/pull/6470))
- Fix drag / drop publish issues for web users _community pr!_ ([#6466](https://github.com/lbryio/lbry-desktop/pull/6466))
- Fix yarn copyenv on windows _community pr!_ ([#6702](https://github.com/lbryio/lbry-desktop/pull/6702))
- Fix unnecessary livestream api calls in channel page _community pr!_ ([#6652](https://github.com/lbryio/lbry-desktop/pull/6652))
## [0.51.1] - [2021-06-26]

View file

@ -9,6 +9,7 @@ const config = {
WEBPACK_ELECTRON_PORT: process.env.WEBPACK_ELECTRON_PORT,
WEB_SERVER_PORT: process.env.WEB_SERVER_PORT,
LBRY_WEB_API: process.env.LBRY_WEB_API, //api.na-backend.odysee.com',
LBRY_WEB_PUBLISH_API: process.env.LBRY_WEB_PUBLISH_API,
LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com',
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //cdn.lbryplayer.xyz',
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
@ -25,9 +26,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,
@ -63,7 +65,8 @@ 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',
AVATAR_DEFAULT: process.env.AVATAR_DEFAULT,
LIGHTHOUSE_DEFAULT_TYPES: process.env.LIGHTHOUSE_DEFAULT_TYPES,
BRANDED_SITE: process.env.BRANDED_SITE,
};
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

@ -24,7 +24,7 @@
"compile:electron": "node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.electron.config.js",
"compile:web": "yarn copyenv && cd web && node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.config.js",
"compile": "cross-env NODE_ENV=production yarn compile:electron && cross-env NODE_ENV=production yarn compile:web",
"copyenv": "cp ./.env* web/",
"copyenv": "copyfiles ./.env* web/",
"dev": "yarn dev:electron",
"dev:electron": "cross-env NODE_ENV=development node ./electron/devServer.js",
"dev:web": "yarn copyenv && cd web && yarn dev",
@ -107,6 +107,7 @@
"concurrently": "^4.1.2",
"connected-react-router": "^6.8.0",
"copy-webpack-plugin": "^5.1.2",
"copyfiles": "^2.4.1",
"country-data": "^0.0.31",
"cross-env": "^7.0.3",
"crypto-js": "^4.0.0",

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,12 +2056,20 @@
"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",
"added to": "added to",
"removed from": "removed from",
"Skip Navigation": "Skip Navigation",
"Reset": "Reset",
"Reset to original (previous) publish date": "Reset to original (previous) publish date",
"Show reply": "Show reply",
"Show %count% replies": "Show %count% replies",
"Open in Desktop": "Open in Desktop",
"%title% by %channelTitle%": "%title% by %channelTitle%",
"%title% by %channelTitle% %ariaDate%": "%title% by %channelTitle% %ariaDate%",
"%title% by %channelTitle% %ariaDate%, %mediaDuration%": "%title% by %channelTitle% %ariaDate%, %mediaDuration%",
"Search for something...": "Search for something...",
"--end--": "--end--"
}

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

@ -285,6 +285,11 @@ function App(props: Props) {
}
}, [hasMyChannels, hasNoChannels, hasActiveChannelClaim, setActiveChannelIfNotSet, setIncognito]);
useEffect(() => {
// $FlowFixMe
document.documentElement.setAttribute('lang', language);
}, [language]);
useEffect(() => {
if (!languages.includes(language)) {
setLanguage(language);

View file

@ -97,6 +97,9 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
const combinedRef = useCombinedRefs(ref, innerRef, myref);
const size = iconSize || (!label && !children) ? 18 : undefined; // Fall back to default
// Label can be a string or object ( use title instead )
const ariaLabel = description || (typeof label === 'string' ? label : title);
const content = (
<span className="button__content">
{icon && <Icon icon={icon} iconColor={iconColor} size={iconSize} />}
@ -150,6 +153,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
className={combinedClassName}
title={title}
onClick={onClick}
aria-label={ariaLabel}
>
{content}
</a>
@ -196,6 +200,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
disabled={disable}
className={combinedClassName}
activeClassName={activeClass}
aria-label={ariaLabel}
>
{content}
</NavLink>
@ -216,6 +221,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
}}
className={combinedClassName}
activeClassName={activeClass}
aria-label={ariaLabel}
{...otherProps}
>
{content}
@ -224,7 +230,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
<button
ref={combinedRef}
title={title || defaultTooltip}
aria-label={description || label || title}
aria-label={ariaLabel}
className={combinedClassName}
onClick={(e) => {
if (onClick) {

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

@ -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

@ -15,6 +15,8 @@ import {
doCollectionEdit,
makeSelectUrlsForCollectionId,
makeSelectIndexForUrlInCollection,
makeSelectTitleForUri,
makeSelectDateForUri,
} from 'lbry-redux';
import { selectMutedChannels, makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
@ -22,32 +24,43 @@ import { selectShowMatureContent } from 'redux/selectors/settings';
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
import { selectModerationBlockList } from 'redux/selectors/comments';
import ClaimPreview from './view';
const select = (state, props) => ({
pending: props.uri && makeSelectClaimIsPending(props.uri)(state),
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
reflectingProgress: props.uri && makeSelectReflectingClaimForUri(props.uri)(state),
obscureNsfw: selectShowMatureContent(state) === false,
claimIsMine: props.uri && makeSelectClaimIsMine(props.uri)(state),
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
isResolvingRepost: props.uri && makeSelectIsUriResolving(props.repostUrl)(state),
repostClaim: props.uri && makeSelectClaimForUri(props.uri)(state),
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
blackListedOutpoints: selectBlackListedOutpoints(state),
filteredOutpoints: selectFilteredOutpoints(state),
mutedUris: selectMutedChannels(state),
blockedUris: selectModerationBlockList(state),
hasVisitedUri: props.uri && makeSelectHasVisitedUri(props.uri)(state),
channelIsBlocked: props.uri && makeSelectChannelIsMuted(props.uri)(state),
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state),
streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state),
wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state),
collectionUris: makeSelectUrlsForCollectionId(props.collectionId)(state),
collectionIndex: makeSelectIndexForUrlInCollection(props.uri, props.collectionId)(state),
});
import ClaimPreview from './view';
import formatMediaDuration from 'util/formatMediaDuration';
const select = (state, props) => {
const claim = props.uri && makeSelectClaimForUri(props.uri)(state);
const media = claim && claim.value && (claim.value.video || claim.value.audio);
const mediaDuration = media && media.duration && formatMediaDuration(media.duration, { screenReader: true });
return {
claim,
mediaDuration,
date: props.uri && makeSelectDateForUri(props.uri)(state),
title: props.uri && makeSelectTitleForUri(props.uri)(state),
pending: props.uri && makeSelectClaimIsPending(props.uri)(state),
reflectingProgress: props.uri && makeSelectReflectingClaimForUri(props.uri)(state),
obscureNsfw: selectShowMatureContent(state) === false,
claimIsMine: props.uri && makeSelectClaimIsMine(props.uri)(state),
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
isResolvingRepost: props.uri && makeSelectIsUriResolving(props.repostUrl)(state),
repostClaim: props.uri && makeSelectClaimForUri(props.uri)(state),
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
blackListedOutpoints: selectBlackListedOutpoints(state),
filteredOutpoints: selectFilteredOutpoints(state),
mutedUris: selectMutedChannels(state),
blockedUris: selectModerationBlockList(state),
hasVisitedUri: props.uri && makeSelectHasVisitedUri(props.uri)(state),
channelIsBlocked: props.uri && makeSelectChannelIsMuted(props.uri)(state),
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state),
streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state),
wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state),
collectionUris: makeSelectUrlsForCollectionId(props.collectionId)(state),
collectionIndex: makeSelectIndexForUrlInCollection(props.uri, props.collectionId)(state),
};
};
const perform = (dispatch) => ({
resolveUri: (uri) => dispatch(doResolveUri(uri)),

View file

@ -2,11 +2,12 @@
import type { Node } from 'react';
import React, { useEffect, forwardRef } from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import { isEmpty } from 'util/object';
import { lazyImport } from 'util/lazyImport';
import classnames from 'classnames';
import { parseURI, COLLECTIONS_CONSTS, isURIEqual } from 'lbry-redux';
import { formatLbryUrlForWeb } from 'util/url';
import { isEmpty } from 'util/object';
import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
import FileThumbnail from 'component/fileThumbnail';
import UriIndicator from 'component/uriIndicator';
import PreviewOverlayProperties from 'component/previewOverlayProperties';
@ -85,6 +86,8 @@ type Props = {
collectionUris: Array<Collection>,
collectionIndex?: number,
disableNavigation?: boolean,
mediaDuration?: string,
date?: any,
};
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
@ -99,8 +102,11 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
// claim properties
// is the claim consider nsfw?
nsfw,
date,
title,
claimIsMine,
streamingUrl,
mediaDuration,
// user properties
channelIsBlocked,
hasVisitedUri,
@ -175,6 +181,21 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
(claim.value.stream_type === 'audio' || claim.value.stream_type === 'video');
const isChannelUri = isValid ? parseURI(uri).isChannel : false;
const signingChannel = claim && claim.signing_channel;
// Get channel title ( use name as fallback )
let channelTitle = null;
if (signingChannel) {
const { value, name } = signingChannel;
if (value && value.title) {
channelTitle = value.title;
} else {
channelTitle = name;
}
}
// Aria-label value for claim preview
let ariaLabelData = isChannelUri ? title : formatClaimPreviewTitle(title, channelTitle, date, mediaDuration);
let navigateUrl = formatLbryUrlForWeb((claim && claim.canonical_url) || uri || '/');
if (listId) {
const collectionParams = new URLSearchParams();
@ -313,18 +334,18 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
})}
>
{isChannelUri && claim ? (
<UriIndicator uri={uri} link>
<UriIndicator focusable={false} uri={uri} link>
<ChannelThumbnail uri={uri} small={type === 'inline'} />
</UriIndicator>
) : (
<>
{!pending ? (
<NavLink {...navLinkProps}>
<NavLink aria-hidden tabIndex={-1} {...navLinkProps}>
<FileThumbnail thumbnail={thumbnailUrl}>
{/* @if TARGET='app' */}
{claim && !isCollection && (
<div className="claim-preview__hover-actions">
<FileDownloadLink uri={canonicalUrl} hideOpenButton hideDownloadStatus />
<FileDownloadLink focusable={false} uri={canonicalUrl} hideOpenButton hideDownloadStatus />
</div>
)}
{/* @endif */}
@ -335,7 +356,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
)}
{isPlayable && (
<div className="claim-preview__hover-actions">
<FileWatchLaterLink uri={uri} />
<FileWatchLaterLink focusable={false} uri={uri} />
</div>
)}
</FileThumbnail>
@ -352,7 +373,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
{pending ? (
<ClaimPreviewTitle uri={uri} />
) : (
<NavLink aria-current={active && 'page'} {...navLinkProps}>
<NavLink aria-label={ariaLabelData} aria-current={active ? 'page' : null} {...navLinkProps}>
<ClaimPreviewTitle uri={uri} />
</NavLink>
)}
@ -451,9 +472,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
)}
</div>
</div>
{!hideMenu && (
<ClaimMenuList uri={uri} collectionId={listId} />
)}
{!hideMenu && <ClaimMenuList uri={uri} collectionId={listId} />}
</>
</WrapperElement>
);

View file

@ -9,25 +9,35 @@ import {
makeSelectChannelForClaimUri,
makeSelectClaimIsNsfw,
makeSelectClaimIsStreamPlaceholder,
makeSelectDateForUri,
} from 'lbry-redux';
import { selectMutedChannels } from 'redux/selectors/blocked';
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
import { selectShowMatureContent } from 'redux/selectors/settings';
import ClaimPreviewTile from './view';
import formatMediaDuration from 'util/formatMediaDuration';
const select = (state, props) => ({
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
channel: props.uri && makeSelectChannelForClaimUri(props.uri)(state),
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
thumbnail: props.uri && makeSelectThumbnailForUri(props.uri)(state),
title: props.uri && makeSelectTitleForUri(props.uri)(state),
blackListedOutpoints: selectBlackListedOutpoints(state),
filteredOutpoints: selectFilteredOutpoints(state),
blockedChannelUris: selectMutedChannels(state),
showMature: selectShowMatureContent(state),
isMature: makeSelectClaimIsNsfw(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
});
const select = (state, props) => {
const claim = props.uri && makeSelectClaimForUri(props.uri)(state);
const media = claim && claim.value && (claim.value.video || claim.value.audio);
const mediaDuration = media && media.duration && formatMediaDuration(media.duration, { screenReader: true });
return {
claim,
mediaDuration,
date: props.uri && makeSelectDateForUri(props.uri)(state),
channel: props.uri && makeSelectChannelForClaimUri(props.uri)(state),
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
thumbnail: props.uri && makeSelectThumbnailForUri(props.uri)(state),
title: props.uri && makeSelectTitleForUri(props.uri)(state),
blackListedOutpoints: selectBlackListedOutpoints(state),
filteredOutpoints: selectFilteredOutpoints(state),
blockedChannelUris: selectMutedChannels(state),
showMature: selectShowMatureContent(state),
isMature: makeSelectClaimIsNsfw(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
};
};
const perform = (dispatch) => ({
resolveUri: (uri) => dispatch(doResolveUri(uri)),

View file

@ -10,6 +10,7 @@ import ChannelThumbnail from 'component/channelThumbnail';
import SubscribeButton from 'component/subscribeButton';
import useGetThumbnail from 'effects/use-get-thumbnail';
import { formatLbryUrlForWeb } from 'util/url';
import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
import { parseURI, COLLECTIONS_CONSTS, isURIEqual } from 'lbry-redux';
import PreviewOverlayProperties from 'component/previewOverlayProperties';
import FileDownloadLink from 'component/fileDownloadLink';
@ -22,7 +23,9 @@ import PlaceholderTx from 'static/img/placeholderTx.gif';
type Props = {
uri: string,
date?: any,
claim: ?Claim,
mediaDuration?: string,
resolveUri: (string) => void,
isResolvingUri: boolean,
history: { push: (string) => void },
@ -54,6 +57,7 @@ function ClaimPreviewTile(props: Props) {
const {
history,
uri,
date,
isResolvingUri,
thumbnail,
title,
@ -73,6 +77,7 @@ function ClaimPreviewTile(props: Props) {
showNoSourceClaims,
isLivestream,
collectionId,
mediaDuration,
} = props;
const isRepost = claim && claim.repost_channel_url;
const isCollection = claim && claim.value_type === 'collection';
@ -115,6 +120,10 @@ function ClaimPreviewTile(props: Props) {
const signingChannel = claim && claim.signing_channel;
const isChannel = claim && claim.value_type === 'channel';
const channelUri = !isChannel ? signingChannel && signingChannel.permanent_url : claim && claim.permanent_url;
const channelTitle = signingChannel && (signingChannel.value.title || signingChannel.name);
// Aria-label value for claim preview
let ariaLabelData = isChannel ? title : formatClaimPreviewTitle(title, channelTitle, date, mediaDuration);
function handleClick(e) {
if (navigateUrl) {
@ -189,28 +198,27 @@ function ClaimPreviewTile(props: Props) {
return (
<li
role="link"
onClick={handleClick}
className={classnames('card claim-preview--tile', {
'claim-preview__wrapper--channel': isChannel,
'claim-preview__live': live,
})}
>
<NavLink {...navLinkProps}>
<NavLink {...navLinkProps} role="none" tabIndex={-1} aria-hidden>
<FileThumbnail thumbnail={thumbnailUrl} allowGifs>
{!isChannel && (
<React.Fragment>
{/* @if TARGET='app' */}
{isStream && (
<div className="claim-preview__hover-actions">
<FileDownloadLink uri={canonicalUrl} hideOpenButton />
<FileDownloadLink focusable={false} uri={canonicalUrl} hideOpenButton />
</div>
)}
{/* @endif */}
{isPlayable && (
<div className="claim-preview__hover-actions">
<FileWatchLaterLink uri={uri} />
<FileWatchLaterLink focusable={false} uri={uri} />
</div>
)}
@ -228,17 +236,19 @@ function ClaimPreviewTile(props: Props) {
)}
</FileThumbnail>
</NavLink>
<NavLink {...navLinkProps}>
<h2 className="claim-tile__title">
<TruncatedText text={title || (claim && claim.name)} lines={isChannel ? 1 : 2} />
{isChannel && (
<div className="claim-tile__about">
<UriIndicator uri={uri} />
</div>
)}
<ClaimMenuList uri={uri} collectionId={listId} />
</h2>
</NavLink>
<div className="claim-tile__header">
<NavLink aria-label={ariaLabelData} {...navLinkProps}>
<h2 className="claim-tile__title">
<TruncatedText text={title || (claim && claim.name)} lines={isChannel ? 1 : 2} />
{isChannel && (
<div className="claim-tile__about">
<UriIndicator uri={uri} />
</div>
)}
</h2>
</NavLink>
<ClaimMenuList uri={uri} collectionId={listId} channelUri={channelUri} />
</div>
<div>
<div className="claim-tile__info">
{isChannel ? (
@ -247,7 +257,7 @@ function ClaimPreviewTile(props: Props) {
</div>
) : (
<React.Fragment>
<UriIndicator uri={uri} link hideAnonymous>
<UriIndicator focusable={false} uri={uri} link hideAnonymous>
<ChannelThumbnail uri={channelUri} xsmall />
</UriIndicator>

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

@ -7,9 +7,14 @@ import {
makeSelectClaimForUri,
makeSelectClaimIsMine,
} from 'lbry-redux';
import {
selectPlayingUri,
} from 'redux/selectors/content';
const select = (state, props) => {
const claim = makeSelectClaimForUri(props.uri)(state);
const playingUri = selectPlayingUri(state);
const playingUrl = playingUri && playingUri.uri;
const claim = makeSelectClaimForUri(playingUrl)(state);
const url = claim && claim.permanent_url;
return {

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,13 +65,14 @@ export function CommentCreate(props: Props) {
isReply,
parentId,
activeChannelClaim,
bottom,
livestream,
embed,
claimIsMine,
sendTip,
doToast,
} = props;
const buttonref: ElementRef<any> = React.useRef();
const buttonRef: ElementRef<any> = React.useRef();
const {
push,
location: { pathname },
@ -85,11 +88,8 @@ export function CommentCreate(props: Props) {
const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false);
const hasChannels = channels && channels.length;
const charCount = commentValue.length;
const [activeTab, setActiveTab] = React.useState('');
const [tipError, setTipError] = React.useState();
const disabled = isSubmitting || !activeChannelClaim || !commentValue.length;
const [shouldDisableReviewButton, setShouldDisableReviewButton] = React.useState();
@ -106,9 +106,9 @@ 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();
buttonRef.current.click();
}
}
@ -147,8 +147,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) {
@ -184,8 +182,7 @@ export function CommentCreate(props: Props) {
}
const sourceClaimId = claim.claim_id;
var roundedAmount = Math.round(tipAmount * 100) / 100;
const roundedAmount = Math.round(tipAmount * 100) / 100;
Lbryio.call(
'customer',
@ -204,8 +201,6 @@ export function CommentCreate(props: Props) {
'post'
)
.then((customerTipResponse) => {
console.log(customerTipResponse);
const paymentIntendId = customerTipResponse.payment_intent_id;
handleCreateComment(null, paymentIntendId, stripeEnvironment);
@ -225,14 +220,14 @@ export function CommentCreate(props: Props) {
// handleCreateComment(null);
})
.catch(function (error) {
var displayError = 'Sorry, there was an error in processing your payment!';
if (error.message !== 'payment intent failed to confirm') {
displayError = error.message;
}
doToast({ message: displayError, isError: true });
.catch((error) => {
doToast({
message:
error.message !== 'payment intent failed to confirm'
? error.message
: 'Sorry, there was an error in processing your payment!',
isError: true,
});
});
}
}
@ -261,7 +256,7 @@ export function CommentCreate(props: Props) {
}
}
})
.catch((e) => {
.catch(() => {
setIsSubmitting(false);
setCommentFailure(true);
});
@ -280,6 +275,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);
@ -306,7 +306,7 @@ export function CommentCreate(props: Props) {
<div className="comment__create">
<div className="comment__sc-preview">
<CreditAmount
className="comment__scpreview-amount"
className="comment__sc-preview-amount"
isFiat={activeTab === TAB_FIAT}
amount={tipAmount}
size={activeTab === TAB_LBC ? 18 : 2}
@ -344,6 +344,7 @@ export function CommentCreate(props: Props) {
className={classnames('comment__create', {
'comment__create--reply': isReply,
'comment__create--nested-reply': isNested,
'comment__create--bottom': bottom,
})}
>
<FormField
@ -398,7 +399,7 @@ export function CommentCreate(props: Props) {
) : (
<>
<Button
ref={buttonref}
ref={buttonRef}
button="primary"
disabled={disabled}
type="submit"

View file

@ -44,7 +44,15 @@ const buildIcon = (iconStrokes: React$Node, customSvgValues = {}) =>
export const icons = {
// The LBRY icon is different from the base icon set so don't use buildIcon()
[ICONS.LBRY]: (props: IconProps) => (
<svg stroke="currentColor" fill="currentColor" x="0px" y="0px" viewBox="0 0 322 254" className="icon lbry-icon">
<svg
{...props}
stroke="currentColor"
fill="currentColor"
x="0px"
y="0px"
viewBox="0 0 322 254"
className="icon lbry-icon"
>
<path d="M296,85.9V100l-138.8,85.3L52.6,134l0.2-7.9l104,51.2L289,96.1v-5.8L164.2,30.1L25,116.2v38.5l131.8,65.2 l137.6-84.4l3.9,6l-141.1,86.4L18.1,159.1v-46.8l145.8-90.2C163.9,22.1,296,85.9,296,85.9z" />
<path d="M294.3,150.9l2-12.6l-12.2-2.1l0.8-4.9l17.1,2.9l-2.8,17.5L294.3,150.9L294.3,150.9z" />
</svg>

View file

@ -73,6 +73,7 @@ class IconComponent extends React.PureComponent<Props> {
size={size || (sectionIcon ? 20 : 16)}
className={classnames(`icon icon--${icon}`, className, { 'color-override': iconColor })}
color={color}
aria-hidden
{...rest}
/>
);

View file

@ -11,6 +11,7 @@ type Props = {
claimIsMine: boolean,
downloading: boolean,
loading: boolean,
focusable: boolean,
fileInfo: ?FileListItem,
openModal: (id: string, { path: string }) => void,
pause: () => void,
@ -35,6 +36,7 @@ function FileDownloadLink(props: Props) {
uri,
claim,
buttonType,
focusable = true,
showLabel = false,
hideOpenButton = false,
hideDownloadStatus = false,
@ -91,6 +93,8 @@ function FileDownloadLink(props: Props) {
pause();
openModal(MODALS.CONFIRM_EXTERNAL_RESOURCE, { path: fileInfo.download_path, isMine: claimIsMine });
}}
aria-hidden={!focusable}
tabIndex={focusable ? 0 : -1}
/>
);
}
@ -105,6 +109,8 @@ function FileDownloadLink(props: Props) {
icon={ICONS.DOWNLOAD}
label={showLabel ? label : null}
onClick={handleDownload}
aria-hidden={!focusable}
tabIndex={focusable ? 0 : -1}
/>
);
}

View file

@ -23,7 +23,7 @@ type Props = {
index: number,
length: number,
location: { pathname: string },
push: string => void,
push: (string) => void,
},
};
@ -43,7 +43,7 @@ function FileDrop(props: Props) {
const navigationTimer = React.useRef(null);
// Gets respective icon given a mimetype
const getFileIcon = type => {
const getFileIcon = (type) => {
// Not all files have a type
if (!type) return ICONS.FILE;
// Detect common types
@ -77,10 +77,13 @@ function FileDrop(props: Props) {
}, [navigateToPublish]);
// Handle file selection
const handleFileSelected = React.useCallback((selectedFile) => {
updatePublishForm({ filePath: selectedFile });
hideDropArea();
}, [updatePublishForm, hideDropArea]);
const handleFileSelected = React.useCallback(
(selectedFile) => {
updatePublishForm({ filePath: selectedFile });
hideDropArea();
},
[updatePublishForm, hideDropArea]
);
// Clear timers when unmounted
React.useEffect(() => {
@ -114,12 +117,12 @@ function FileDrop(props: Props) {
React.useEffect(() => {
if (dropData && !files.length && (!modal || modal.id !== MODALS.FILE_SELECTION)) {
getTree(dropData)
.then(entries => {
.then((entries) => {
if (entries && entries.length) {
setFiles(entries);
}
})
.catch(error => {
.catch((error) => {
setError(error || true);
});
}
@ -146,7 +149,7 @@ function FileDrop(props: Props) {
const show = files.length === 1 || (!target && drag && (!modal || modal.id !== MODALS.FILE_SELECTION));
return (
<div className={classnames('file-drop', show && 'file-drop--show')}>
<div aria-hidden={!show} className={classnames('file-drop', show && 'file-drop--show')}>
<div className={classnames('card', 'file-drop__area')}>
<Icon size={64} icon={icon} className={'main-icon'} />
<p>{target ? target.name : __(`Drop here to publish!`)} </p>

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

@ -7,15 +7,16 @@ import useLazyLoading from 'effects/use-lazy-loading';
type Props = {
thumb: string,
children?: Node,
className?: string,
};
const Thumb = (props: Props) => {
const { thumb, children } = props;
const { thumb, children, className } = props;
const thumbnailRef = React.useRef(null);
useLazyLoading(thumbnailRef);
return (
<div ref={thumbnailRef} data-background-image={thumb} className={classnames('media__thumb')}>
<div ref={thumbnailRef} data-background-image={thumb} className={classnames('media__thumb', className)}>
{children}
</div>
);

View file

@ -53,7 +53,11 @@ function FileThumbnail(props: Props) {
const thumbnailUrl = url ? url.replace(/'/g, "\\'") : '';
if (hasResolvedClaim || thumbnailUrl) {
return <Thumb thumb={thumbnailUrl}>{children}</Thumb>;
return (
<Thumb thumb={thumbnailUrl} className={className}>
{children}
</Thumb>
);
}
return (
<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

@ -8,13 +8,14 @@ import { COLLECTIONS_CONSTS } from 'lbry-redux';
type Props = {
uri: string,
claim: StreamClaim,
focusable: boolean,
hasClaimInWatchLater: boolean,
doToast: ({ message: string }) => void,
doCollectionEdit: (string, any) => void,
};
function FileWatchLaterLink(props: Props) {
const { claim, hasClaimInWatchLater, doToast, doCollectionEdit } = props;
const { claim, hasClaimInWatchLater, doToast, doCollectionEdit, focusable = true } = props;
const buttonRef = useRef();
let isHovering = useHover(buttonRef);
@ -51,6 +52,8 @@ function FileWatchLaterLink(props: Props) {
(isHovering ? ICONS.COMPLETED : ICONS.TIME)
}
onClick={(e) => handleWatchLater(e)}
aria-hidden={!focusable}
tabIndex={focusable ? 0 : -1}
/>
);
}

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';
@ -15,6 +15,8 @@ import { useIsMobile } from 'effects/use-screensize';
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';
@ -234,6 +236,7 @@ const Header = (props: Props) => {
) : (
<>
<div className="header__navigation">
<SkipNavigationButton />
{!authHeader && (
<span style={{ position: 'relative' }}>
<Button
@ -244,6 +247,7 @@ const Header = (props: Props) => {
}
className="header__navigation-item menu__title header__navigation-item--icon"
icon={ICONS.MENU}
aria-expanded={sidebarOpen}
onClick={() => setSidebarOpen(!sidebarOpen)}
>
{isAbsoluteSideNavHidden && isMobile && notificationsEnabled && <NotificationBubble />}
@ -251,14 +255,8 @@ const Header = (props: Props) => {
</span>
)}
<Button
aria-label={__('Home')}
className="header__navigation-item header__navigation-item--lbry"
// @if TARGET='app'
label={'LBRY'}
// @endif
// @if TARGET='web'
label={LOGO_TITLE} // eslint-disable-line
// @endif
icon={ICONS.LBRY}
onClick={() => {
if (history.location.pathname === '/') window.location.reload();
}}
@ -268,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

@ -193,7 +193,7 @@ export default function Notification(props: Props) {
<div className="notification__menu">
<Menu>
<MenuButton onClick={(e) => e.stopPropagation()}>
<MenuButton className={'menu-button notification__menu-button'} onClick={(e) => e.stopPropagation()}>
<Icon size={18} icon={ICONS.MORE_VERTICAL} />
</MenuButton>
<MenuList className="menu__list">

View file

@ -109,6 +109,7 @@ function Page(props: Props) {
/>
)}
<main
id={'main-content'}
className={classnames(MAIN_CLASS, className, {
'main--full-width': fullWidthPage,
'main--auth-page': authPage,

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

@ -13,11 +13,12 @@ type Props = {
label: ?string,
reward: Reward,
button: ?boolean,
disabled: boolean,
claimReward: (Reward) => void,
};
const RewardLink = (props: Props) => {
const { reward, claimReward, label, isPending, button } = props;
const { reward, claimReward, label, isPending, button, disabled = false } = props;
let displayLabel = label;
if (isPending) {
displayLabel = __('Claiming...');
@ -34,7 +35,7 @@ const RewardLink = (props: Props) => {
return !reward ? null : (
<Button
button={button ? 'primary' : 'link'}
disabled={isPending}
disabled={disabled || isPending}
label={<LbcMessage>{displayLabel}</LbcMessage>}
aria-label={displayLabel}
onClick={() => {

View file

@ -23,10 +23,11 @@ type Props = {
claim_code: string,
},
user: User,
disabled: boolean,
};
const RewardTile = (props: Props) => {
const { reward, openRewardCodeModal, openSetReferrerModal, user } = props;
const { reward, openRewardCodeModal, openSetReferrerModal, user, disabled = false } = props;
const referrerSet = user && user.invited_by_id;
const claimed = !!reward.transaction_id;
const customActionsRewards = [rewards.TYPE_REFERRAL, rewards.TYPE_REFEREE];
@ -38,18 +39,25 @@ const RewardTile = (props: Props) => {
actions={
<div className="section__actions">
{reward.reward_type === rewards.TYPE_GENERATED_CODE && (
<Button button="primary" onClick={openRewardCodeModal} label={__('Enter Code')} />
<Button button="primary" onClick={openRewardCodeModal} label={__('Enter Code')} disabled={disabled} />
)}
{reward.reward_type === rewards.TYPE_REFERRAL && (
<Button button="primary" navigate="/$/invite" label={__('Go To Invites')} />
<Button
button="primary"
navigate="/$/invite"
label={__('Go To Invites')}
aria-hidden={disabled}
tabIndex={disabled ? -1 : 0}
/>
)}
{reward.reward_type === rewards.TYPE_REFEREE && (
<>
{referrerSet && <RewardLink button reward_type={reward.reward_type} />}
{referrerSet && <RewardLink button reward_type={reward.reward_type} disabled={disabled} />}
<Button
button={referrerSet ? 'link' : 'primary'}
onClick={openSetReferrerModal}
label={referrerSet ? __('Change Inviter') : __('Set Inviter')}
disabled={disabled}
/>
</>
)}
@ -59,7 +67,7 @@ const RewardTile = (props: Props) => {
<Icon icon={ICONS.COMPLETED} /> {__('Reward claimed.')}
</span>
) : (
<RewardLink button claim_code={reward.claim_code} />
<RewardLink button claim_code={reward.claim_code} disabled={disabled} />
))}
</div>
}

View file

@ -331,6 +331,7 @@ function SideNavigation(props: Props) {
>
{!isOnFilePage && (
<nav
aria-label={'Sidebar'}
className={classnames('navigation', {
'navigation--micro': microNavigation,
// @if TARGET='app'

View file

@ -0,0 +1,22 @@
// @flow
import React from 'react';
import Button from 'component/button';
// Allow screen reader users ( or keyboard navigation )
// to jump to main content
export default function SkipNavigationButton() {
const skipNavigation = (e) => {
// Match any focusable element
const focusableElementQuery = `
#main-content [tabindex]:not([tabindex="-1"]):not(:disabled),
#main-content a:not([aria-hidden]):not([tabindex="-1"]):not(:disabled),
#main-content button:not([aria-hidden]):not([tabindex="-1"]):not(:disabled)
`;
// Find first focusable element
const element = document.querySelector(focusableElementQuery);
// Trigger focus to skip navigation
if (element && element.focus) {
element.focus();
}
};
return <Button className={'skip-button'} onClick={skipNavigation} label={__('Skip Navigation')} button={'link'} />;
}

View file

@ -19,6 +19,7 @@ type Props = {
inline: boolean,
external?: boolean,
className?: string,
focusable: boolean,
};
class UriIndicator extends React.PureComponent<Props> {
@ -45,8 +46,9 @@ class UriIndicator extends React.PureComponent<Props> {
claim,
children,
inline,
hideAnonymous = false,
focusable = true,
external = false,
hideAnonymous = false,
className,
} = this.props;
@ -86,7 +88,13 @@ class UriIndicator extends React.PureComponent<Props> {
if (children) {
return (
<Button className={className} target={external ? '_blank' : undefined} navigate={channelLink}>
<Button
aria-hidden={!focusable}
tabIndex={focusable ? 0 : -1}
className={className}
target={external ? '_blank' : undefined}
navigate={channelLink}
>
{children}
</Button>
);
@ -96,6 +104,8 @@ class UriIndicator extends React.PureComponent<Props> {
className={classnames(className, 'button--uri-indicator')}
navigate={channelLink}
target={external ? '_blank' : undefined}
aria-hidden={!focusable}
tabIndex={focusable ? 0 : -1}
>
{inner}
</Button>

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

@ -1,6 +1,6 @@
// @flow
import React from 'react';
import formatMediaDuration from 'util/formatMediaDuration';
type Props = {
claim: ?StreamClaim,
className?: string,
@ -9,19 +9,11 @@ type Props = {
function VideoDuration(props: Props) {
const { claim, className } = props;
const video = claim && claim.value && (claim.value.video || claim.value.audio);
const media = claim && claim.value && (claim.value.video || claim.value.audio);
let duration;
if (video && video.duration) {
if (media && media.duration) {
// $FlowFixMe
let date = new Date(null);
date.setSeconds(video.duration);
let timeString = date.toISOString().substr(11, 8);
if (timeString.startsWith('00:')) {
timeString = timeString.substr(3);
}
duration = timeString;
duration = formatMediaDuration(media.duration);
}
return duration ? <span className={className}>{duration}</span> : null;

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

@ -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

@ -35,7 +35,7 @@ import {
doAuthTokenRefresh,
} from 'util/saved-passwords';
import { X_LBRY_AUTH_TOKEN } from 'constants/token';
import { LBRY_WEB_API, DEFAULT_LANGUAGE, LBRY_API_URL } from 'config';
import { LBRY_WEB_API, DEFAULT_LANGUAGE, LBRY_API_URL, LBRY_WEB_PUBLISH_API } from 'config';
// Import 3rd-party styles before ours for the current way we are code-splitting.
import 'scss/third-party.scss';
@ -43,8 +43,12 @@ import 'scss/third-party.scss';
// Import our app styles
// If a style is not necessary for the initial page load, it should be removed from `all.scss`
// and loaded dynamically in the component that consumes it
// @if TARGET='app'
import 'scss/all.scss';
// @endif
// @if TARGET='web'
import 'web/theme';
// @endif
// @if TARGET='web'
// These overrides can't live in web/ because they need to use the same instance of `Lbry`
import apiPublishCallViaWeb from 'web/setup/publish';
@ -65,12 +69,11 @@ if (process.env.SDK_API_URL) {
}
let sdkAPIHost = process.env.SDK_API_HOST || process.env.SDK_API_URL;
// @if TARGET='web'
sdkAPIHost = LBRY_WEB_API;
// @endif
export const SDK_API_PATH = `${sdkAPIHost}/api/v1`;
const proxyURL = `${SDK_API_PATH}/proxy`;
const publishURL = LBRY_WEB_PUBLISH_API; // || `${SDK_API_PATH}/proxy`;
Lbry.setDaemonConnectionString(proxyURL);
@ -80,7 +83,7 @@ Lbry.setOverride(
new Promise((resolve, reject) => {
apiPublishCallViaWeb(
apiCall,
proxyURL,
publishURL,
Lbry.getApiRequestHeaders() && Object.keys(Lbry.getApiRequestHeaders()).includes(X_LBRY_AUTH_TOKEN)
? Lbry.getApiRequestHeaders()[X_LBRY_AUTH_TOKEN]
: '',

View file

@ -14,8 +14,8 @@ type ModalProps = {
abortButtonLabel?: string,
confirmButtonDisabled?: boolean,
abortButtonDisabled?: boolean,
onConfirmed?: any => any,
onAborted?: any => any,
onConfirmed?: (any) => any,
onAborted?: (any) => any,
className?: string,
children?: React.Node,
extraContent?: React.Node,
@ -52,7 +52,13 @@ export function Modal(props: ModalProps) {
>
{title && <h1 className="card__title card__title--deprecated">{title}</h1>}
{type === 'card' && (
<Button iconSize={isMobile ? 24 : undefined} button="close" icon={ICONS.REMOVE} onClick={onAborted} />
<Button
iconSize={isMobile ? 24 : undefined}
button="close"
aria-label={__('Close')}
icon={ICONS.REMOVE}
onClick={onAborted}
/>
)}
{children}
{type === 'custom' || type === 'card' ? null : ( // custom modals define their own buttons

View file

@ -24,15 +24,20 @@ import ClaimMenuList from 'component/claimMenuList';
import OptimizedImage from 'component/optimizedImage';
import Yrbl from 'component/yrbl';
import I18nMessage from 'component/i18nMessage';
import TruncatedText from 'component/common/truncated-text';
// $FlowFixMe cannot resolve ...
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,
@ -83,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;
@ -143,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:
@ -165,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]);
@ -225,7 +230,9 @@ function ChannelPage(props: Props) {
<div className="channel__primary-info">
<ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} allowGifs hideStakedIndicator />
<h1 className="channel__title">
{title || '@' + channelName}
<TruncatedText lines={2} showTooltip>
{title || '@' + channelName}
</TruncatedText>
<ChannelStakedIndicator uri={uri} large />
</h1>
<div className="channel__meta">
@ -241,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}
@ -284,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

@ -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

@ -102,6 +102,8 @@ class RewardsPage extends PureComponent<Props> {
}
renderCustomRewardCode() {
const { user } = this.props;
const isNotEligible = !user || !user.primary_email || !user.has_verified_email || !user.is_reward_approved;
return (
<RewardTile
key={REWARD_TYPES.TYPE_GENERATED_CODE}
@ -110,6 +112,7 @@ class RewardsPage extends PureComponent<Props> {
reward_title: __('Custom Code'),
reward_description: __('Are you a supermodel or rockstar that received a custom reward code? Claim it here.'),
}}
disabled={isNotEligible}
/>
);
}
@ -155,12 +158,13 @@ class RewardsPage extends PureComponent<Props> {
return (
<div
aria-hidden={isNotEligible}
className={classnames('card__list', {
'card--disabled': isNotEligible,
})}
>
{rewards.map((reward) => (
<RewardTile key={reward.claim_code} reward={reward} />
<RewardTile disabled={isNotEligible} key={reward.claim_code} reward={reward} />
))}
{this.renderCustomRewardCode()}
</div>

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

@ -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

@ -236,6 +236,7 @@
&:focus {
box-shadow: none;
background-color: var(--color-button-alt-bg);
}
}

View file

@ -1,5 +1,6 @@
$cover-z-index: 0;
$metadata-z-index: 1;
$actions-z-index: 2;
.channel-cover {
position: relative;
@ -200,7 +201,7 @@ $metadata-z-index: 1;
top: 0;
right: var(--spacing-m);
margin-top: var(--spacing-m);
z-index: $metadata-z-index;
z-index: $actions-z-index;
flex-wrap: wrap;
font-size: var(--font-base);

View file

@ -79,7 +79,7 @@
&:hover {
.claim__menu-button {
display: block;
opacity: 1;
}
}
}
@ -269,7 +269,7 @@
.claim-preview__title {
font-weight: var(--font-weight-bold);
font-size: var(--font-body);
margin-right: auto;
margin-right: var(--spacing-l);
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
@ -475,7 +475,7 @@
cursor: pointer;
.claim__menu-button {
display: block;
opacity: 1;
}
.collection-preview__overlay-thumbs {
opacity: 1;
@ -531,7 +531,7 @@
.claim-tile__title {
position: relative;
padding: var(--spacing-s);
padding-right: var(--spacing-l);
padding-right: var(--spacing-xl);
padding-bottom: 0;
margin-bottom: var(--spacing-s);
@ -540,14 +540,8 @@
font-size: var(--font-small);
min-height: 2rem;
.claim__menu-button {
right: 0.2rem;
top: var(--spacing-s);
}
@media (min-width: $breakpoint-small) {
min-height: 2.5rem;
padding-right: var(--spacing-m);
}
}
@ -745,29 +739,38 @@
}
}
.claim__menu-button {
position: absolute;
top: var(--spacing-xs);
right: var(--spacing-xs);
.claim-tile__header {
position: relative;
.icon {
stroke: var(--color-text);
}
@media (min-width: $breakpoint-small) {
&:not([aria-expanded='true']) {
display: none;
}
.claim__menu-button {
right: 0.2rem;
}
}
.claim__menu-button--inline {
position: relative;
display: block;
right: auto;
top: auto;
@extend .button--alt;
padding: 0 var(--spacing-xxs);
.menu__button {
&.claim__menu-button {
position: absolute;
top: var(--spacing-xs);
right: var(--spacing-xs);
}
&.claim__menu-button--inline {
position: relative;
@extend .button--alt;
width: var(--height-button);
padding: 0;
border-radius: var(--border-radius);
align-self: flex-end;
}
}
@media (min-width: $breakpoint-small) {
.claim-preview--tile:not(:hover),
.claim-preview__wrapper:not(:hover) {
.claim__menu-button:not(:focus):not([aria-expanded='true']) {
opacity: 0;
}
}
}
.claim-preview__overlay-properties {

View file

@ -127,7 +127,7 @@ $thumbnailWidthSmall: 1rem;
margin-top: var(--spacing-xxs);
}
.comment__scpreview-amount {
.comment__sc-preview-amount {
margin-right: var(--spacing-m);
font-size: var(--font-large);
}
@ -263,14 +263,6 @@ $thumbnailWidthSmall: 1rem;
.comment__menu {
align-self: flex-end;
line-height: 1;
button {
border-radius: var(--border-radius);
&:focus {
@include linkFocus;
}
}
}
.comment__char-count {
@ -295,14 +287,6 @@ $thumbnailWidthSmall: 1rem;
}
}
.comment__menu-icon--hovering {
stroke: var(--color-comment-menu-hovering);
}
.comment__menu-icon {
stroke: var(--color-comment-menu);
}
.comment__menu-list {
box-shadow: var(--card-box-shadow);
border-radius: var(--card-radius);

View file

@ -1,4 +1,4 @@
@import 'init/mixins';
@import '../init/mixins';
input,
textarea,

View file

@ -9,6 +9,25 @@
-webkit-user-select: none;
-webkit-app-region: drag;
.skip-button {
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
overflow: hidden;
margin-right: var(--spacing-l);
&:focus {
opacity: 1;
position: relative;
overflow: unset;
width: inherit;
height: inherit;
}
}
& > * {
user-select: none;
}
@ -58,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);
@ -132,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

@ -318,7 +318,7 @@ $discussion-header__height: 3rem;
padding-bottom: var(--spacing-xxs);
.markdown-preview {
p {
word-break: break-all;
word-break: break-word;
}
}
}

View file

@ -218,14 +218,6 @@ $contentMaxWidth: 60rem;
.icon {
stroke: var(--color-text-help);
}
button {
border-radius: var(--border-radius);
&:focus {
@include linkFocus;
}
}
}
.notification__filter {

View file

@ -44,10 +44,25 @@
}
.menu__button {
display: flex;
justify-content: center;
align-items: center;
border-radius: 100%;
padding: 0.3rem;
.icon {
stroke: var(--color-menu);
}
.comment__menu-icon--hovering {
}
&:focus,
&:hover {
opacity: 1;
background-color: var(--color-button-alt-bg);
.icon {
border-radius: var(--border-radius);
background-color: var(--color-card-background-highlighted);
stroke: var(--color-menu-hovering);
}
}
}

View file

@ -124,10 +124,10 @@
--color-menu-background: var(--color-header-background);
--color-menu-background--active: var(--color-card-background-highlighted);
--color-menu-icon: var(--color-navigation-link);
--color-menu: var(--color-gray-3);
--color-menu-hovering: var(--color-gray-6);
// Comments
--color-comment-menu: var(--color-gray-3);
--color-comment-menu-hovering: var(--color-gray-6);
--color-comment-highlighted: #fff2d9;
--color-comment-threadline: var(--color-gray-1);
--color-comment-threadline-hover: var(--color-gray-4);

View file

@ -90,10 +90,10 @@
--color-menu-background: var(--color-header-background);
--color-menu-background--active: var(--color-gray-7);
--color-menu-icon: var(--color-gray-4);
--color-menu: var(--color-gray-5);
--color-menu-hovering: var(--color-gray-2);
// Comments
--color-comment-menu: var(--color-gray-5);
--color-comment-menu-hovering: var(--color-gray-2);
--color-comment-threadline: #434b54;
--color-comment-threadline-hover: var(--color-gray-4);
--color-comment-highlighted: #484734;

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

@ -0,0 +1,32 @@
import DateTime from 'component/dateTime';
export function formatClaimPreviewTitle(title, channelTitle, date, mediaDuration) {
// Aria-label value for claim preview
let ariaDate = date ? DateTime.getTimeAgoStr(date) : null;
let ariaLabelData = title;
if (mediaDuration) {
if (ariaDate) {
ariaLabelData = __('%title% by %channelTitle% %ariaDate%, %mediaDuration%', {
title,
channelTitle,
ariaDate,
mediaDuration,
});
} else {
ariaLabelData = __('%title% by %channelTitle%, %mediaDuration%', {
title,
channelTitle,
mediaDuration,
});
}
} else {
if (ariaDate) {
ariaLabelData = __('%title% by %channelTitle% %ariaDate%', { title, channelTitle, ariaDate });
} else {
ariaLabelData = __('%title% by %channelTitle%', { title, channelTitle });
}
}
return ariaLabelData;
}

View file

@ -0,0 +1,24 @@
import moment from 'moment';
export default function formatMediaDuration(duration = 0, config) {
const options = {
screenReader: false,
...config,
};
// Optimize for screen readers
if (options.screenReader) {
return moment.utc(moment.duration(duration, 'seconds').asMilliseconds()).format('HH:mm:ss');
}
// Normal format
let date = new Date(null);
date.setSeconds(duration);
let timeString = date.toISOString().substr(11, 8);
if (timeString.startsWith('00:')) {
timeString = timeString.substr(3);
}
return timeString;
}

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>
);

70
web/scss/lbrytv.scss Normal file
View file

@ -0,0 +1,70 @@
@charset "utf-8";
@import '../../ui/scss/init/reset';
@import '../../ui/scss/init/vars';
@import '../../ui/scss/init/mixins';
@import '../../ui/scss/init/gui';
@import '../../ui/scss/init/base-theme';
@import 'themes/lbrytv/light.scss';
@import 'themes/lbrytv/dark.scss';
@import '../../ui/scss/component/ads';
@import '../../ui/scss/component/animation';
@import '../../ui/scss/component/badge';
@import '../../ui/scss/component/block-list';
@import '../../ui/scss/component/button';
@import '../../ui/scss/component/card';
@import '../../ui/scss/component/channel';
@import '../../ui/scss/component/claim-list';
@import '../../ui/scss/component/collection';
@import '../../ui/scss/component/comments';
@import '../../ui/scss/component/content';
@import '../../ui/scss/component/dat-gui';
@import '../../ui/scss/component/embed-player';
@import '../../ui/scss/component/expandable';
@import '../../ui/scss/component/expanding-details';
@import '../../ui/scss/component/file-drop';
@import '../../ui/scss/component/file-list';
@import '../../ui/scss/component/file-properties';
@import '../../ui/scss/component/file-render';
@import '../../ui/scss/component/footer';
@import '../../ui/scss/component/form-field';
@import '../../ui/scss/component/header';
@import '../../ui/scss/component/icon';
@import '../../ui/scss/component/main';
@import '../../ui/scss/component/markdown-editor';
@import '../../ui/scss/component/markdown-preview';
@import '../../ui/scss/component/media';
@import '../../ui/scss/component/menu-button';
@import '../../ui/scss/component/modal';
@import '../../ui/scss/component/nag';
@import '../../ui/scss/component/navigation';
@import '../../ui/scss/component/notification';
@import '../../ui/scss/component/nudge';
@import '../../ui/scss/component/pagination';
@import '../../ui/scss/component/post';
@import '../../ui/scss/component/purchase';
@import '../../ui/scss/component/placeholder';
@import '../../ui/scss/component/progress';
@import '../../ui/scss/component/search';
@import '../../ui/scss/component/claim-search';
@import '../../ui/scss/component/section';
@import '../../ui/scss/component/share';
@import '../../ui/scss/component/snack-bar';
@import '../../ui/scss/component/spinner';
@import '../../ui/scss/component/splash';
@import '../../ui/scss/component/status-bar';
@import '../../ui/scss/component/superchat';
@import '../../ui/scss/component/syntax-highlighter';
@import '../../ui/scss/component/table';
@import '../../ui/scss/component/livestream';
@import '../../ui/scss/component/tabs';
@import '../../ui/scss/component/tooltip';
@import '../../ui/scss/component/txo-list';
@import '../../ui/scss/component/videojs';
@import '../../ui/scss/component/tags';
@import '../../ui/scss/component/wunderbar';
@import '../../ui/scss/component/yrbl';
@import '../../ui/scss/component/empty';
@import '../../ui/scss/component/stripe-card';
@import '../../ui/scss/component/wallet-tip-send';

70
web/scss/odysee.scss Normal file
View file

@ -0,0 +1,70 @@
@charset "utf-8";
@import 'themes/odysee/init/reset';
@import 'themes/odysee/init/vars';
@import 'themes/odysee/init/mixins';
@import 'themes/odysee/init/gui';
@import 'themes/odysee/init/base-theme';
@import 'themes/odysee/light.scss';
@import 'themes/odysee/dark.scss';
@import '../../ui/scss/component/ads';
@import '../../ui/scss/component/animation';
@import '../../ui/scss/component/badge';
@import '../../ui/scss/component/block-list';
@import '../../ui/scss/component/button';
@import '../../ui/scss/component/card';
@import '../../ui/scss/component/channel';
@import '../../ui/scss/component/claim-list';
@import '../../ui/scss/component/collection';
@import '../../ui/scss/component/comments';
@import '../../ui/scss/component/content';
@import '../../ui/scss/component/dat-gui';
@import '../../ui/scss/component/embed-player';
@import '../../ui/scss/component/expandable';
@import '../../ui/scss/component/expanding-details';
@import '../../ui/scss/component/file-drop';
@import '../../ui/scss/component/file-list';
@import '../../ui/scss/component/file-properties';
@import '../../ui/scss/component/file-render';
@import '../../ui/scss/component/footer';
@import '../../ui/scss/component/form-field';
@import '../../ui/scss/component/header';
@import '../../ui/scss/component/icon';
@import '../../ui/scss/component/main';
@import '../../ui/scss/component/markdown-editor';
@import '../../ui/scss/component/markdown-preview';
@import '../../ui/scss/component/media';
@import '../../ui/scss/component/menu-button';
@import '../../ui/scss/component/modal';
@import '../../ui/scss/component/nag';
@import '../../ui/scss/component/navigation';
@import '../../ui/scss/component/notification';
@import '../../ui/scss/component/nudge';
@import '../../ui/scss/component/pagination';
@import '../../ui/scss/component/post';
@import '../../ui/scss/component/purchase';
@import '../../ui/scss/component/placeholder';
@import '../../ui/scss/component/progress';
@import '../../ui/scss/component/search';
@import '../../ui/scss/component/claim-search';
@import '../../ui/scss/component/section';
@import '../../ui/scss/component/share';
@import '../../ui/scss/component/snack-bar';
@import '../../ui/scss/component/spinner';
@import '../../ui/scss/component/splash';
@import '../../ui/scss/component/status-bar';
@import '../../ui/scss/component/superchat';
@import '../../ui/scss/component/syntax-highlighter';
@import '../../ui/scss/component/table';
@import '../../ui/scss/component/livestream';
@import '../../ui/scss/component/tabs';
@import '../../ui/scss/component/tooltip';
@import '../../ui/scss/component/txo-list';
@import '../../ui/scss/component/videojs';
@import '../../ui/scss/component/tags';
@import '../../ui/scss/component/wunderbar';
@import '../../ui/scss/component/yrbl';
@import '../../ui/scss/component/empty';
@import '../../ui/scss/component/stripe-card';
@import '../../ui/scss/component/wallet-tip-send';

View file

@ -0,0 +1,159 @@
[theme='dark'] {
// Color overrides
--color-primary: #2bbb90;
--color-primary-alt: #3e675d;
--color-primary-alt-2: #065f46;
--color-primary-alt-3: #34e5b0;
--color-secondary: #204166;
--color-secondary-alt: #dbeafe;
--color-secondary-alt-2: #bfdbfe;
--color-secondary-alt-3: #2c5c8c;
// Structure
--color-background: var(--color-gray-9);
--color-background-overlay: #21252999;
--color-border: #333338;
--color-card-background: var(--color-gray-8);
--color-card-background-highlighted: var(--color-gray-7);
// Text
--color-text: var(--color-white);
--color-text-subtitle: var(--color-gray-4);
--color-text-empty: var(--color-text-subtitle);
--color-text-help: #bbbbbb;
--color-text-warning: #212529;
--color-text-warning--background: var(--lbry-yellow-1);
--color-text-error: #f87171;
--color-error: #61373f;
// Tags (words)
--color-tag-words: var(--color-text);
--color-tag-words-bg: var(--color-gray-5);
--color-tag-words-hover: var(--color-white);
--color-tag-words-bg-hover: var(--color-gray-4);
// Header
--color-header-background: var(--color-gray-8);
--color-header-button: var(--color-gray-6);
--color-header-button-hover: var(--color-gray-6);
--color-header-button-active: var(--color-gray-6);
// Button
--color-button-primary-bg: var(--color-primary-alt);
--color-button-primary-bg-hover: var(--color-primary-alt-2);
--color-button-primary-text: var(--color-gray-2);
--color-button-primary-hover-text: var(--color-primary-alt);
--color-button-secondary-bg: var(--color-secondary);
--color-button-secondary-border: var(--color-secondary);
--color-button-secondary-bg-hover: var(--color-secondary-alt-3);
--color-button-secondary-text: var(--color-gray-2);
--color-button-alt-bg: var(--color-gray-7);
--color-button-alt-bg-hover: var(--color-gray-6);
--color-button-alt-text: var(--color-gray-1);
--color-button-border: var(--color-gray-5);
--color-button-toggle-text: var(--color-gray-1);
--color-link: var(--color-primary-alt-3);
--color-link-hover: var(--color-text);
--color-link-focus-bg: var(--color-gray-7);
// Input
--color-input: var(--color-white);
--color-input-label: var(--color-gray-3);
--color-input-placeholder: var(--color-gray-1);
--color-input-bg: var(--color-header-button);
--color-input-bg-copyable: var(--color-gray-6);
--color-input-border: var(--color-border);
--color-input-border-active: var(--color-secondary);
--color-input-toggle: var(--color-primary-alt-3);
--color-input-toggle-bg: var(--color-input-bg);
--color-input-toggle-bg-hover: var(--color-secondary);
--color-input-bg-selected: var(--color-primary-alt);
--color-input-prefix-bg: var(--color-gray-5);
--color-input-prefix-border: var(--color-gray-4);
--select-toggle-background: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23ffffff'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
// Navigation
--color-navigation-icon: var(--color-gray-4);
--color-navigation-link: var(--color-gray-4);
--color-navigation-active: var(--color-gray-7);
--color-navigation-active-text: var(--color-gray-3);
--color-navigation-hover: var(--color-gray-6);
--color-navigation-hover-text: var(--color-gray-3);
// Tags
--color-tag: var(--color-primary-alt-3);
--color-tag-bg: var(--color-gray-7);
--color-tag-hover: var(--color-white);
--color-tag-bg-hover: var(--color-primary-alt);
// Menu
--color-menu-background: var(--color-header-background);
--color-menu-background--active: var(--color-gray-7);
--color-menu-icon: var(--color-gray-4);
// Comments
--color-comment-menu: var(--color-gray-5);
--color-comment-menu-hovering: var(--color-gray-2);
--color-comment-threadline: #434b54;
--color-comment-threadline-hover: var(--color-gray-4);
--color-comment-highlighted: #484734;
// Snack
--color-snack-bg: var(--color-secondary);
// Superchat
--color-superchat-text: var(--color-black);
--color-superchat-text__light: var(--color-text);
--color-superchat: #fcd34d;
--color-superchat__light: #ef4e1647;
--color-superchat-2: #fde68a;
--color-superchat-3: #fef3c7;
--color-superchat-3__light: #58066087;
--color-superchat-4: #fffbeb;
// Other
--color-focus: #93c5fd50;
--color-nag: var(--color-orange);
--color-tab-text: var(--color-white);
--color-tabs-background: var(--color-card-background);
--color-tab-divider: var(--color-white);
--color-modal-background: var(--color-card-background);
--color-notice: #58563b;
--color-purchased: #ffd580;
--color-purchased-alt: var(--color-purchased);
--color-purchased-text: var(--color-gray-5);
--color-thumbnail-background: var(--color-gray-5);
--color-tooltip-bg: #2f3337;
--color-help-warning-bg: #d97706;
--color-help-warning-text: white;
--color-blockquote: var(--color-gray-5);
--color-placeholder-background: #4e5862;
--color-spinner-light: #5a6570;
--color-spinner-dark: #212529;
--color-login-graphic-background: var(--color-background);
// Editor
--color-editor-cursor: var(--color-text);
--color-editor-quote: #d3d3d3;
--color-editor-tag: #efbe5d;
--color-editor-attr: #68ccf9;
--color-editor-string: #ff8b6b;
--color-editor-inline-code-fg: #ce9178;
--color-editor-inline-code-fg-preview: #e8b692;
--color-editor-inline-code-bg: rgba(20, 68, 102, 0.3);
--color-editor-inline-code-bg-preview: #464b50;
--color-editor-selected: #264f78;
--color-editor-link: var(--color-link);
--color-editor-url: var(--color-editor-string);
--color-editor-hr: var(--color-editor-tag);
--color-editor-hr-preview: #a0a0a0;
// Ads
--color-ads-background: #475057;
--color-ads-text: #111;
--color-ads-link: var(--color-primary-alt);
// Scrollbar
--color-scrollbar-thumb-bg: rgba(255, 255, 255, 0.2);
--color-scrollbar-track-bg: transparent;
}

View file

@ -0,0 +1,2 @@
:root {
}

View file

@ -0,0 +1,755 @@
.file-page {
.file-page__video-container + .card,
.file-render + .card,
.content__cover + .card,
.card + .file-render,
.card + .file-page__video-container,
.card + .content__cover {
margin-top: var(--spacing-m);
}
.card + .file-render {
margin-top: var(--spacing-m);
}
.file-page__md {
.file-viewer--document {
margin-top: var(--spacing-l);
@media (min-width: $breakpoint-small) {
margin-top: var(--spacing-xl);
}
.button {
display: inline;
.button__content {
display: inline;
}
}
.claim-link {
.button {
display: block;
.button__content {
display: block;
}
}
}
}
.media__actions {
justify-content: center;
}
.file-page__secondary-content {
display: flex;
flex-direction: column;
padding: 0 var(--spacing-m);
}
}
> * {
width: 100%;
}
@media (max-width: $breakpoint-medium) {
flex-direction: column;
}
}
.file-render {
width: 100%;
height: 100%;
z-index: 1;
overflow: hidden;
max-height: var(--inline-player-max-height);
}
.file-render--video {
background-color: black;
&:after {
content: '';
position: absolute;
background-color: black;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
animation: fadeInFromBlack 2s ease;
opacity: 0;
pointer-events: none;
}
}
@keyframes fadeInFromBlack {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.file-render--embed {
border-radius: 0;
position: fixed;
max-height: none;
}
.file-render--img-container {
width: 100%;
aspect-ratio: 16 / 9;
}
.file-render--post-container {
min-height: 30vh;
}
.file-render__header {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.file-viewer {
width: 100%;
height: 100%;
iframe,
webview,
img {
width: 100%;
height: 100%;
object-fit: contain;
max-height: var(--inline-player-max-height);
}
video {
cursor: pointer;
}
.video-js.vjs-user-inactive.vjs-playing {
video {
cursor: none;
}
}
}
.file-render__viewer--comic {
position: relative;
overflow: hidden;
.comic-viewer {
width: 100%;
height: calc(100vh - var(--header-height) - var(--spacing-m) * 2);
max-height: var(--inline-player-max-height);
}
}
.file-viewer--iframe {
display: flex; /*this eliminates extra height from whitespace, if someone edits this with a better technique, tell Jeremy*/
/*
ideally iframes would dynamiclly grow, see <IframeReact> for a start at this
for now, since we don't know size, let's make as large as we can without being larger than available area
*/
iframe {
height: calc(100vh - var(--header-height) - var(--spacing-m) * 2);
}
}
.file-render__viewer--three {
position: relative;
overflow: hidden;
.three-viewer {
height: calc(100vh - var(--header-height) - var(--spacing-m) * 2);
max-height: var(--inline-player-max-height);
}
}
.file-viewer__overlay {
position: absolute;
left: auto;
right: auto;
height: 100%;
width: 100%;
z-index: 2;
color: var(--color-white);
font-size: var(--font-body); /* put back font size from videojs change*/
background-color: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--spacing-l);
@media (max-width: $breakpoint-small) {
font-size: var(--font-small);
}
.button--uri-indicator,
.button--link {
color: var(--color-white);
}
}
.content__viewer--floating {
.file-viewer__overlay-title,
.file-viewer__overlay-secondary {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
}
}
@media (max-width: $breakpoint-small) {
.file-viewer__overlay-title,
.file-viewer__overlay-secondary {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
}
}
.file-viewer__overlay-title {
text-align: center;
font-size: var(--font-large);
margin-bottom: var(--spacing-m);
}
.file-viewer__overlay-secondary {
color: var(--color-text-subtitle);
margin-bottom: var(--spacing-m);
}
.file-viewer__overlay-actions {
.button + .button {
margin-left: var(--spacing-m);
}
}
.file-viewer__overlay-logo {
height: 3.5rem;
width: 12rem;
display: flex;
align-items: center;
&:hover {
filter: drop-shadow(1px 2px 10px var(--color-gray-3));
}
@media (max-width: $breakpoint-small) {
margin-right: var(--spacing-m);
width: 2.5rem;
.button__label {
display: none;
}
}
}
.file-viewer__overlay-logo--videoend {
height: 3.5rem;
width: 12rem;
}
.file-viewer--is-playing:not(:hover) .file-viewer__embedded-header {
display: none;
}
.file-viewer__embedded-header {
position: absolute;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
top: 0;
opacity: 1;
z-index: 2;
height: 4rem;
padding-left: var(--spacing-m);
padding-right: var(--spacing-s);
font-size: var(--font-large);
overflow-x: hidden;
overflow-y: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
.button {
color: var(--color-white);
z-index: 2;
.button__label {
white-space: nowrap;
}
&:hover {
color: var(--color-white);
}
}
.credit-amount,
.icon--Key {
margin-right: var(--spacing-m);
}
@media (min-width: $breakpoint-small) {
padding: 0 var(--spacing-l);
}
}
.file-viewer__embedded-gradient {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 2;
background: linear-gradient(#000000, #00000000 70%);
height: 75px;
z-index: 1;
}
.file-viewer__embedded-title {
max-width: 75%;
z-index: 2;
}
.file-viewer__embedded-info {
display: flex;
align-items: center;
font-size: var(--font-small);
margin-left: var(--spacing-m);
white-space: nowrap;
position: relative;
overflow: hidden;
& > *:not(:last-child) {
margin-right: var(--spacing-s);
}
}
.file-render__content {
width: 100%;
height: 100%;
overflow: auto;
max-width: 100vw;
}
//
// Custom viewers live below here
// These either have custom class names that can't be changed or have styles that need to be overridden
//
// Code-viewer
.CodeMirror {
@extend .file-render__content;
.cm-invalidchar {
display: none;
}
.CodeMirror .CodeMirror-lines {
// is there really a .CodeMirror inside a .CodeMirror?
padding: var(--spacing-s) 0;
}
.CodeMirror-code {
@include font-sans;
letter-spacing: 0.1rem;
}
.CodeMirror-gutters {
background-color: var(--color-gray-1);
border-right: 1px solid var(--color-gray-4);
padding-right: var(--spacing-m);
}
.CodeMirror-line {
padding-left: var(--spacing-m);
}
.CodeMirror-linenumber {
color: var(--color-gray-5);
}
}
// ****************************************************************************
// Video
// ****************************************************************************
.video-js-parent {
height: 100%;
width: 100%;
}
// By default no video js play button
.vjs-big-play-button {
display: none;
}
.video-js {
height: 100%;
width: 100%;
.vjs-modal-dialog .vjs-modal-dialog-content {
position: relative;
padding-top: 5rem;
// Make sure no videojs message interferes with overlaying buttons
pointer-events: none;
}
.vjs-control-bar {
// background-color: rgba(0, 0, 0, 0.8);
.vjs-remaining-time {
display: none;
}
.vjs-current-time,
.vjs-time-divider,
.vjs-duration {
display: flex;
}
}
.vjs-picture-in-picture-control {
display: none;
}
}
// ****************************************************************************
// Video::Overlays
// ****************************************************************************
.video-js {
.vjs-overlay-playrate,
.vjs-overlay-seeked {
background-color: rgba(0, 0, 0, 0.5);
font-size: var(--font-large);
width: auto;
padding: 10px 30px;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
animation: fadeOutAnimation ease-in 0.6s;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
@keyframes fadeOutAnimation {
0% {
opacity: 1;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
}
// ****************************************************************************
// Video - Mobile UI
// ****************************************************************************
.video-js.vjs-mobile-ui {
.vjs-control-bar {
background-color: transparent;
}
.vjs-touch-overlay:not(.show-play-toggle) {
.vjs-control-bar {
// Sync the controlBar's visibility with the overlay's
display: none;
}
}
.vjs-touch-overlay {
&.show-play-toggle,
&.skip {
background-color: rgba(0, 0, 0, 0.5);
}
// Override the original's 'display: block' to avoid the big play button
// from being squished to the side:
position: absolute;
}
}
video::-internal-media-controls-overlay-cast-button {
// Push the cast button above vjs-touch-overlay:
z-index: 3;
// Move it to the upper-right since it will clash with "tap to unmute":
left: unset;
right: 8px;
}
.video-js.video-js.vjs-user-inactive {
video::-internal-media-controls-overlay-cast-button {
// (1) Android-Chrome's original Cast button behavior:
// - If video is playing, fade out the button.
// - If video is paused and video is tapped, display the button and stay on.
// (2) We then injected another behavior:
// - Display the button when '.vjs-touch-overlay' is displayed. However,
// the 'controlslist' attribute hack that was used to do this results in the
// button staying displayed without a fade-out timer.
// (3) Ideally, we should sync the '.vjs-touch-overlay' visibility with the
// cast button, similar to how to controlBar's visibility is synced above.
// But I have no idea how to grab the sibling '.show-play-toggle' into the
// css logic.
// (4) So, this is the best that I can come up with: Whenever user is idle,
// the button goes away. The only downside I know is the scenario of
// "overlay is up and video is paused, but button goes away due to idle".
// The user just needs to re-tap any empty space on the overlay to get the
// Cast button again.
opacity: 0;
transition: opacity 1s ease;
}
}
// ****************************************************************************
// Layout and control visibility
// ****************************************************************************
.video-js.vjs-fullscreen,
.video-js:not(.vjs-fullscreen) {
// --- Unhide desired components per layout ---
&.vjs-layout-x-small {
.vjs-playback-rate {
display: flex !important;
}
}
&.vjs-layout-small {
.vjs-current-time,
.vjs-time-divider,
.vjs-duration,
.vjs-playback-rate {
display: flex !important;
}
}
// --- Adjust spacing ---
.vjs-current-time {
padding-right: 0;
}
.vjs-duration {
padding-left: 0;
}
.vjs-playback-rate .vjs-playback-rate-value {
// Reduce the gigantic font a bit. Default was 1.5em.
font-size: 1.25em;
line-height: 2.5;
}
.vjs-playback-rate .vjs-menu {
// Extend the width to prevent a potential scrollbar from blocking the text.
width: 8em;
left: -2em;
}
}
.video-js.vjs-fullscreen {
.vjs-button--theater-mode {
display: none;
}
}
// ****************************************************************************
// Tap-to-unmute
// ****************************************************************************
.video-js--tap-to-unmute {
visibility: hidden; // Start off as hidden.
z-index: 2;
position: absolute;
top: var(--spacing-xs);
left: var(--spacing-xs);
padding: var(--spacing-xs) var(--spacing-m); // Make it comfy for touch.
color: var(--color-gray-1);
background: black;
border: 1px solid var(--color-gray-4);
opacity: 0.9;
&:hover {
opacity: 1;
color: var(--color-white);
}
}
// ****************************************************************************
// ****************************************************************************
.video-js:hover {
.vjs-big-play-button {
background-color: var(--color-primary);
}
}
.file-render {
.video-js {
/*display: flex;*/
/*align-items: center;*/
/*justify-content: center;*/
}
.vjs-big-play-button {
@extend .button--icon;
@extend .button--play;
border: none;
/*position: static;*/
z-index: 2;
.vjs-icon-placeholder {
display: none;
}
}
.vjs-menu-item-text,
.vjs-icon-placeholder {
text-transform: capitalize;
}
}
// ****************************************************************************
// ****************************************************************************
.file-render--embed {
// on embeds, do not inject our colors until interaction
.video-js:hover {
.vjs-big-play-button {
background-color: var(--color-primary);
}
}
.vjs-paused {
.vjs-big-play-button {
display: block;
background-color: rgba(0, 0, 0, 0.6);
}
}
.vjs-ended {
.vjs-big-play-button {
display: none;
}
}
.video-js--tap-to-unmute {
margin-top: var(--spacing-xl);
margin-left: var(--spacing-s);
@media (min-width: $breakpoint-small) {
margin-left: calc(var(--spacing-m) + var(--spacing-s));
}
}
.file-viewer {
iframe,
webview,
img {
max-height: none;
}
}
}
.file-viewer--ended-embed .vjs-big-play-button {
display: none !important; // yes this is dumb, but this was broken and the above CSS was overriding
}
// ****************************************************************************
// Autoplay Countdown
// ****************************************************************************
.autoplay-countdown {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
}
.autoplay-countdown__timer {
width: 100%;
text-align: center;
font-size: var(--font-small);
}
.autoplay-countdown__counter {
margin-top: var(--spacing-m);
}
.autoplay-countdown__button {
/* Border size and color */
border: 3px solid transparent;
/* Creates a circle */
border-radius: 50%;
/* Circle size */
display: inline-block;
height: 86px;
width: 86px;
/* Use transform to rotate to adjust where opening appears */
transition: border 1s;
transform: rotate(45deg);
.button {
background-color: transparent;
transform: rotate(-45deg);
&:hover {
background-color: var(--color-primary);
}
}
}
.autoplay-countdown__button--4 {
border-top-color: #fff;
}
.autoplay-countdown__button--3 {
border-top-color: #fff;
border-right-color: #fff;
}
.autoplay-countdown__button--2 {
border-top-color: #fff;
border-right-color: #fff;
border-bottom-color: #fff;
}
.autoplay-countdown__button--1 {
border-color: #fff;
}
// ****************************************************************************
// ****************************************************************************
.file__viewdate {
display: flex;
justify-content: space-between;
flex-direction: column;
margin-bottom: var(--spacing-s);
> :first-child {
margin-bottom: var(--spacing-s);
}
@media (max-width: $breakpoint-medium) {
flex-direction: row;
justify-content: start;
> :first-child {
margin-bottom: 0;
margin-right: 1rem;
}
}
}
.file-page__image {
img {
cursor: pointer;
}
}

View file

@ -0,0 +1,713 @@
@import '../init/mixins';
input,
textarea,
select,
.date-picker-input {
height: var(--height-input);
border-radius: var(--border-radius);
border: 1px solid;
color: var(--color-input);
border-color: var(--color-input-border);
background-color: var(--color-input-bg);
padding-right: var(--spacing-s);
padding-left: var(--spacing-s);
&:focus {
@include focus;
}
&::placeholder {
color: var(--color-input-placeholder);
opacity: 0.4;
}
&:disabled {
opacity: 0.4;
& + label {
opacity: 0.4;
}
}
&[type='range'] {
height: auto;
height: 0.5rem;
background-color: var(--color-secondary);
}
}
checkbox-element,
radio-element,
select {
cursor: pointer;
}
select {
background-image: var(--select-toggle-background);
background-position: 99% center;
background-repeat: no-repeat;
background-size: 1rem;
padding-right: var(--spacing-l);
padding-left: var(--spacing-s);
font-weight: bold;
}
fieldset-group {
display: flex;
flex-direction: row;
justify-content: space-between;
&.fieldset-group--smushed {
fieldset-section + fieldset-section {
margin-top: 0;
}
}
}
fieldset-section,
fieldset-group,
form,
.checkbox,
.radio,
.form-field--SimpleMDE,
.form-field__help {
+ fieldset-section,
+ fieldset-group,
+ form,
+ .checkbox,
+ .radio,
+ .form-field--SimpleMDE {
margin-top: var(--spacing-l);
}
+ .form-field__help {
margin-top: var(--spacing-s);
}
&:last-child {
margin-bottom: 0;
}
input,
select {
width: 100%;
}
}
fieldset-section,
.checkbox,
.radio {
display: flex;
flex-direction: column;
}
label {
font-size: var(--font-small);
color: var(--color-input-label);
display: inline-block;
margin-bottom: 0.1rem;
.icon__lbc {
margin-bottom: 4px;
}
}
input-submit {
display: flex;
& > *:first-child,
& > *:nth-child(2) {
margin: 0;
}
& > *:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
}
& > *:nth-child(2) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border: 1px solid var(--color-border);
}
}
.checkbox,
.radio {
position: relative;
input[type='checkbox'],
input[type='radio'] {
height: var(--height-checkbox);
width: var(--height-checkbox);
position: absolute;
border: none;
left: 0;
padding: 0;
background-color: transparent;
&:disabled + label {
cursor: default;
pointer-events: none;
}
}
label {
position: relative;
display: inline-block;
margin: 0;
font-size: var(--font-base);
padding-left: calc(var(--height-checkbox) + var(--spacing-s));
min-height: var(--height-checkbox);
&::before {
background-color: var(--color-input-toggle-bg);
}
&:hover {
&::before {
background-color: var(--color-input-toggle-bg-hover);
}
}
}
label::before,
label::after {
position: absolute;
content: '';
}
// Hide the checkmark by default
input[type='checkbox'] + label::after,
input[type='radio'] + label::after {
content: none;
}
// Unhide on the checked state
input[type='checkbox']:checked + label::after,
input[type='radio']:checked + label::after {
content: '';
}
input[type='checkbox']:focus + label::before,
input[type='radio']:focus + label::before {
@include focus;
}
}
.checkbox {
// Outer box of the fake checkbox
label::before {
height: var(--height-checkbox);
width: var(--height-checkbox);
border: 1px solid var(--color-input-border);
border-radius: var(--border-radius);
left: 0px;
top: -1px;
}
// Checkmark of the fake checkbox
label::after {
height: 6px;
width: 12px;
border-left: 2px solid;
border-bottom: 2px solid;
border-color: var(--color-input-toggle);
border-left-color: var(--color-input-toggle);
transform: rotate(-45deg);
left: 6px;
top: 6px;
}
}
.radio {
input[type='radio'] {
border-radius: 50%;
}
// Outer box of the fake radio
label::before {
height: var(--height-radio);
width: var(--height-radio);
border: 1px solid var(--color-input-border);
border-radius: calc(var(--height-radio) * 0.5);
left: 0px;
top: -1px;
}
// Checkmark of the fake radio
label::after {
height: 12px;
width: 12px;
border-radius: 50%;
background-color: var(--color-primary);
left: 6px;
top: 5px;
}
}
.range__label {
display: flex;
justify-content: space-between;
width: 100%;
margin-bottom: var(--spacing-m);
> * {
width: 33%;
text-align: center;
&:first-of-type {
text-align: left;
}
&:last-of-type {
text-align: right;
}
}
}
.fieldset-group {
@extend fieldset-group;
}
.fieldset-section {
@extend fieldset-section;
}
.input-submit {
@extend input-submit;
}
input-submit {
align-items: center;
input {
z-index: 2;
}
}
input[type='number'] {
width: 8rem;
}
fieldset-group {
+ fieldset-group {
margin-top: var(--spacing-s);
}
&.fieldset-group--smushed {
justify-content: flex-start;
fieldset-section {
width: auto;
margin: 0;
&:first-child {
input,
select {
border-right: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
&:nth-of-type(2) {
input,
select {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
label {
margin-left: var(--spacing-s);
}
}
}
&.fieldgroup--paginate {
padding-bottom: var(--spacing-l);
margin-top: var(--spacing-l);
align-items: flex-end;
justify-content: center;
}
}
// This is a special case where the prefix appears "inside" the input
// It would be way simpler to just use position: absolute and give it a width
// but the width can change when we use it for the name prefix
// lbry:// {input}, lbry://@short {input}, @lbry://longername {input}
// The spacing/alignment isn't very robust and will probably need to be changed
// if we use this in more places
&.fieldset-group--disabled-prefix {
align-items: flex-end;
label {
min-height: 18px;
white-space: nowrap;
// Set width 0 and overflow visible so the label can act as if it's the input label and not a random text node in a side by side div
overflow: visible;
width: 0;
}
fieldset-section:first-child {
max-width: 40%;
.form-field__prefix {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding: 0.5rem;
height: var(--height-input);
border: 1px solid;
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
border-color: var(--color-input-border);
border-right-color: var(--color-input-prefix-border);
color: var(--color-text);
background-color: var(--color-input-prefix-bg);
}
}
fieldset-section:last-child {
width: 100%;
label {
// Overwrite the input's label to wrap instead. This is usually
// an error message, which could be long in other languages.
width: 100%;
white-space: normal;
}
input {
border-left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-color: var(--color-input-border);
padding-left: var(--spacing-xs);
}
}
}
}
.form-field--copyable {
padding: 0.2rem 0.75rem;
text-overflow: ellipsis;
user-select: text;
cursor: default;
}
.form-field--short {
width: 100%;
@media (min-width: $breakpoint-small) {
width: 25em;
}
}
.form-field--price-amount {
max-width: 6em;
}
.form-field--price-amount--auto {
width: auto;
min-width: 100%;
}
.form-field--address {
min-width: 18em;
@media (max-width: $breakpoint-xxsmall) {
min-width: 10em;
}
}
.form-field__help {
@extend .help;
}
.form-field__help + .checkbox,
.form-field__help + .radio {
margin-top: var(--spacing-l);
}
.form-field__conjuction {
padding-top: 1rem;
}
.form-field__two-column {
@media (min-width: $breakpoint-small) {
column-count: 2;
}
}
.form-field__quick-action {
float: right;
font-size: var(--font-xsmall);
margin-top: 2.5%;
}
.form-field__textarea-info {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
margin-top: var(--spacing-xxs);
margin-bottom: var(--spacing-s);
}
.form-field__quick-emojis {
> *:not(:last-child) {
margin-right: var(--spacing-s);
}
}
fieldset-section {
.form-field__internal-option {
margin-top: var(--spacing-s);
margin-left: 2.2rem;
&:first-of-type {
margin-top: var(--spacing-s); // Extra specificity needed here since _section.scss is applied after this file
}
}
.select--slim {
margin-bottom: var(--spacing-xxs);
@media (min-width: $breakpoint-small) {
max-width: none;
}
select {
max-height: 1.5rem !important;
padding: 0 var(--spacing-xs);
padding-right: var(--spacing-l);
}
}
}
#automatic_dark_mode_range_start,
#automatic_dark_mode_range_end {
min-width: 6em;
}
.date-picker-input {
font-weight: bold;
.react-datetime-picker__wrapper {
border: 0;
}
}
.form-field-date-picker {
margin-bottom: var(--spacing-l);
label {
display: block;
}
.controls {
display: flex;
.date-picker-input,
.button--link {
margin-right: var(--spacing-m);
}
}
.react-datetime-picker__button {
svg {
stroke: var(--color-text);
}
}
.react-datetime-picker__button:enabled:hover .react-datetime-picker__button__icon,
.react-datetime-picker__button:enabled:focus .react-datetime-picker__button__icon {
stroke: var(--color-primary);
}
.react-date-picker__calendar {
z-index: 1000;
}
.react-calendar {
width: 350px;
max-width: 100%;
background: var(--color-card-background);
border: 1px solid #a0a096;
font-family: inherit;
line-height: 1;
}
.react-calendar--doubleView {
width: 700px;
}
.react-calendar--doubleView .react-calendar__viewContainer {
display: flex;
margin: -0.5em;
}
.react-calendar--doubleView .react-calendar__viewContainer > * {
width: 50%;
margin: 0.5em;
}
.react-calendar,
.react-calendar *,
.react-calendar *:before,
.react-calendar *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
padding: 2px 1px;
}
.react-calendar button {
margin: 0;
border: 0;
outline: none;
}
.react-calendar button:enabled:hover {
cursor: pointer;
}
.react-calendar__navigation {
height: 44px;
margin-bottom: 1em;
color: var(--color-text);
}
.react-calendar__navigation__label {
color: var(--color-text);
}
.react-calendar__navigation button {
min-width: 44px;
background: none;
color: var(--color-text);
}
.react-calendar__navigation button:enabled:hover,
.react-calendar__navigation button:enabled:focus {
background: var(--color-button-alt-bg-hover);
}
.react-calendar__navigation button[disabled] {
color: var(--color-text);
}
.react-calendar__month-view__weekdays {
text-align: center;
text-transform: uppercase;
font-weight: bold;
font-size: 0.75em;
color: var(--color-text-alt);
}
.react-calendar__month-view__weekdays__weekday {
padding: 0.5em;
}
.react-calendar__month-view__weekNumbers {
font-weight: bold;
}
.react-calendar__month-view__weekNumbers .react-calendar__tile {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75em;
padding: calc(0.75em / 0.75) calc(0.5em / 0.75);
}
.react-calendar__month-view__days__day,
.react-calendar__month-view__days__day--weekend {
color: var(--color-text);
font-weight: normal;
}
.react-calendar__month-view__days__day--neighboringMonth {
color: var(--color-gray-5);
}
.react-calendar__year-view .react-calendar__tile,
.react-calendar__decade-view .react-calendar__tile,
.react-calendar__century-view .react-calendar__tile {
padding: 2em 0.5em;
}
.react-calendar__tile {
max-width: 100%;
text-align: center;
padding: 0.75em 0.5em;
background: none;
border-radius: var(--border-radius);
color: var(--color-text);
}
.react-calendar__tile:enabled:hover,
.react-calendar__tile:enabled:focus {
background: var(--color-button-alt-bg-hover);
}
.react-calendar__tile--now {
background: var(--color-button-secondary-bg);
}
.react-calendar__tile--now:enabled:hover,
.react-calendar__tile--now:enabled:focus {
background: var(--color-button-secondary-bg-hover);
}
.react-calendar__tile--hasActive {
color: var(--color-button-primary-text);
background: var(--color-button-primary-bg);
}
.react-calendar__tile--hasActive:enabled:hover,
.react-calendar__tile--hasActive:enabled:focus {
background: var(--color-button-primary-bg-hover);
}
.react-calendar__tile--active {
color: var(--color-button-primary-text);
background: var(--color-button-primary-bg);
}
.react-calendar__tile--active:enabled:hover,
.react-calendar__tile--active:enabled:focus {
background: var(--color-button-primary-bg-hover);
}
.react-calendar--selectRange .react-calendar__tile--hover {
background-color: #e6e6e6;
}
.react-datetime-picker__inputGroup__amPm {
background: var(--color-input-bg);
}
.react-datetime-picker__inputGroup__leadingZero {
// Not perfect, but good enough for our standard zoom levels.
margin-bottom: 1px;
}
.react-datetime-picker__inputGroup__input--hasLeadingZero {
margin-left: -0.54em;
padding-left: calc(1px + 0.54em);
}
.react-calendar__month-view__days__day--neighboringMonth {
color: var(--color-gray-5);
}
}
.form-field-calendar {
border-radius: var(--border-radius);
border: 1px solid var(--color-border);
margin-left: calc(var(--spacing-xs) * -1);
margin-bottom: var(--spacing-xs);
animation: menu-animate-in var(--animation-duration) var(--animation-style);
box-shadow: 3px 3px rgba(0, 0, 0, 0.1);
}

View file

@ -0,0 +1,143 @@
[theme='dark'] {
// Color overrides
--color-primary: #e50054;
--color-primary-alt: #66001880;
--color-fire: #ff6635;
--color-fire-outside: #ff9b20;
// Structure
--color-background: #140e1b;
--color-background-overlay: #0c0d0e95;
--color-border: #30243d;
--color-card-background: #181021;
--color-card-background-highlighted: #241c30;
// Text
--color-text: var(--color-gray-1);
--color-text-subtitle: var(--color-gray-4);
--color-text-empty: var(--color-text-subtitle);
--color-text-help: #bbbbbb;
--color-text-warning: #212529;
--color-text-warning--background: var(--lbry-yellow-1);
--color-text-error: var(--color-danger);
--color-error: var(--color-danger-alt);
--color-blockquote: var(--color-gray-5);
--color-blockquote-bg: var(--color-card-background-highlighted);
--color-help-warning-text: var(--color-white-alt);
--color-help-warning-bg: #fbbf2450;
// Tags (words)
--color-tag-words: var(--color-text);
--color-tag-words-bg: var(--color-gray-5);
--color-tag-words-hover: var(--color-white);
--color-tag-words-bg-hover: var(--color-gray-4);
// Header
--color-header-button: #38274c;
--color-header-background: #231830;
// Button
--color-button-primary-text: white;
--color-button-primary-hover-text: var(--color-primary-alt);
--color-button-secondary-bg: #2c1543;
--color-button-secondary-border: #4f1c82;
--color-button-secondary-bg-hover: #3b1c5b;
--color-button-secondary-text: #efefef;
--color-button-alt-bg: var(--color-header-button);
--color-button-alt-bg-hover: #2b2037;
--color-button-toggle-text: var(--color-text);
--color-button-toggle-bg: var(--color-primary-alt);
--color-button-toggle-bg-hover: var(--color-primary-alt);
--color-button-alt-text: #e2e9f0;
--color-button-border: #5b4475;
--color-link: var(--color-primary);
--color-link-hover: #d75673;
--color-link-active: #ec1d4c;
--color-link-focus-bg: #3d2d4e;
// Input
--color-input: #f4f4f5;
--color-input-label: #a7a7a7;
--color-input-placeholder: #f4f4f5;
--color-input-bg: var(--color-header-button);
--color-input-bg-copyable: #4c3861;
--color-input-border: var(--color-border);
--color-input-border-active: var(--color-secondary);
--color-input-toggle: var(--color-primary-alt-3);
--color-input-toggle-bg: var(--color-input-bg);
--color-input-toggle-bg-hover: var(--color-secondary);
--color-input-bg-selected: var(--color-primary-alt);
--color-input-prefix-bg: var(--color-input-bg-copyable);
--color-input-prefix-border: var(--color-gray-4);
--select-toggle-background: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23ffffff'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
// Navigation
--color-navigation-icon: #76808a;
--color-navigation-link: #b9c3ce;
--color-navigation-active: #2b2037;
--color-navigation-active-text: #c6bcd2;
--color-navigation-hover: #21182a;
--color-navigation-hover-text: #c6bcd2;
// Tags
--color-tag: #ff85b1;
--color-tag-bg: var(--color-navigation-hover);
--color-tag-hover: var(--color-white);
--color-tag-bg-hover: var(--color-primary-alt-2);
--color-tag-mature-bg: var(--color-primary-alt-2);
// Menu
--color-menu-background: var(--color-header-background);
--color-menu-background--active: var(--color-primary-alt);
--color-menu-icon: #928b9b;
--color-menu-icon-active: #d6d6d6;
// Comments
--color-comment-menu: #6a6a6a;
--color-comment-menu-hovering: #e0e0e0;
--color-comment-highlighted: #484734;
--color-comment-threadline: #24192f;
--color-comment-threadline-hover: var(--color-gray-4);
// Other
--color-tab-text: var(--color-white);
--color-tabs-background: var(--color-card-background);
--color-tab-divider: var(--color-white);
--color-modal-background: var(--color-card-background);
--color-notice: #58563b;
--color-purchased: #ffd580;
--color-purchased-alt: var(--color-purchased);
--color-purchased-text: black;
--color-thumbnail-background: var(--color-gray-5);
--color-tooltip-bg: #2f3337;
--color-focus: #e91e6329;
--color-placeholder-background: #261a35;
--color-spinner-light: white;
--color-spinner-dark: #212529;
--color-login-graphic-background: var(--color-background);
// Editor
--color-editor-cursor: var(--color-text);
--color-editor-quote: #d3d3d3;
--color-editor-tag: #efbe5d;
--color-editor-attr: #68ccf9;
--color-editor-string: #ff8b6b;
--color-editor-inline-code-fg: #ce9178;
--color-editor-inline-code-fg-preview: #e8b692;
--color-editor-inline-code-bg: rgba(20, 68, 102, 0.3);
--color-editor-inline-code-bg-preview: #464b50;
--color-editor-selected: #264f78;
--color-editor-link: var(--color-link);
--color-editor-url: var(--color-editor-string);
--color-editor-hr: var(--color-editor-tag);
--color-editor-hr-preview: #a0a0a0;
// Ads
--color-ads-background: #475057;
--color-ads-text: #111;
--color-ads-link: var(--color-primary-alt);
// Scrollbar
--color-scrollbar-thumb-bg: rgba(255, 255, 255, 0.2);
--color-scrollbar-track-bg: transparent;
}

View file

@ -0,0 +1,199 @@
//
// Colors are taken from this color palette
// https://tailwindcss.com/docs/customizing-colors
// New colors should be also taken from the same color palette (if possible)
//
:root {
// Generic colors
--color-primary: #047857;
--color-primary-alt: #e4f4ef;
--color-primary-alt-2: #065f46;
--color-primary-alt-3: #10b981;
--color-secondary: #1e3a8a;
--color-secondary-alt: #dbeafe;
--color-secondary-alt-2: #bfdbfe;
--color-secondary-alt-3: #1e40af;
--color-tertiary: #5b21b6;
--color-tertiary-alt: #f5f3ff;
--color-danger: #991b1b;
--color-danger-alt: #fecaca;
--color-warning: #fff58c;
--color-black: #111;
--color-white: #fdfdfd;
--color-white-alt: #fafafa;
--color-gray-1: #f3f4f6;
--color-gray-2: #e5e7eb;
--color-gray-3: #d1d5db;
--color-gray-4: #9ca3af;
--color-gray-5: #71717a;
--color-gray-6: #52525b;
--color-gray-7: #3f3f46;
--color-gray-8: #27272a;
--color-gray-9: #1f1f22;
--color-gray-10: #18181b;
--color-amber: #f26522;
--color-orange: #fb923c;
// Structure
--color-text: var(--color-black);
--color-text-inverse: #fdfdfd;
--color-background: #fafafa;
--color-background--splash: #212529;
--color-border: #ededed;
--color-background-overlay: #21252980;
--color-card-background: #ffffff;
--color-card-background-highlighted: #f1f7fe;
// Text
--color-text-selection-bg: var(--color-secondary-alt);
--color-text-selection: var(--color-secondary);
--color-text-empty: #999999;
--color-text-help: #999999;
--color-text-subtitle: #767676;
--color-text-warning: #212529;
--color-help-warning-bg: #fef3c7;
--color-help-warning-text: #555555;
--color-text-warning--background: var(--lbry-yellow-1);
--color-blockquote: var(--color-gray-3);
--color-text-error: var(--color-danger);
--color-error: var(--color-danger-alt);
--color-tooltip-bg: #222;
--color-tooltip-text: #fafafa;
// Header
--color-header-background: #ffffff;
--color-header-button: var(--color-button-alt-bg);
--color-header-button-active: var(--color-primary-alt);
--color-header-button-hover: var(--color-primary-alt);
// Button
--color-button-primary-bg: var(--color-primary);
--color-button-primary-text: var(--color-primary-alt);
--color-button-primary-bg-hover: var(--color-primary-alt-2);
--color-button-primary-hover-text: var(--color-primary-alt);
--color-button-secondary-bg: var(--color-secondary-alt);
--color-button-secondary-border: var(--color-secondary-alt);
--color-button-secondary-text: var(--color-secondary);
--color-button-secondary-bg-hover: var(--color-secondary-alt-2);
--color-button-alt-bg: var(--color-gray-1);
--color-button-alt-text: var(--color-text);
--color-button-alt-bg-hover: var(--color-gray-2);
--color-button-toggle-text: var(--color-primary);
--color-button-toggle-bg: var(--color-primary-alt);
--color-button-border: var(--color-gray-3);
--color-link: var(--color-primary);
--color-link-hover: var(--color-black);
--color-link-focus-bg: var(--color-gray-1);
// Input
--color-input-color: var(--color-black);
--color-input-label: var(--color-gray-5);
--color-input-placeholder: var(--color-gray-8);
--color-input-bg: var(--color-gray-1);
--color-input-border: var(--color-border);
--color-input-border-active: var(--color-secondary);
--color-input-toggle: var(--color-secondary);
--color-input-toggle-bg: var(--color-gray-1);
--color-input-toggle-bg-hover: var(--color-secondary-alt);
--color-input-prefix-bg: var(--color-gray-2);
--color-input-prefix-border: var(--color-gray-5);
--color-input-bg-selected: var(--color-primary-alt);
--select-toggle-background: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23212529'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
// Navigation
--color-navigation-icon: var(--color-gray-5);
--color-navigation-link: var(--color-gray-5);
--color-navigation-active: var(--color-primary-alt);
--color-navigation-active-text: var(--color-primary);
--color-navigation-hover: var(--color-gray-1);
--color-navigation-hover-text: var(--color-primary);
// Tags
--color-tag: var(--color-gray-5);
--color-tag-bg: var(--color-button-alt-bg);
--color-tag-hover: var(--color-primary-alt);
--color-tag-bg-hover: var(--color-button-primary-bg);
// Tags (words)
--color-tag-words: var(--color-gray-5);
--color-tag-words-bg: var(--color-button-alt-bg);
--color-tag-words-hover: var(--color-button-alt-text);
--color-tag-words-bg-hover: var(--color-button-alt-bg-hover);
// Menu
--color-menu-background: var(--color-header-background);
--color-menu-background--active: var(--color-card-background-highlighted);
--color-menu-icon: var(--color-navigation-link);
// Comments
--color-comment-menu: var(--color-gray-3);
--color-comment-menu-hovering: var(--color-gray-6);
--color-comment-highlighted: #fff2d9;
--color-comment-threadline: var(--color-gray-1);
--color-comment-threadline-hover: var(--color-gray-4);
// Icons
--color-follow-bg: #ffd4da;
--color-follow-icon: #e2495e;
--color-view-bg: var(--color-secondary-alt);
--color-view-icon: var(--color-secondary);
// Snack
--color-snack-bg: var(--color-primary);
--color-snack: var(--color-white);
--color-snack-bg-error: var(--color-danger);
--color-snack-upgrade: var(--color-tertiary);
// Superchat
--color-superchat-text: var(--color-black);
--color-superchat: var(--color-cost);
--color-superchat__light: #fcd34d50;
--color-superchat-2: #fde68a;
--color-superchat-3: #fef3c7;
--color-superchat-3__light: #fef3c750;
--color-superchat-4: #fffbeb;
// Editor
--color-editor-cursor: var(--color-text);
--color-editor-quote: #707070;
--color-editor-tag: #ea9400;
--color-editor-attr: #04b0f4;
--color-editor-string: #ff7451;
--color-editor-inline-code-fg: var(--color-text);
--color-editor-inline-code-fg-preview: #2e3439;
--color-editor-inline-code-bg: rgba(157, 161, 165, 0.3);
--color-editor-inline-code-bg-preview: #d0e8ff;
--color-editor-selected: #add6ff;
--color-editor-link: var(--color-link);
--color-editor-url: var(--color-editor-string);
--color-editor-hr: var(--color-editor-tag);
--color-editor-hr-preview: #cccccc;
// Other
--color-focus: #bfdbfe;
--color-notification: #cc190f;
--color-live: #cc190f;
--color-nag: var(--color-orange);
--color-cost: #fcd34d;
--color-notice: #fef3ca;
--color-purchased: var(--color-cost);
--color-purchased-alt: #ffebc2;
--color-purchased-text: var(--color-gray-5);
--color-thumbnail-background: var(--color-gray-1);
--color-spinner-light: var(--color-white);
--color-spinner-dark: var(--color-black);
--color-placeholder-background: var(--color-gray-1);
--color-file-viewer-background: var(--color-card-background);
--color-tabs-background: var(--color-card-background);
--color-tab-divider: var(--color-primary);
--color-modal-background: var(--color-card-background);
--color-login-graphic-background: var(--color-primary-alt);
// Ads
--color-ads-background: #fae5ff;
--color-ads-link: var(--color-link);
// Scrollbar
--color-scrollbar-thumb-bg: rgba(0, 0, 0, 0.2);
--color-scrollbar-track-bg: transparent;
}

View file

@ -0,0 +1,57 @@
:root {
// Generic colors
--color-primary: #257761;
--color-primary-alt: #e4f4ef;
--color-primary-alt-2: #4b8576;
--color-secondary: #295284;
--color-secondary-alt: #d9eaff;
--color-tertiary: #552470;
--color-tertiary-alt: #f7e8ff;
--color-danger: #9b2023;
--color-danger-alt: #fccdce;
--color-warning: #fff58c;
--color-cost: #ffd580;
--color-focus: #93cff2;
--color-notification: #f02849;
--color-live: #cc190f;
--color-black: #111;
--color-white: #fdfdfd;
--color-white-alt: #fafafa;
--color-gray-1: #eff1f4;
--color-gray-2: #d8dde1;
--color-gray-3: #ced4da;
--color-gray-4: #abb1b7;
--color-gray-5: #666a6d;
// Text
--color-text: var(--color-black);
--color-text-subtitle: var(--color-gray-5);
--color-text-inverse: #fdfdfd;
// Components
// Button
--color-button-primary-bg: var(--color-primary);
--color-button-primary-text: var(--color-primary-alt);
--color-button-primary-bg-hover: var(--color-primary-alt-2);
--color-button-primary-hover-text: var(--color-primary-alt);
--color-button-secondary-bg: var(--color-secondary-alt);
--color-button-secondary-border: var(--color-secondary-alt);
--color-button-secondary-text: var(--color-secondary);
--color-button-secondary-bg-hover: #b9d0e9;
--color-button-alt-bg: var(--color-gray-1);
--color-button-alt-text: var(--color-text);
--color-button-alt-bg-hover: var(--color-gray-2);
--color-link: var(--color-primary);
--color-link-hover: var(--color-black);
// Table
--color-table-highlight: var(--color-white-alt);
// Tag
--color-tag: var(--color-gray-5);
--color-tag-bg: var(--color-button-alt-bg);
--color-tag-hover: var(--color-button-alt-text);
--color-tag-bg-hover: var(--color-button-alt-bg-hover);
}

View file

@ -0,0 +1,517 @@
// Generic html styles used across the App
// component specific styling should go in the component scss file
*::selection {
background-color: var(--color-text-selection-bg);
color: var(--color-text-selection);
}
*:focus {
outline: none;
}
html {
@include font-sans;
height: 100%;
min-height: 100%;
overflow-x: hidden;
color: var(--color-text);
background-color: var(--color-background);
font-size: 16px;
}
body {
font-size: 1em;
cursor: default;
line-height: 1.5;
font-weight: 400;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}
hr {
height: 1px;
background-color: var(--color-gray-2);
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 1rem;
}
p,
ol,
ul {
& + p,
& + ul,
& + ol {
margin-top: var(--spacing-s);
}
}
ul,
ol {
li {
position: relative;
list-style-position: outside;
margin: var(--spacing-xs) 0;
margin-left: var(--spacing-s);
margin-bottom: 0;
@media (min-width: $breakpoint-small) {
margin-left: var(--spacing-xl);
}
}
}
.ul--no-style {
list-style: none;
margin-bottom: 0;
li {
margin: 0;
}
}
dl {
display: flex;
flex-direction: row;
flex-wrap: wrap;
overflow-x: visible;
margin-top: var(--spacing-m);
}
dt {
flex-basis: 50%;
text-align: left;
font-weight: bold;
}
.dt__text {
margin-right: var(--spacing-s);
}
dd {
display: flex;
align-items: center;
justify-content: flex-end;
flex-basis: 45%;
flex-grow: 1;
margin: 0;
text-align: right;
.help--warning {
margin-bottom: 0;
margin-top: var(--spacing-s);
text-align: left;
}
}
.dd__text {
display: flex;
justify-content: flex-end;
}
.dd__button {
margin-right: var(--spacing-s);
}
dt,
dd {
padding: var(--spacing-m) var(--spacing-s);
border-top: 1px solid var(--color-border);
&:last-of-type {
border-bottom: 1px solid var(--color-border);
}
}
blockquote {
padding: 0 0.8rem;
margin-top: var(--spacing-xxs);
margin-bottom: var(--spacing-xxs);
opacity: 0.9;
border-left: 0.2rem solid var(--color-blockquote);
color: var(--color-text-subtitle);
}
code {
@include font-mono;
font-size: 1.5rem;
}
hr {
width: 100%;
height: 1px;
background-color: var(--color-border);
}
img,
a {
-webkit-user-drag: none;
}
img {
// Hide alt text when an image fails to load
text-indent: -9999px;
}
textarea {
min-height: calc(var(--height-input) * 2);
}
.columns {
display: flex;
justify-content: space-between;
align-items: flex-start;
> * {
flex-grow: 1;
flex-basis: 0;
min-width: 15rem;
margin-bottom: var(--spacing-l);
&:first-child {
flex-basis: 1px;
margin-right: 1.5rem;
}
}
@media (max-width: $breakpoint-small) {
flex-direction: column;
& > * {
margin: 0;
margin-bottom: var(--spacing-m);
width: 100%;
flex-basis: auto;
&:first-child {
margin-right: 0;
}
}
}
}
.hidden {
display: none;
}
.disabled {
opacity: 0.3;
pointer-events: none;
}
.column {
display: flex;
.column__item:not(:first-child) {
padding-left: $spacing-width * 2/3;
flex: 1;
}
.column__item--between {
justify-content: space-between;
}
@media (max-width: $breakpoint-small) {
flex-direction: column;
.column__item:not(:first-child) {
padding-left: 0;
flex: 1;
}
& > * {
margin: 0;
margin-bottom: var(--spacing-m);
width: 100%;
flex-basis: auto;
&:first-child {
margin-right: 0;
}
}
}
}
.truncated-text {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
word-break: break-word;
}
.busy-indicator__loader {
min-width: 16px;
min-height: 8px;
margin: -1rem 0;
padding: 0 30px;
background: url('../../static/img/busy.gif') no-repeat center center;
display: inline-block;
vertical-align: middle;
&:first-child {
padding-left: 2px;
}
&:last-child {
padding-right: 2px;
}
}
.help {
display: block;
font-size: var(--font-xsmall);
color: var(--color-text-help);
margin-top: var(--spacing-s);
&:not(:last-child) {
margin-bottom: var(--spacing-m);
}
.button--link + .button--link {
margin-left: var(--spacing-s);
}
@media (min-width: $breakpoint-small) {
font-size: var(--font-small);
}
}
.help--warning {
@extend .help;
padding: var(--spacing-s);
border-radius: var(--border-radius);
background-color: var(--color-help-warning-bg);
color: var(--color-help-warning-text);
margin-bottom: var(--spacing-s);
border: 1px solid var(--color-border);
}
.help--notice {
@extend .help--warning;
background-color: var(--color-card-background-highlighted);
}
.help--inline {
@extend .help;
margin-top: 0;
margin-bottom: 0;
&:not(:last-child) {
margin-bottom: 0;
}
.icon--help {
top: 3px;
margin-left: 2px;
}
}
.help--card-actions {
@extend .help;
margin-top: var(--spacing-m);
}
.help--dt {
@extend .help;
display: inline-block;
margin-top: 0;
}
.help--spendable {
margin-left: var(--spacing-xxs);
}
.empty {
color: var(--color-text-empty);
font-style: italic;
}
.empty--centered {
text-align: center;
padding: calc(var(--spacing-l) * 3) 0;
}
.qr-code {
width: 134px;
height: 134px;
border: 3px solid white;
&.qr-code--right-padding {
margin-right: $spacing-vertical * 2/3;
}
&.qr-code--top-padding {
margin-top: $spacing-vertical * 2/3;
}
}
.error__wrapper {
background-color: var(--color-error);
padding: var(--spacing-s);
border-radius: var(--border-radius);
}
.error__wrapper--no-overflow {
@extend .error__wrapper;
max-height: 10rem;
overflow: hidden;
}
.error__text {
color: var(--color-text-error);
}
.help--error {
@extend .help;
color: var(--color-text-error);
}
.thumbnail-preview {
width: var(--thumbnail-preview-width);
height: var(--thumbnail-preview-height);
background-color: var(--color-thumbnail-background);
padding: var(--spacing-s);
font-size: var(--font-small);
border-radius: var(--border-radius);
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;
}
.thumbnail-picker__preview {
width: calc(var(--thumbnail-preview-width) * 1.5);
height: calc(var(--thumbnail-preview-height) * 1.5);
background-color: var(--color-thumbnail-background);
padding: var(--spacing-s);
font-size: var(--font-small);
border-radius: var(--border-radius);
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;
}
.emoji {
font-size: 1.3em;
}
.download-text {
font-size: var(--font-xsmall);
}
.notice-message {
position: relative;
border-radius: var(--border-radius);
padding: var(--spacing-l);
background-color: var(--color-primary-alt);
~ .card {
margin-top: var(--spacing-m);
}
}
.notice-message--loud {
@extend .notice-message;
background-color: #fef1f6;
color: var(--color-black);
font-weight: bold;
.button {
color: #fa6165;
}
}
.privacy-img {
height: 10rem;
}
.confirm__label {
@extend label;
}
.confirm__value {
display: flex;
align-items: center;
margin-bottom: var(--spacing-m);
font-size: var(--font-large);
&:last-child {
margin-bottom: 0;
}
}
.confirm__value--no-gap {
margin-bottom: 0;
}
.confirm__value--subitem {
font-size: var(--font-xsmall);
}
.mobile-only {
display: none;
@media (max-width: $breakpoint-small) {
display: block;
}
}
.mobile-hidden {
@media (max-width: $breakpoint-small) {
display: none !important;
}
}
.ads-test {
height: 50vh;
position: relative;
.video-js {
height: 50vh;
}
.video-js .vjs-tech {
height: 50vh;
}
}
.adspruce-bannerspot {
height: 5rem;
width: 100%;
}
.release__notes {
max-height: 50vh;
overflow: scroll;
}
.signup__odysee-logo {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
img {
margin-top: var(--spacing-xl);
height: 4rem;
}
}
.home__meme {
text-align: center;
font-weight: bold;
line-height: 1;
font-size: 1rem;
margin-bottom: var(--spacing-m);
@media (min-width: $breakpoint-small) {
font-size: 1.2rem;
margin-bottom: var(--spacing-l);
}
}

View file

@ -0,0 +1,237 @@
@mixin between {
display: flex;
justify-content: space-between;
}
@mixin breakpoint-max($breakpoint) {
@media (max-width: #{$breakpoint}px) {
@content;
}
}
@mixin breakpoint-min($breakpoint) {
@media (min-width: #{$breakpoint}px) {
@content;
}
}
@mixin center {
align-items: center;
display: inline-flex;
justify-content: center;
}
@mixin clearfix {
clear: both;
content: '';
display: block;
}
// (Smart) text truncation
// Pass in a width to customize how much text is allowed
// Omit value for basic text truncation
@mixin constrict($value: null) {
@if ($value) {
max-width: $value;
}
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin create-grid($items-per-row: 4) {
grid-template: repeat(1, 1fr) / repeat($items-per-row, 1fr);
}
// Smart font include
// Simply pass in the font-weight you want to use and the normal/italicized versions will be added
// No more weighing down the front-end with references to unused weights
@mixin font-face($font-weight, $relative-font-path, $font-name) {
@font-face {
font-family: $font-name;
font-style: normal;
font-weight: $font-weight;
// sass-lint:disable indentation
src: url('#{$relative-font-path}/#{$font-weight}.woff2') format('woff2'),
url('#{$relative-font-path}/#{$font-weight}.woff') format('woff');
// sass-lint:enable indentation
}
@font-face {
font-family: $font-name;
font-style: italic;
font-weight: $font-weight;
// sass-lint:disable indentation
src: url('#{$relative-font-path}/#{$font-weight}i.woff2') format('woff2'),
url('#{$relative-font-path}/#{$font-weight}i.woff') format('woff');
// sass-lint:enable indentation
}
}
@mixin font-mono {
font-family: 'Fira Code', 'Courier New', monospace;
}
@mixin font-sans {
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}
@mixin font-serif {
font-family: Georgia, serif;
}
@mixin hide-text {
border: none;
color: transparent;
font: 0 / 0 a;
text-shadow: none;
}
// Cross-browser line-clamp support
@mixin line-clamp($element-height: 2rem, $row-count: 2, $fade-color: var(--lbry-white), $computed-position: relative) {
height: $element-height;
overflow: hidden;
position: $computed-position;
&::after {
width: 50%;
height: calc(#{$element-height} / #{$row-count});
right: 0;
bottom: 0;
background-image: linear-gradient(to right, rgba($lbry-white, 0), #{$fade-color} 80%);
content: '';
position: absolute;
}
}
@mixin no-user-select {
user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
// Use CSS variables without upsetting Sass-Lint
// https://github.com/sasstools/sass-lint/issues/1161#issuecomment-390537190
@mixin root-prop($prop: null, $value: null) {
@if ($prop and $value) {
#{$prop}: $value;
}
}
@mixin selection($background-color: var(--lbry-white), $text-color: var(--lbry-black)) {
&::selection {
background-color: $background-color;
color: $text-color;
text-shadow: none;
}
&::-moz-selection {
background-color: $background-color;
color: $text-color;
text-shadow: none;
}
}
@mixin thumbnail {
&::before,
&::after {
content: '';
}
&::before {
float: left;
padding-top: var(--aspect-ratio-standard);
}
&::after {
clear: both;
display: block;
}
}
@mixin focus {
box-shadow: 0 0 0 3px var(--color-focus);
}
@mixin linkFocus {
background-color: var(--color-link-focus-bg);
box-shadow: 0 0 0 5px var(--color-link-focus-bg);
}
@mixin underline($text-color: var(--lbry-black), $whitespace-color: var(--lbry-white)) {
@include selection($text-color, $whitespace-color);
background-image: linear-gradient($whitespace-color, $whitespace-color),
linear-gradient($whitespace-color, $whitespace-color), linear-gradient($text-color, $text-color);
background-position: 0 88%, 100% 88%, 0 88%;
background-repeat: no-repeat, no-repeat, repeat-x;
background-size: 0.05rem 1px, 0.05rem 1px, 1px 1px;
box-decoration-break: clone;
display: inline;
text-decoration: none;
text-shadow: 0.03rem 0 $whitespace-color, -0.03rem 0 $whitespace-color, 0 0.03rem $whitespace-color,
0 -0.03rem $whitespace-color, 0.06rem 0 $whitespace-color, -0.06rem 0 $whitespace-color, 0.09rem 0 $whitespace-color,
-0.09rem 0 $whitespace-color, 0.12rem 0 $whitespace-color, -0.12rem 0 $whitespace-color, 0.15rem 0 $whitespace-color,
-0.15rem 0 $whitespace-color;
@-moz-document url-prefix() {
// sass-lint:disable-line empty-args
background-position: 0 90%, 100% 90%, 0 90%;
}
}
@mixin placeholder {
animation: pulse 2s infinite ease-in-out;
background-color: var(--color-placeholder-background);
border-radius: var(--card-radius);
border-width: 0;
}
@mixin mediaThumbHoverZoom {
.media__thumb,
img {
transition: all 0.2s ease;
}
&:hover {
.media__thumb,
img {
transform: scale(1.05);
}
}
}
@mixin handleClaimTileGifThumbnail($width) {
.ff-canvas,
.freezeframe-img {
height: calc(#{$width} * (9 / 16)) !important;
width: $width;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
@mixin handleClaimListGifThumbnail($width) {
.ff-canvas,
.freezeframe-img {
height: calc(#{$width} * (9 / 16)) !important;
width: $width;
}
}
@mixin handleChannelGif($size) {
height: $size;
width: $size;
.ff-canvas,
.freezeframe-img {
border-radius: var(--border-radius);
height: $size !important;
width: $size !important;
}
}

View file

@ -0,0 +1,245 @@
html {
box-sizing: border-box;
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
*,
*::before,
*::after {
margin: 0;
padding: 0;
border: none;
box-sizing: inherit;
outline: 0;
}
[disabled] {
pointer-events: none;
resize: none;
}
[readonly] {
cursor: not-allowed;
}
[for],
[role='button'],
[type='button'],
[type='checkbox'],
[type='file'],
[type='radio'],
[type='select'],
[type='submit'] {
cursor: pointer;
}
a,
area,
button,
[role='button'],
input,
label,
select,
summary,
textarea {
// Remove touch delay on supported devices
touch-action: manipulation;
}
button,
input,
select,
textarea {
background-color: transparent;
border-radius: 0;
font-family: inherit;
font-size: inherit;
font-weight: inherit;
-moz-appearance: none;
-webkit-appearance: none;
&:disabled {
cursor: default;
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: normal;
font-size: 1em;
}
ol,
ul {
list-style-position: inside;
> li {
list-style-position: inside;
}
}
table {
border-spacing: 0;
}
dd {
width: 80%;
float: left;
}
dl {
width: 100%;
overflow-x: scroll;
overflow-y: hidden;
}
dt {
width: 20%;
float: left;
}
img {
width: auto;
max-width: 100%;
height: auto;
max-height: 100%;
vertical-align: middle;
}
a {
text-decoration: none;
}
button {
background-color: transparent;
line-height: inherit;
&:not(:disabled) {
cursor: pointer;
}
&:disabled {
opacity: 0.3;
}
}
hr {
width: 100%;
height: 1px;
background-color: var(--color-gray-1);
}
input {
background-color: transparent;
color: inherit;
&::placeholder {
color: inherit;
opacity: 0.2;
}
&::-webkit-search-cancel-button {
-webkit-appearance: none;
}
}
select {
outline: none;
}
textarea {
width: 100%;
min-height: var(--spacing-xxl);
padding: var(--spacing-s);
// border-color should be added in apps for blur/focus
border: 1px solid;
&:not([disabled]) {
resize: vertical;
}
}
@media print {
// sass-lint:disable-block no-important
// Intelligent print styles
pre,
blockquote {
border: 1px solid var(--color-gray-5) !important;
page-break-inside: avoid !important;
}
tr,
img {
page-break-inside: avoid !important;
}
img {
max-width: 100% !important;
}
@page {
margin: 0.5cm !important;
}
p,
h2,
h3 {
orphans: 3 !important;
widows: 3 !important;
}
h2,
h3 {
page-break-after: avoid !important;
}
thead {
display: table-header-group !important;
}
// Faster, more stable printing
* {
background-color: transparent !important;
background-image: none !important;
color: var(--lbry-black) !important;
filter: none !important;
text-shadow: none !important;
}
p {
a {
&[href]::after {
// Show hypertext data for links and abbreviations
content: ' (' attr(href) ')' !important;
}
&[href^='javascript:'],
&[href^='#'] {
&::after {
content: '' !important;
}
}
}
abbr {
&[title]::after {
content: ' (' attr(title) ')' !important;
}
}
a,
abbr {
text-decoration: underline !important;
word-wrap: break-word !important;
}
}
}

View file

@ -0,0 +1,108 @@
// Both of these should probably die and become variables as well
$spacing-vertical: 2rem;
$spacing-width: 36px;
$breakpoint-xxsmall: 450px;
$breakpoint-xsmall: 600px;
$breakpoint-small: 900px;
$breakpoint-medium: 1150px;
$breakpoint-large: 1600px;
:root {
--border-radius: 10px;
--height-input: 2.5rem;
--height-button: 2.5rem;
--height-checkbox: 24px;
--height-radio: 24px;
--height-badge: 24px;
// Spacing
--spacing-xxs: calc(2rem / 5);
--spacing-xs: calc(2rem / 4);
--spacing-s: calc(2rem / 3);
--spacing-m: calc(2rem / 2);
--spacing-l: 2rem;
--spacing-xl: 3rem;
// Aspect ratio
--aspect-ratio-bluray: 41.6666666667%; // 12:5
--aspect-ratio-panavision: 36.3636363636%; // 11:4
--aspect-ratio-sd: 75%; // 4:3
--aspect-ratio-standard: 56.25%; // 16:9
// Type
--font-mono: 'Fira Code';
--font-sans: Inter;
--font-serif: Georgia;
--font-weight-base: 400;
--font-weight-light: 300;
--font-weight-bold: 700;
--font-base: 14px;
--font-body: 1rem;
--font-xxsmall: 0.65rem;
--font-xsmall: 0.7344rem;
--font-small: 0.8571rem;
--font-large: 1.3rem;
--font-title: 1.71rem;
--font-heading: 2.94rem;
// Width & spacing
--page-max-width: 1280px;
--page-max-width--filepage: 1700px;
--mac-titlebar-height: 24px;
--mobile: 600px;
--side-nav-width: 230px;
--side-nav-width--micro: 125px;
--spacing-main-padding: var(--spacing-xl);
--floating-viewer-width: 32rem;
--floating-viewer-height: 18rem; // 32 * 9/16
--floating-viewer-info-height: 5rem;
--floating-viewer-container-height: calc(var(--floating-viewer-height) + var(--floating-viewer-info-height));
--option-select-width: 8rem;
// Text
--text-max-width: 660px;
--text-link-padding: 4px;
// Tabs
--tab-indicator-size: 0.5rem;
// Header
// This is tied to the floating player so it knows where to attach to
// ui/component/fileRenderFloating/view.jsx
--header-height: 80px;
// Inline Player
--inline-player-max-height: calc(100vh - var(--header-height) - var(--spacing-l) * 2);
// Card
--card-radius: var(--border-radius);
--card-max-width: 1000px;
--card-box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
// Modal
--modal-width: 550px;
// Animation :)
--animation-duration: 0.2s;
--animation-style: ease-in-out;
// Image
--thumbnail-preview-height: 100px;
--thumbnail-preview-width: 177px;
--cover-photo-height: 210px;
--channel-thumbnail-width: 10rem;
--channel-thumbnail-width--small: 4rem;
--file-list-thumbnail-width: 10rem;
--tag-height: 1.5rem;
--livestream-comments-width: 30rem;
}
@media (max-width: $breakpoint-small) {
:root {
--font-body: 0.8rem;
}
}

View file

@ -0,0 +1,155 @@
:root {
// Color overrides
--color-primary: #fa6165;
--color-primary-alt: #fef1f6;
--color-primary-alt-2: #fb7e82;
--color-primary-alt-3: #fbcbdd;
--color-secondary: #f9902a;
--color-secondary-alt: #fee8d2;
--color-secondary-alt-2: #fefcf6;
// Structure
--color-border: #ededed;
--color-background: #fafafa;
--color-background-overlay: #21252980;
--color-card-background: #ffffff;
--color-card-background-highlighted: #fff5f5;
// Text
--color-text-selection-bg: var(--color-primary-alt);
--color-text-selection: var(--color-primary);
--color-text-error: var(--color-danger);
--color-text-empty: #999999;
--color-text-help: #999999;
--color-text-subtitle: #767676;
--color-text-warning: #212529;
--color-help-warning-bg: #fef3c7;
--color-text-warning--background: var(--lbry-yellow-1);
--color-blockquote: var(--color-gray-3);
--color-blockquote-bg: var(--color-gray-1);
--color-tooltip-bg: #222;
--color-tooltip-text: #fafafa;
// Header
--color-header-button: var(--color-button-alt-bg);
--color-header-background: #ffffff;
// Button
--color-button-alt-bg: var(--color-gray-1);
--color-button-alt-bg-hover: var(--color-gray-2);
--color-button-alt-text: black;
--color-button-primary-bg: var(--color-primary);
--color-button-primary-bg-hover: var(--color-primary-alt-2);
--color-button-primary-text: var(--color-primary-alt);
--color-button-primary-hover-text: var(--color-white);
--color-button-secondary-bg: var(--color-primary-alt);
--color-button-secondary-border: var(--color-primary-alt-3);
--color-button-secondary-text: var(--color-primary);
--color-button-secondary-bg-hover: var(--color-primary-alt-3);
--color-button-toggle-text: var(--color-primary);
--color-button-toggle-bg: var(--color-primary-alt);
--color-button-toggle-bg-hover: var(--color-primary-alt);
--color-button-border: var(--color-gray-3);
--color-link-active: var(--color-primary);
--color-link-focus-bg: #f1f1f1;
--color-link: var(--color-primary);
// Input
--color-input-bg-selected: var(--color-primary-alt);
--color-input-color: #111111;
--color-input-label: var(--color-gray-5);
--color-input-placeholder: #212529;
--color-input-bg: var(--color-gray-1);
--color-input-border: var(--color-border);
--color-input-border-active: var(--color-secondary);
--color-input-toggle: var(--color-secondary);
--color-input-toggle-bg: var(--color-gray-1);
--color-input-toggle-bg-hover: var(--color-secondary-alt);
--color-input-prefix-bg: var(--color-gray-2);
--color-input-prefix-border: var(--color-gray-5);
--select-toggle-background: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23212529'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
// Navigation
--color-navigation-icon: var(--color-gray-5);
--color-navigation-link: var(--color-gray-5);
--color-navigation-active: var(--color-primary-alt);
--color-navigation-active-text: var(--color-primary);
--color-navigation-hover: var(--color-gray-1);
--color-navigation-hover-text: #3f3f3f;
// Tags
--color-tag: var(--color-primary-alt-2);
--color-tag-bg: #f9f6f7;
--color-tag-hover: var(--color-button-alt-text);
--color-tag-bg-hover: var(--color-button-alt-bg-hover);
// Menu
--color-menu-background: var(--color-header-background);
--color-menu-icon: var(--color-navigation-link);
--color-menu-icon-active: var(--color-navigation-link);
--color-menu-background--selected: var(--color-secondary-alt);
--color-menu-background--active: var(--color-primary-alt);
// Comments
--color-comment-menu: #e0e0e0;
--color-comment-menu-hovering: #6a6a6a;
--color-comment-highlighted: #fff2d9;
--color-comment-threadline: var(--color-gray-1);
--color-comment-threadline-hover: var(--color-gray-4);
// Superchat
--color-superchat-text: var(--color-black);
--color-superchat: #fcd34d;
--color-superchat__light: #fcd34d50;
--color-superchat-2: #fde68a;
--color-superchat-3: #fef3c7;
--color-superchat-3__light: #fef3c750;
--color-superchat-4: #fffbeb;
// Color
--color-focus: #8dbff0;
--color-nag: #fa8700;
--color-error: #fcafca;
--color-notice: #fef3ca;
--color-purchased: var(--color-cost);
--color-purchased-alt: #ffebc2;
--color-purchased-text: black;
--color-thumbnail-background: var(--color-gray-1);
--color-spinner-light: #ffffff;
--color-spinner-dark: #212529;
--color-placeholder-background: #f0f0f0;
--color-file-viewer-background: var(--color-card-background);
--color-tabs-background: var(--color-card-background);
--color-tab-divider: var(--color-primary);
--color-modal-background: var(--color-card-background);
// Icons
--color-follow-bg: #ffd4da;
--color-follow-icon: #e2495e;
--color-view-bg: var(--color-secondary-alt);
--color-view-icon: var(--color-secondary);
// Editor
--color-editor-cursor: var(--color-text);
--color-editor-quote: #707070;
--color-editor-tag: #ea9400;
--color-editor-attr: #04b0f4;
--color-editor-string: #ff7451;
--color-editor-inline-code-fg: var(--color-text);
--color-editor-inline-code-fg-preview: #2e3439;
--color-editor-inline-code-bg: rgba(157, 161, 165, 0.3);
--color-editor-inline-code-bg-preview: #d0e8ff;
--color-editor-selected: #add6ff;
--color-editor-link: var(--color-link);
--color-editor-url: var(--color-editor-string);
--color-editor-hr: var(--color-editor-tag);
--color-editor-hr-preview: #cccccc;
// Ads
--color-ads-background: #fae5ff;
--color-ads-link: var(--color-link);
// Scrollbar
--color-scrollbar-thumb-bg: rgba(0, 0, 0, 0.2);
--color-scrollbar-track-bg: transparent;
}

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>`
);

Some files were not shown because too many files have changed in this diff Show more