From 7d5d7d3c55df04bf8c2121daea47288956683d62 Mon Sep 17 00:00:00 2001
From: zeppi
Date: Fri, 22 Oct 2021 10:46:59 -0400
Subject: [PATCH] remove web and embed
---
ui/component/fileRender/view.jsx | 9 +-
ui/component/router/view.jsx | 4 -
ui/component/viewers/videoViewer/index.js | 5 +-
.../viewers/videoViewer/internal/videojs.jsx | 16 +-
ui/component/viewers/videoViewer/view.jsx | 187 +-
ui/page/embedWrapper/index.js | 33 -
ui/page/embedWrapper/view.jsx | 126 -
ui/scss/all.scss | 1 -
ui/scss/component/_embed-player.scss | 67 -
web/.env.ody | 92 -
web/bundle-id.js | 8 -
.../fileViewerEmbeddedEnded/index.js | 10 -
.../fileViewerEmbeddedEnded/view.jsx | 82 -
web/effects/use-degraded-performance.js | 45 -
web/index.js | 39 -
web/lbry.js | 247 -
web/middleware/cache-control.js | 38 -
web/middleware/iframe-destroyer.js | 16 -
web/middleware/redirect.js | 67 -
web/package.json | 59 -
web/page/code2257/index.js | 2 -
web/page/code2257/view.jsx | 47 -
web/scss/lbrytv.scss | 71 -
web/scss/odysee.scss | 71 -
web/scss/themes/lbrytv/dark.scss | 160 -
web/scss/themes/lbrytv/light.scss | 2 -
.../themes/odysee/component/_file-render.scss | 757 ---
.../themes/odysee/component/_form-field.scss | 713 ---
web/scss/themes/odysee/dark.scss | 137 -
web/scss/themes/odysee/init/_base-theme.scss | 200 -
web/scss/themes/odysee/init/_color.scss | 57 -
web/scss/themes/odysee/init/_gui.scss | 521 --
web/scss/themes/odysee/init/_mixins.scss | 237 -
web/scss/themes/odysee/init/_reset.scss | 245 -
web/scss/themes/odysee/init/_vars.scss | 108 -
web/scss/themes/odysee/light.scss | 155 -
web/setup/publish.js | 104 -
web/src/category-metadata.js | 71 -
web/src/chainquery.js | 60 -
web/src/getHomepageJSON.js | 13 -
web/src/html.js | 370 --
web/src/lbryURI.js | 343 -
web/src/robots.js | 12 -
web/src/routes.js | 76 -
web/src/rss.js | 298 -
web/src/xml.js | 18 -
web/static/pwa/icon-180.png | Bin 12574 -> 0 bytes
web/static/pwa/icon-192.png | Bin 17186 -> 0 bytes
web/static/pwa/icon-512.png | Bin 61476 -> 0 bytes
web/static/pwa/icon.png | Bin 24241 -> 0 bytes
web/static/pwa/manifest.json | 28 -
web/static/pwa/serviceWorker.js | 14 -
web/stubs/electron.js | 43 -
web/stubs/fs.js | 17 -
web/theme.js | 6 -
web/webpack.config.js | 181 -
web/yarn.lock | 5692 -----------------
57 files changed, 65 insertions(+), 11915 deletions(-)
delete mode 100644 ui/page/embedWrapper/index.js
delete mode 100644 ui/page/embedWrapper/view.jsx
delete mode 100644 ui/scss/component/_embed-player.scss
delete mode 100644 web/.env.ody
delete mode 100644 web/bundle-id.js
delete mode 100644 web/component/fileViewerEmbeddedEnded/index.js
delete mode 100644 web/component/fileViewerEmbeddedEnded/view.jsx
delete mode 100644 web/effects/use-degraded-performance.js
delete mode 100644 web/index.js
delete mode 100644 web/lbry.js
delete mode 100644 web/middleware/cache-control.js
delete mode 100644 web/middleware/iframe-destroyer.js
delete mode 100644 web/middleware/redirect.js
delete mode 100644 web/package.json
delete mode 100644 web/page/code2257/index.js
delete mode 100644 web/page/code2257/view.jsx
delete mode 100644 web/scss/lbrytv.scss
delete mode 100644 web/scss/odysee.scss
delete mode 100644 web/scss/themes/lbrytv/dark.scss
delete mode 100644 web/scss/themes/lbrytv/light.scss
delete mode 100644 web/scss/themes/odysee/component/_file-render.scss
delete mode 100644 web/scss/themes/odysee/component/_form-field.scss
delete mode 100644 web/scss/themes/odysee/dark.scss
delete mode 100644 web/scss/themes/odysee/init/_base-theme.scss
delete mode 100644 web/scss/themes/odysee/init/_color.scss
delete mode 100644 web/scss/themes/odysee/init/_gui.scss
delete mode 100644 web/scss/themes/odysee/init/_mixins.scss
delete mode 100644 web/scss/themes/odysee/init/_reset.scss
delete mode 100644 web/scss/themes/odysee/init/_vars.scss
delete mode 100644 web/scss/themes/odysee/light.scss
delete mode 100644 web/setup/publish.js
delete mode 100644 web/src/category-metadata.js
delete mode 100644 web/src/chainquery.js
delete mode 100644 web/src/getHomepageJSON.js
delete mode 100644 web/src/html.js
delete mode 100644 web/src/lbryURI.js
delete mode 100644 web/src/robots.js
delete mode 100644 web/src/routes.js
delete mode 100644 web/src/rss.js
delete mode 100644 web/src/xml.js
delete mode 100644 web/static/pwa/icon-180.png
delete mode 100644 web/static/pwa/icon-192.png
delete mode 100644 web/static/pwa/icon-512.png
delete mode 100644 web/static/pwa/icon.png
delete mode 100644 web/static/pwa/manifest.json
delete mode 100644 web/static/pwa/serviceWorker.js
delete mode 100644 web/stubs/electron.js
delete mode 100644 web/stubs/fs.js
delete mode 100644 web/theme.js
delete mode 100644 web/webpack.config.js
delete mode 100644 web/yarn.lock
diff --git a/ui/component/fileRender/view.jsx b/ui/component/fileRender/view.jsx
index 19fa61983..b99529fb3 100644
--- a/ui/component/fileRender/view.jsx
+++ b/ui/component/fileRender/view.jsx
@@ -24,7 +24,6 @@ import PdfViewer from 'component/viewers/pdfViewer';
type Props = {
uri: string,
streamingUrl: string,
- embedded?: boolean,
contentType: string,
claim: StreamClaim,
currentTheme: string,
@@ -45,9 +44,8 @@ class FileRender extends React.PureComponent {
}
componentDidMount() {
- const { embedded } = this.props;
window.addEventListener('keydown', this.escapeListener, true);
- analytics.playerLoadedEvent(embedded);
+ analytics.playerLoadedEvent();
}
componentWillUnmount() {
@@ -146,13 +144,12 @@ class FileRender extends React.PureComponent {
}
render() {
- const { embedded, renderMode, className } = this.props;
+ const { renderMode, className } = this.props;
return (
diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx
index 27e6fc0ed..8a5e3b80d 100644
--- a/ui/component/router/view.jsx
+++ b/ui/component/router/view.jsx
@@ -37,7 +37,6 @@ import ChannelsPage from 'page/channels';
import CheckoutPage from 'page/checkoutPage';
import CreatorDashboard from 'page/creatorDashboard';
import DiscoverPage from 'page/discover';
-import EmbedWrapperPage from 'page/embedWrapper';
import FileListPublished from 'page/fileListPublished';
import FourOhFourPage from 'page/fourOhFour';
import HelpPage from 'page/help';
@@ -308,9 +307,6 @@ function AppRouter(props: Props) {
-
-
-
{/* Below need to go at the end to make sure we don't match any of our pages first */}
diff --git a/ui/component/viewers/videoViewer/index.js b/ui/component/viewers/videoViewer/index.js
index 5cccfc102..7e6a0a774 100644
--- a/ui/component/viewers/videoViewer/index.js
+++ b/ui/component/viewers/videoViewer/index.js
@@ -22,12 +22,11 @@ import { withRouter } from 'react-router';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { selectDaemonSettings, makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
import { toggleVideoTheaterMode, toggleAutoplayNext, doSetClientSetting } from 'redux/actions/settings';
-import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
+import { selectUser } from 'redux/selectors/user';
const select = (state, props) => {
const { search } = props.location;
const urlParams = new URLSearchParams(search);
- const autoplay = urlParams.get('autoplay');
const uri = props.uri;
// TODO: eventually this should be received from DB and not local state (https://github.com/lbryio/lbry-desktop/issues/6796)
const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(uri)(state);
@@ -53,7 +52,6 @@ const select = (state, props) => {
nextRecommendedUri,
previousListUri,
isMarkdownOrComment,
- autoplayIfEmbedded: Boolean(autoplay),
autoplayNext: makeSelectClientSetting(SETTINGS.AUTOPLAY_NEXT)(state),
volume: selectVolume(state),
muted: selectMute(state),
@@ -61,7 +59,6 @@ const select = (state, props) => {
thumbnail: makeSelectThumbnailForUri(uri)(state),
claim: makeSelectClaimForUri(uri)(state),
homepageData: selectHomepageData(state),
- authenticated: selectUserVerifiedEmail(state),
shareTelemetry: IS_WEB || selectDaemonSettings(state).share_usage_data,
isFloating: makeSelectIsPlayerFloating(props.location)(state),
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx
index 8b68a776e..7fb0cde92 100644
--- a/ui/component/viewers/videoViewer/internal/videojs.jsx
+++ b/ui/component/viewers/videoViewer/internal/videojs.jsx
@@ -48,12 +48,9 @@ type Props = {
poster: ?string,
onPlayerReady: (Player, any) => void,
isAudio: boolean,
- startMuted: boolean,
autoplay: boolean,
autoplaySetting: boolean,
- embedded: boolean,
toggleVideoTheaterMode: () => void,
- adUrl: ?string,
claimId: ?string,
userId: ?number,
// allowPreRoll: ?boolean,
@@ -163,18 +160,14 @@ export default React.memo
(function VideoJs(props: Props) {
const {
autoplay,
autoplaySetting,
- embedded,
- startMuted,
source,
sourceType,
poster,
isAudio,
onPlayerReady,
toggleVideoTheaterMode,
- adUrl,
claimId,
userId,
- // allowPreRoll,
shareTelemetry,
replay,
videoTheaterMode,
@@ -188,7 +181,6 @@ export default React.memo(function VideoJs(props: Props) {
const videoJsOptions = {
...VIDEO_JS_OPTIONS,
autoplay: autoplay,
- muted: startMuted,
sources: [
{
src: source,
@@ -361,10 +353,9 @@ export default React.memo(function VideoJs(props: Props) {
}
const onEnded = React.useCallback(() => {
- if (!adUrl) {
- showTapButton(TAP.NONE);
- }
- }, [adUrl]);
+ // not sure if this is necessary - used to be dependent on !adUrl
+ showTapButton(TAP.NONE);
+ }, []);
function handleKeyDown(e: KeyboardEvent) {
const player = playerRef.current;
@@ -565,7 +556,6 @@ export default React.memo(function VideoJs(props: Props) {
player.recsys({
videoId: claimId,
userId: userId,
- embedded: embedded,
});
}
diff --git a/ui/component/viewers/videoViewer/view.jsx b/ui/component/viewers/videoViewer/view.jsx
index 5619065f9..50f229231 100644
--- a/ui/component/viewers/videoViewer/view.jsx
+++ b/ui/component/viewers/videoViewer/view.jsx
@@ -1,29 +1,19 @@
// @flow
-import { ENABLE_PREROLL_ADS } from 'config';
-import * as PAGES from 'constants/pages';
-import * as ICONS from 'constants/icons';
-import React, { useEffect, useState, useContext, useCallback } from 'react';
+import React, { useEffect, useState, useCallback } from 'react';
import { stopContextMenu } from 'util/context-menu';
import type { Player } from './internal/videojs';
import VideoJs from './internal/videojs';
import analytics from 'analytics';
-import { EmbedContext } from 'page/embedWrapper/view';
import classnames from 'classnames';
import { FORCE_CONTENT_TYPE_PLAYER } from 'constants/claim';
import AutoplayCountdown from 'component/autoplayCountdown';
import usePrevious from 'effects/use-previous';
-import FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded';
-import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
import LoadingScreen from 'component/common/loading-screen';
import { addTheaterModeButton } from './internal/theater-mode';
import { addAutoplayNextButton } from './internal/autoplay-next';
import { addPlayNextButton } from './internal/play-next';
import { addPlayPreviousButton } from './internal/play-previous';
-import { useGetAds } from 'effects/use-get-ads';
-import Button from 'component/button';
-import I18nMessage from 'component/i18nMessage';
import { useHistory } from 'react-router';
-import { getAllIds } from 'util/buildHomepage';
import type { HomepageCat } from 'util/buildHomepage';
import { formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url';
@@ -43,7 +33,6 @@ type Props = {
volume: number,
uri: string,
autoplayNext: boolean,
- autoplayIfEmbedded: boolean,
desktopPlayStartTime?: number,
doAnalyticsView: (string, number) => Promise,
doAnalyticsBuffer: (string, any) => void,
@@ -85,7 +74,6 @@ function VideoViewer(props: Props) {
muted,
volume,
autoplayNext,
- autoplayIfEmbedded,
doAnalyticsView,
doAnalyticsBuffer,
claimRewards,
@@ -95,8 +83,6 @@ function VideoViewer(props: Props) {
toggleVideoTheaterMode,
toggleAutoplayNext,
setVideoPlaybackRate,
- homepageData,
- authenticated,
userId,
shareTelemetry,
isFloating,
@@ -108,27 +94,17 @@ function VideoViewer(props: Props) {
isMarkdownOrComment,
} = props;
const permanentUrl = claim && claim.permanent_url;
- const adApprovedChannelIds = homepageData ? getAllIds(homepageData) : [];
const claimId = claim && claim.claim_id;
- const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
const isAudio = contentType.includes('audio');
const forcePlayer = FORCE_CONTENT_TYPE_PLAYER.includes(contentType);
- const {
- push,
- location: { pathname },
- } = useHistory();
+ const { push } = useHistory();
const [doNavigate, setDoNavigate] = useState(false);
const [playNextUrl, setPlayNextUrl] = useState(true);
const [isPlaying, setIsPlaying] = useState(false);
const [ended, setEnded] = useState(false);
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
- const [isEndedEmbed, setIsEndedEmbed] = useState(false);
const vjsCallbackDataRef: any = React.useRef();
const previousUri = usePrevious(uri);
- const embedded = useContext(EmbedContext);
- const approvedVideo = Boolean(channelClaimId) && adApprovedChannelIds.includes(channelClaimId);
- const adsEnabled = ENABLE_PREROLL_ADS && !authenticated && !embedded && approvedVideo;
- const [adUrl, setAdUrl, isFetchingAd] = useGetAds(approvedVideo, adsEnabled);
/* isLoading was designed to show loading screen on first play press, rather than completely black screen, but
breaks because some browsers (e.g. Firefox) block autoplay but leave the player.play Promise pending */
const [isLoading, setIsLoading] = useState(false);
@@ -139,7 +115,6 @@ function VideoViewer(props: Props) {
useEffect(() => {
if (uri && previousUri && uri !== previousUri) {
setShowAutoplayCountdown(false);
- setIsEndedEmbed(false);
setIsLoading(false);
}
}, [uri, previousUri]);
@@ -147,10 +122,9 @@ function VideoViewer(props: Props) {
// Update vjsCallbackDataRef (ensures videojs callbacks are not using stale values):
useEffect(() => {
vjsCallbackDataRef.current = {
- embedded: embedded,
videoPlaybackRate: videoPlaybackRate,
};
- }, [embedded, videoPlaybackRate]);
+ }, [videoPlaybackRate]);
function doTrackingBuffered(e: Event, data: any) {
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
@@ -166,7 +140,7 @@ function VideoViewer(props: Props) {
const differenceToAdd = Date.now() - desktopPlayStartTime;
timeToStart += differenceToAdd;
}
- analytics.playerStartedEvent(embedded);
+ analytics.playerStartedEvent();
// convert bytes to bits, and then divide by seconds
const contentInBits = Number(claim.value.source.size) * 8;
@@ -178,7 +152,15 @@ function VideoViewer(props: Props) {
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
let playerPoweredBy = response.headers.get('x-powered-by') || '';
- analytics.videoStartEvent(claimId, timeToStart, playerPoweredBy, userId, claim.canonical_url, this, bitrateAsBitsPerSecond);
+ analytics.videoStartEvent(
+ claimId,
+ timeToStart,
+ playerPoweredBy,
+ userId,
+ claim.canonical_url,
+ this,
+ bitrateAsBitsPerSecond
+ );
});
doAnalyticsView(uri, timeToStart).then(() => {
@@ -248,28 +230,20 @@ function VideoViewer(props: Props) {
analytics.videoIsPlaying(false);
- if (adUrl) {
- setAdUrl(null);
- return;
- }
-
- if (embedded) {
- setIsEndedEmbed(true);
- } else if (!collectionId && autoplayNext) {
+ if (!collectionId && autoplayNext) {
setShowAutoplayCountdown(true);
} else if (collectionId) {
setDoNavigate(true);
}
clearPosition(uri);
- }, [adUrl, autoplayNext, clearPosition, collectionId, embedded, ended, setAdUrl, uri]);
+ }, [autoplayNext, clearPosition, collectionId, ended, uri]);
function onPlay(player) {
setEnded(false);
setIsLoading(false);
setIsPlaying(true);
setShowAutoplayCountdown(false);
- setIsEndedEmbed(false);
setReplay(false);
setDoNavigate(false);
analytics.videoIsPlaying(true, player);
@@ -296,7 +270,7 @@ function VideoViewer(props: Props) {
}
}
- const playerReadyDependencyList = [uri, adUrl, embedded, autoplayIfEmbedded];
+ const playerReadyDependencyList = [uri];
if (!IS_WEB) {
playerReadyDependencyList.push(desktopPlayStartTime);
}
@@ -312,43 +286,38 @@ function VideoViewer(props: Props) {
};
const onPlayerReady = useCallback((player: Player, videoNode: any) => {
- if (!embedded) {
- setVideoNode(videoNode);
- player.muted(muted);
- player.volume(volume);
- player.playbackRate(videoPlaybackRate);
- if (!isMarkdownOrComment) {
- addTheaterModeButton(player, toggleVideoTheaterMode);
- if (collectionId) {
- addPlayNextButton(player, doPlayNext);
- addPlayPreviousButton(player, doPlayPrevious);
- } else {
- addAutoplayNextButton(player, toggleAutoplayNext, autoplayNext);
- }
+ setVideoNode(videoNode);
+ player.muted(muted);
+ player.volume(volume);
+ player.playbackRate(videoPlaybackRate);
+ if (!isMarkdownOrComment) {
+ addTheaterModeButton(player, toggleVideoTheaterMode);
+ if (collectionId) {
+ addPlayNextButton(player, doPlayNext);
+ addPlayPreviousButton(player, doPlayPrevious);
+ } else {
+ addAutoplayNextButton(player, toggleAutoplayNext, autoplayNext);
}
}
- const shouldPlay = !embedded || autoplayIfEmbedded;
// https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
- if (shouldPlay) {
- const playPromise = player.play();
- const timeoutPromise = new Promise((resolve, reject) =>
- setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT)
- );
+ const playPromise = player.play();
+ const timeoutPromise = new Promise((resolve, reject) =>
+ setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT)
+ );
- Promise.race([playPromise, timeoutPromise]).catch((error) => {
- if (typeof error === 'object' && error.name && error.name === 'NotAllowedError') {
- if (player.autoplay() && !player.muted()) {
- // player.muted(true);
- // another version had player.play()
- }
+ Promise.race([playPromise, timeoutPromise]).catch((error) => {
+ if (typeof error === 'object' && error.name && error.name === 'NotAllowedError') {
+ if (player.autoplay() && !player.muted()) {
+ // player.muted(true);
+ // another version had player.play()
}
- setIsLoading(false);
- setIsPlaying(false);
- });
- }
+ }
+ setIsLoading(false);
+ setIsPlaying(false);
+ });
- setIsLoading(shouldPlay); // if we are here outside of an embed, we're playing
+ setIsLoading(true); // if we are here outside of an embed, we're playing
// PR: #5535
// Move the restoration to a later `loadedmetadata` phase to counter the
@@ -398,7 +367,6 @@ function VideoViewer(props: Props) {
@@ -409,64 +377,25 @@ function VideoViewer(props: Props) {
doReplay={() => setReplay(true)}
/>
)}
- {isEndedEmbed && }
- {embedded && !isEndedEmbed && }
{/* disable this loading behavior because it breaks when player.play() promise hangs */}
{isLoading && }
-
- {!isFetchingAd && adUrl && (
- <>
-
- {__('Advertisement')}{' '}
- setAdUrl(null)}
- />
-
-
-
- ),
- }}
- >
- %sign_up% to turn ads off.
-
-
- >
- )}
-
- {!isFetchingAd && (
-
- )}
+
);
}
diff --git a/ui/page/embedWrapper/index.js b/ui/page/embedWrapper/index.js
deleted file mode 100644
index cb25a0658..000000000
--- a/ui/page/embedWrapper/index.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { connect } from 'react-redux';
-import EmbedWrapperPage from './view';
-import { makeSelectClaimForUri, makeSelectIsUriResolving } from 'redux/selectors/claims';
-import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
-import { doResolveUri } from 'redux/actions/claims';
-import { buildURI } from 'util/lbryURI';
-import { doPlayUri } from 'redux/actions/content';
-import { makeSelectCostInfoForUri, doFetchCostInfoForUri, selectBlackListedOutpoints } from 'lbryinc';
-
-const select = (state, props) => {
- const { match } = props;
- const { params } = match;
- const { claimName, claimId } = params;
- const uri = claimName ? buildURI({ claimName, claimId }) : '';
- return {
- uri,
- claim: makeSelectClaimForUri(uri)(state),
- costInfo: makeSelectCostInfoForUri(uri)(state),
- streamingUrl: makeSelectStreamingUrlForUri(uri)(state),
- isResolvingUri: makeSelectIsUriResolving(uri)(state),
- blackListedOutpoints: selectBlackListedOutpoints(state),
- };
-};
-
-const perform = (dispatch) => {
- return {
- resolveUri: (uri) => dispatch(doResolveUri(uri)),
- doPlayUri: (uri) => dispatch(doPlayUri(uri)),
- doFetchCostInfoForUri: (uri) => dispatch(doFetchCostInfoForUri(uri)),
- };
-};
-
-export default connect(select, perform)(EmbedWrapperPage);
diff --git a/ui/page/embedWrapper/view.jsx b/ui/page/embedWrapper/view.jsx
deleted file mode 100644
index 78a5ce8fe..000000000
--- a/ui/page/embedWrapper/view.jsx
+++ /dev/null
@@ -1,126 +0,0 @@
-// @flow
-import { SITE_NAME } from 'config';
-import React, { useEffect } from 'react';
-import classnames from 'classnames';
-import FileRender from 'component/fileRender';
-import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
-import Spinner from 'component/spinner';
-import Button from 'component/button';
-import Card from 'component/common/card';
-import { formatLbryUrlForWeb } from 'util/url';
-import { useHistory } from 'react-router';
-
-type Props = {
- uri: string,
- resolveUri: (string) => void,
- claim: Claim,
- doPlayUri: (string) => void,
- costInfo: any,
- streamingUrl: string,
- doFetchCostInfoForUri: (string) => void,
- isResolvingUri: boolean,
- blackListedOutpoints: Array<{
- txid: string,
- nout: number,
- }>,
-};
-
-export const EmbedContext = React.createContext();
-const EmbedWrapperPage = (props: Props) => {
- const {
- resolveUri,
- claim,
- uri,
- doPlayUri,
- costInfo,
- streamingUrl,
- doFetchCostInfoForUri,
- isResolvingUri,
- blackListedOutpoints,
- } = props;
-
- const {
- location: { search },
- } = useHistory();
- const urlParams = new URLSearchParams(search);
- const embedLightBackground = urlParams.get('embedBackgroundLight');
- const haveClaim = Boolean(claim);
- const readyToDisplay = claim && streamingUrl;
- const loading = !claim && isResolvingUri;
- const noContentFound = !claim && !isResolvingUri;
- const isPaidContent = costInfo && costInfo.cost > 0;
- const contentLink = formatLbryUrlForWeb(uri);
- const signingChannel = claim && claim.signing_channel;
- const isClaimBlackListed =
- claim &&
- blackListedOutpoints &&
- blackListedOutpoints.some(
- (outpoint) =>
- (signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
- (outpoint.txid === claim.txid && outpoint.nout === claim.nout)
- );
-
- useEffect(() => {
- if (resolveUri && uri && !haveClaim) {
- resolveUri(uri);
- }
- if (uri && haveClaim && costInfo && costInfo.cost === 0) {
- doPlayUri(uri);
- }
- }, [resolveUri, uri, doPlayUri, haveClaim, costInfo]);
-
- useEffect(() => {
- if (haveClaim && uri && doFetchCostInfoForUri) {
- doFetchCostInfoForUri(uri);
- }
- }, [uri, haveClaim, doFetchCostInfoForUri]);
-
- if (isClaimBlackListed) {
- return (
-
-
-
- }
- />
- );
- }
-
- return (
-
-
- {readyToDisplay ? (
-
- ) : (
-
-
-
-
- {loading &&
}
- {noContentFound &&
{__('No content found.')} }
- {isPaidContent && (
-
-
{__('Paid content cannot be embedded.')}
-
-
-
-
- )}
-
-
- )}
-
-
- );
-};
-
-export default EmbedWrapperPage;
diff --git a/ui/scss/all.scss b/ui/scss/all.scss
index c654e7b0d..77ca90423 100644
--- a/ui/scss/all.scss
+++ b/ui/scss/all.scss
@@ -20,7 +20,6 @@
@import 'component/comments';
@import 'component/content';
@import 'component/dat-gui';
-@import 'component/embed-player';
@import 'component/expandable';
@import 'component/expanding-details';
@import 'component/file-drop';
diff --git a/ui/scss/component/_embed-player.scss b/ui/scss/component/_embed-player.scss
deleted file mode 100644
index 59ee8119f..000000000
--- a/ui/scss/component/_embed-player.scss
+++ /dev/null
@@ -1,67 +0,0 @@
-.embed__wrapper {
- height: 100vh;
- width: 100vw;
- position: relative;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- align-items: center;
- background-color: var(--color-black);
-}
-
-.embed__wrapper--light-background {
- @extend .embed__wrapper;
-
- .vjs-poster,
- video {
- background-color: var(--color-white);
- }
-}
-
-.embed__inline-button {
- @include thumbnail;
- position: relative;
- background-position: 50% 50%;
- background-repeat: no-repeat;
- background-size: 100%;
- width: 100%;
- height: auto;
- display: flex;
- justify-content: center;
- align-items: center;
- border-top-left-radius: var(--border-radius);
- border-top-right-radius: var(--border-radius);
- background-color: var(--color-black);
-
- @media (max-width: $breakpoint-small) {
- height: 200px;
- }
-}
-
-.embed__inline-button-preview {
- @extend .embed__inline-button;
- background-color: var(--color-editor-inline-code-bg);
- width: 50%;
-}
-
-.embed__loading {
- width: 100%;
- height: 100%;
-}
-
-.embed__loading-text {
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--color-white);
-
- h1 {
- font-size: var(--font-large);
- }
-}
-
-.embed__overlay-logo {
- max-height: 2rem;
- max-width: 7rem;
-}
diff --git a/web/.env.ody b/web/.env.ody
deleted file mode 100644
index 2d9e1313e..000000000
--- a/web/.env.ody
+++ /dev/null
@@ -1,92 +0,0 @@
-# Copy this file to .env to make modifications
-
-# Base config
-
-WEBPACK_WEB_PORT=9090
-WEBPACK_ELECTRON_PORT=9091
-WEB_SERVER_PORT=1337
-
-WELCOME_VERSION=1.0
-
-# Custom Site info
-DOMAIN=lbry.tv
-URL=https://lbry.tv
-
-# UI
-SITE_TITLE=lbry.tv
-SITE_NAME=local.lbry.tv
-SITE_DESCRIPTION=Meet LBRY, an open, free, and community-controlled content wonderland.
-LOGO_TITLE=local.lbry.tv
-
-##### ODYSEE SETTINGS #######
-
-MATOMO_URL=https://analytics.lbry.com/
-MATOMO_ID=4
-
-# Base config
-WEBPACK_WEB_PORT=9090
-WEBPACK_ELECTRON_PORT=9091
-WEB_SERVER_PORT=1337
-
-## APIS
-LBRY_API_URL=https://api.odysee.com
-#LBRY_WEB_API=https://api.na-backend.odysee.com
-#LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
-# deprecated:
-#LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
-#COMMENT_SERVER_API=https://comments.lbry.com/api/v2
-WELCOME_VERSION=1.0
-
-# STRIPE
-STRIPE_PUBLIC_KEY='pk_live_e8M4dRNnCCbmpZzduEUZBgJO'
-
-## UI
-
-LOADING_BAR_COLOR=#e50054
-
-# IMAGE ASSETS
-YRBL_HAPPY_IMG_URL=https://spee.ch/spaceman-happy:a.png
-YRBL_SAD_IMG_URL=https://spee.ch/spaceman-sad:d.png
-LOGIN_IMG_URL=https://spee.ch/login:b.png
-LOGO=https://spee.ch/odysee-logo-png:3.png
-LOGO_TEXT_LIGHT=https://spee.ch/odysee-white-png:f.png
-LOGO_TEXT_DARK=https://spee.ch/odysee-png:2.png
-AVATAR_DEFAULT=https://spee.ch/spaceman-png:2.png
-FAVICON=https://spee.ch/favicon-png:c.png
-
-# LOCALE
-DEFAULT_LANGUAGE=en
-
-## LINKED CONTENT WHITELIST
-KNOWN_APP_DOMAINS=open.lbry.com,lbry.tv,lbry.lat,odysee.com
-
-## CUSTOM CONTENT
-# If the following is true, copy custom/homepage.example.js to custom/homepage.js and modify
-CUSTOM_HOMEPAGE=true
-
-# Add channels to auto-follow on firstrun (space delimited)
-AUTO_FOLLOW_CHANNELS=lbry://@Odysee#80d2590ad04e36fb1d077a9b9e3a8bba76defdf8 lbry://@OdyseeHelp#b58dfaeab6c70754d792cdd9b56ff59b90aea334
-
-## FEATURES AND LIMITS
-SIMPLE_SITE=true
-BRANDED_SITE=odysee
-# SIMPLE_SITE REPLACEMENTS
-ENABLE_MATURE=false
-ENABLE_UI_NOTIFICATIONS=true
-ENABLE_WILD_WEST=true
-SHOW_TAGS_INTRO=false
-
-# CENTRALIZED FEATURES
-ENABLE_COMMENT_REACTIONS=true
-ENABLE_FILE_REACTIONS=true
-ENABLE_CREATOR_REACTIONS=true
-ENABLE_NO_SOURCE_CLAIMS=true
-ENABLE_PREROLL_ADS=false
-SHOW_ADS=true
-
-CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS=4
-CHANNEL_STAKED_LEVEL_LIVESTREAM=3
-WEB_PUBLISH_SIZE_LIMIT_GB=4
-
-#SEARCH TYPES - comma-delimited
-LIGHTHOUSE_DEFAULT_TYPES=audio,video
diff --git a/web/bundle-id.js b/web/bundle-id.js
deleted file mode 100644
index ac768afaf..000000000
--- a/web/bundle-id.js
+++ /dev/null
@@ -1,8 +0,0 @@
-const { v4: uuid } = require('uuid');
-const jsBundleId = uuid();
-
-function getJsBundleId() {
- return jsBundleId;
-}
-
-module.exports = { getJsBundleId };
diff --git a/web/component/fileViewerEmbeddedEnded/index.js b/web/component/fileViewerEmbeddedEnded/index.js
deleted file mode 100644
index 4a53330ea..000000000
--- a/web/component/fileViewerEmbeddedEnded/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { connect } from 'react-redux';
-import fileViewerEmbeddedEnded from './view';
-import { selectUserVerifiedEmail } from 'redux/selectors/user';
-import { makeSelectTagInClaimOrChannelForUri } from 'redux/selectors/claims';
-import { PREFERENCE_EMBED } from 'constants/tags';
-
-export default connect((state, props) => ({
- isAuthenticated: selectUserVerifiedEmail(state),
- preferEmbed: makeSelectTagInClaimOrChannelForUri(props.uri, PREFERENCE_EMBED)(state),
-}))(fileViewerEmbeddedEnded);
diff --git a/web/component/fileViewerEmbeddedEnded/view.jsx b/web/component/fileViewerEmbeddedEnded/view.jsx
deleted file mode 100644
index 69aac3e9f..000000000
--- a/web/component/fileViewerEmbeddedEnded/view.jsx
+++ /dev/null
@@ -1,82 +0,0 @@
-// @flow
-import React from 'react';
-import Button from 'component/button';
-import * as ICONS from 'constants/icons';
-import { formatLbryUrlForWeb } from 'util/url';
-import { withRouter } from 'react-router';
-import { URL, SITE_NAME } from 'config';
-import Logo from 'component/logo';
-
-type Props = {
- uri: string,
- isAuthenticated: boolean,
- preferEmbed: boolean,
-};
-
-function FileViewerEmbeddedEnded(props: Props) {
- const { uri, isAuthenticated, preferEmbed } = props;
-
- const prompts = isAuthenticated
- ? {
- discuss_auth: `Continue the discussion on ${SITE_NAME}`,
- tip_auth: 'Always tip your creators',
- }
- : {
- bigtech_unauth: 'Together, we can take back control from big tech',
- discuss_unauth: `Continue the discussion on ${SITE_NAME}`,
- find_unauth: `Find more great content on ${SITE_NAME}`,
- a_b_unauth: "We test a lot of messages here. Wouldn't it be funny if the one telling you that did the best?",
- earn_unauth: `Join ${SITE_NAME} and earn to watch.`,
- blockchain_unauth: "Now if anyone asks, you can say you've used a blockchain.",
- };
-
- const promptKeys = Object.keys(prompts);
- const promptKey = promptKeys[Math.floor(Math.random() * promptKeys.length)];
- // $FlowFixMe
- const prompt = prompts[promptKey];
- const lbrytvLink = `${URL}${formatLbryUrlForWeb(uri)}?src=${promptKey}`;
- const showReplay = Boolean(window.player);
-
- return (
-
-
-
-
-
-
-
-
-
- <>
- {showReplay && (
- {
- if (window.player) window.player.play();
- }}
- />
- )}
- {!preferEmbed && (
- <>
-
- {!isAuthenticated && (
-
- )}
- >
- )}
- >
-
-
- );
-}
-
-export default withRouter(FileViewerEmbeddedEnded);
diff --git a/web/effects/use-degraded-performance.js b/web/effects/use-degraded-performance.js
deleted file mode 100644
index 5c1e26c36..000000000
--- a/web/effects/use-degraded-performance.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { SDK_API_PATH } from 'ui';
-import { useEffect } from 'react';
-import { getAuthToken } from 'util/saved-passwords';
-import { X_LBRY_AUTH_TOKEN } from 'constants/token';
-
-import fetchWithTimeout from 'util/fetch';
-
-const STATUS_TIMEOUT_LIMIT = 10000;
-export const STATUS_OK = 'ok';
-export const STATUS_DEGRADED = 'degraded';
-export const STATUS_FAILING = 'failing';
-export const STATUS_DOWN = 'down';
-
-const getParams = (user) => {
- const headers = {};
- const token = getAuthToken();
- if (token && user && user.has_verified_email) {
- headers[X_LBRY_AUTH_TOKEN] = token;
- }
- const params = { headers };
- return params;
-};
-
-export function useDegradedPerformance(onDegradedPerformanceCallback, user) {
- const hasUser = user !== undefined && user !== null;
-
- useEffect(() => {
- if (hasUser) {
- // The status endpoint is the only endpoint at "v2" currently
- // This should be moved into the config once more endpoints are using it
- const STATUS_ENDPOINT = `${SDK_API_PATH}/status`.replace('v1', 'v2');
-
- fetchWithTimeout(STATUS_TIMEOUT_LIMIT, fetch(STATUS_ENDPOINT, getParams(user)))
- .then((response) => response.json())
- .then((status) => {
- if (status.general_state !== STATUS_OK) {
- onDegradedPerformanceCallback(STATUS_FAILING);
- }
- })
- .catch(() => {
- onDegradedPerformanceCallback(STATUS_FAILING);
- });
- }
- }, [hasUser]);
-}
diff --git a/web/index.js b/web/index.js
deleted file mode 100644
index b7ee5d01e..000000000
--- a/web/index.js
+++ /dev/null
@@ -1,39 +0,0 @@
-const config = require('../config');
-const path = require('path');
-const Koa = require('koa');
-const serve = require('koa-static');
-const logger = require('koa-logger');
-const router = require('./src/routes');
-const redirectMiddleware = require('./middleware/redirect');
-const cacheControlMiddleware = require('./middleware/cache-control');
-const iframeDestroyerMiddleware = require('./middleware/iframe-destroyer');
-
-const app = new Koa();
-const DIST_ROOT = path.resolve(__dirname, 'dist');
-
-app.proxy = true;
-
-app.use(async (ctx, next) => {
- try {
- await next();
- } catch (err) {
- console.log('error: ', err);
- ctx.status = err.status || 500;
- ctx.body = err.message;
- }
-});
-
-app.use(logger());
-app.use(cacheControlMiddleware);
-app.use(redirectMiddleware);
-app.use(iframeDestroyerMiddleware);
-
-// Check if the request url matches any assets inside of /dist
-app.use(serve(DIST_ROOT, {
- maxage: 3600000, // set a cache time of one hour, helpful for mobile dev
-}));
-
-app.use(serve(DIST_ROOT)); // Check if the request url matches any assets inside of /dist
-
-app.use(router.routes());
-app.listen(config.WEB_SERVER_PORT, () => `Server up at localhost:${config.WEB_SERVER_PORT}`);
diff --git a/web/lbry.js b/web/lbry.js
deleted file mode 100644
index 16059abf4..000000000
--- a/web/lbry.js
+++ /dev/null
@@ -1,247 +0,0 @@
-// Disabled flow in this copy. This copy is for uncompiled web server ES5 require()s.
-require('proxy-polyfill');
-
-const CHECK_DAEMON_STARTED_TRY_NUMBER = 200;
-//
-// Basic LBRY sdk connection config
-// Offers a proxy to call LBRY sdk methods
-
-//
-const Lbry = {
- isConnected: false,
- connectPromise: null,
- daemonConnectionString: 'http://localhost:5279',
- alternateConnectionString: '',
- methodsUsingAlternateConnectionString: [],
- apiRequestHeaders: { 'Content-Type': 'application/json-rpc' },
-
- // Allow overriding daemon connection string (e.g. to `/api/proxy` for lbryweb)
- setDaemonConnectionString: (value) => {
- Lbry.daemonConnectionString = value;
- },
-
- setApiHeader: (key, value) => {
- Lbry.apiRequestHeaders = Object.assign(Lbry.apiRequestHeaders, { [key]: value });
- },
-
- unsetApiHeader: (key) => {
- Object.keys(Lbry.apiRequestHeaders).includes(key) && delete Lbry.apiRequestHeaders['key'];
- },
- // Allow overriding Lbry methods
- overrides: {},
- setOverride: (methodName, newMethod) => {
- Lbry.overrides[methodName] = newMethod;
- },
- getApiRequestHeaders: () => Lbry.apiRequestHeaders,
-
- // Returns a human readable media type based on the content type or extension of a file that is returned by the sdk
- getMediaType: (contentType, fileName) => {
- if (fileName) {
- const formats = [
- [/\.(mp4|m4v|webm|flv|f4v|ogv)$/i, 'video'],
- [/\.(mp3|m4a|aac|wav|flac|ogg|opus)$/i, 'audio'],
- [/\.(jpeg|jpg|png|gif|svg|webp)$/i, 'image'],
- [/\.(h|go|ja|java|js|jsx|c|cpp|cs|css|rb|scss|sh|php|py)$/i, 'script'],
- [/\.(html|json|csv|txt|log|md|markdown|docx|pdf|xml|yml|yaml)$/i, 'document'],
- [/\.(pdf|odf|doc|docx|epub|org|rtf)$/i, 'e-book'],
- [/\.(stl|obj|fbx|gcode)$/i, '3D-file'],
- [/\.(cbr|cbt|cbz)$/i, 'comic-book'],
- [/\.(lbry)$/i, 'application'],
- ];
-
- const res = formats.reduce((ret, testpair) => {
- switch (testpair[0].test(ret)) {
- case true:
- return testpair[1];
- default:
- return ret;
- }
- }, fileName);
- return res === fileName ? 'unknown' : res;
- } else if (contentType) {
- // $FlowFixMe
- return /^[^/]+/.exec(contentType)[0];
- }
-
- return 'unknown';
- },
-
- //
- // Lbry SDK Methods
- // https://lbry.tech/api/sdk
- //
- status: (params = {}) => daemonCallWithResult('status', params),
- stop: () => daemonCallWithResult('stop', {}),
- version: () => daemonCallWithResult('version', {}),
-
- // Claim fetching and manipulation
- resolve: (params) => daemonCallWithResult('resolve', params),
- get: (params) => daemonCallWithResult('get', params),
- claim_search: (params) => daemonCallWithResult('claim_search', params),
- claim_list: (params) => daemonCallWithResult('claim_list', params),
- channel_create: (params) => daemonCallWithResult('channel_create', params),
- channel_update: (params) => daemonCallWithResult('channel_update', params),
- channel_import: (params) => daemonCallWithResult('channel_import', params),
- channel_list: (params) => daemonCallWithResult('channel_list', params),
- stream_abandon: (params) => daemonCallWithResult('stream_abandon', params),
- stream_list: (params) => daemonCallWithResult('stream_list', params),
- channel_abandon: (params) => daemonCallWithResult('channel_abandon', params),
- channel_sign: (params) => daemonCallWithResult('channel_sign', params),
- support_create: (params) => daemonCallWithResult('support_create', params),
- support_list: (params) => daemonCallWithResult('support_list', params),
- stream_repost: (params) => daemonCallWithResult('stream_repost', params),
- collection_resolve: (params) => daemonCallWithResult('collection_resolve', params),
- collection_list: (params) => daemonCallWithResult('collection_list', params),
- collection_create: (params) => daemonCallWithResult('collection_create', params),
- collection_update: (params) => daemonCallWithResult('collection_update', params),
-
- // File fetching and manipulation
- file_list: (params = {}) => daemonCallWithResult('file_list', params),
- file_delete: (params = {}) => daemonCallWithResult('file_delete', params),
- file_set_status: (params = {}) => daemonCallWithResult('file_set_status', params),
- blob_delete: (params = {}) => daemonCallWithResult('blob_delete', params),
- blob_list: (params = {}) => daemonCallWithResult('blob_list', params),
-
- // Wallet utilities
- wallet_balance: (params = {}) => daemonCallWithResult('wallet_balance', params),
- wallet_decrypt: () => daemonCallWithResult('wallet_decrypt', {}),
- wallet_encrypt: (params = {}) => daemonCallWithResult('wallet_encrypt', params),
- wallet_unlock: (params = {}) => daemonCallWithResult('wallet_unlock', params),
- wallet_list: (params = {}) => daemonCallWithResult('wallet_list', params),
- wallet_send: (params = {}) => daemonCallWithResult('wallet_send', params),
- wallet_status: (params = {}) => daemonCallWithResult('wallet_status', params),
- address_is_mine: (params = {}) => daemonCallWithResult('address_is_mine', params),
- address_unused: (params = {}) => daemonCallWithResult('address_unused', params),
- address_list: (params = {}) => daemonCallWithResult('address_list', params),
- transaction_list: (params = {}) => daemonCallWithResult('transaction_list', params),
- utxo_release: (params = {}) => daemonCallWithResult('utxo_release', params),
- support_abandon: (params = {}) => daemonCallWithResult('support_abandon', params),
- purchase_list: (params = {}) => daemonCallWithResult('purchase_list', params),
- txo_list: (params = {}) => daemonCallWithResult('txo_list', params),
-
- sync_hash: (params = {}) => daemonCallWithResult('sync_hash', params),
- sync_apply: (params = {}) => daemonCallWithResult('sync_apply', params),
-
- // Preferences
- preference_get: (params = {}) => daemonCallWithResult('preference_get', params),
- preference_set: (params = {}) => daemonCallWithResult('preference_set', params),
-
- // Comments
- comment_list: (params = {}) => daemonCallWithResult('comment_list', params),
- comment_create: (params = {}) => daemonCallWithResult('comment_create', params),
- comment_hide: (params = {}) => daemonCallWithResult('comment_hide', params),
- comment_abandon: (params = {}) => daemonCallWithResult('comment_abandon', params),
- comment_update: (params = {}) => daemonCallWithResult('comment_update', params),
-
- // Connect to the sdk
- connect: () => {
- if (Lbry.connectPromise === null) {
- Lbry.connectPromise = new Promise((resolve, reject) => {
- let tryNum = 0;
- // Check every half second to see if the daemon is accepting connections
- function checkDaemonStarted() {
- tryNum += 1;
- Lbry.status()
- .then(resolve)
- .catch(() => {
- if (tryNum <= CHECK_DAEMON_STARTED_TRY_NUMBER) {
- setTimeout(checkDaemonStarted, tryNum < 50 ? 400 : 1000);
- } else {
- reject(new Error('Unable to connect to LBRY'));
- }
- });
- }
-
- checkDaemonStarted();
- });
- }
-
- // Flow thinks this could be empty, but it will always reuturn a promise
- // $FlowFixMe
- return Lbry.connectPromise;
- },
-
- publish: (params = {}) =>
- new Promise((resolve, reject) => {
- if (Lbry.overrides.publish) {
- Lbry.overrides.publish(params).then(resolve, reject);
- } else {
- apiCall('publish', params, resolve, reject);
- }
- }),
-};
-
-function checkAndParse(response) {
- if (response.status >= 200 && response.status < 300) {
- return response.json();
- }
- return response.json().then((json) => {
- let error;
- if (json.error) {
- const errorMessage = typeof json.error === 'object' ? json.error.message : json.error;
- error = new Error(errorMessage);
- } else {
- error = new Error('Protocol error with unknown response signature');
- }
- return Promise.reject(error);
- });
-}
-
-function apiCall(method, params, resolve, reject) {
- const counter = new Date().getTime();
- const options = {
- method: 'POST',
- headers: Lbry.apiRequestHeaders,
- body: JSON.stringify({
- jsonrpc: '2.0',
- method,
- params,
- id: counter,
- }),
- };
-
- const connectionString = Lbry.methodsUsingAlternateConnectionString.includes(method)
- ? Lbry.alternateConnectionString
- : Lbry.daemonConnectionString;
- return fetch(connectionString + '?m=' + method, options)
- .then(checkAndParse)
- .then((response) => {
- const error = response.error || (response.result && response.result.error);
-
- if (error) {
- return reject(error);
- }
- return resolve(response.result);
- })
- .catch(reject);
-}
-
-function daemonCallWithResult(name, params = {}) {
- return new Promise((resolve, reject) => {
- apiCall(
- name,
- params,
- (result) => {
- resolve(result);
- },
- reject
- );
- });
-}
-
-// This is only for a fallback
-// If there is a Lbry method that is being called by an app, it should be added to /flow-typed/Lbry.js
-const lbryProxy = new Proxy(Lbry, {
- get(target, name) {
- if (name in target) {
- return target[name];
- }
-
- return (params = {}) =>
- new Promise((resolve, reject) => {
- apiCall(name, params, resolve, reject);
- });
- },
-});
-
-module.exports = { lbryProxy, apiCall };
diff --git a/web/middleware/cache-control.js b/web/middleware/cache-control.js
deleted file mode 100644
index 800b2b815..000000000
--- a/web/middleware/cache-control.js
+++ /dev/null
@@ -1,38 +0,0 @@
-const SIX_MONTHS_IN_SECONDS = 15552000;
-
-const STATIC_ASSET_PATHS = [
- '/public/font/font-v1.css',
- '/public/font/v1/300.woff',
- '/public/font/v1/300i.woff',
- '/public/font/v1/400.woff',
- '/public/font/v1/400i.woff',
- '/public/font/v1/700.woff',
- '/public/font/v1/700i.woff',
- '/public/favicon.png', // LBRY icon
- '/public/favicon-spaceman.png',
- '/public/img/busy.gif',
- '/public/img/fileRenderPlaceholder.png',
- '/public/img/gerbil-happy.png',
- '/public/img/gerbil-sad.png',
- '/public/img/placeholder.png',
- '/public/img/placeholderTx.gif',
- '/public/img/thumbnail-broken.png',
- '/public/img/thumbnail-missing.png',
- '/public/img/total-background.png',
-];
-
-async function redirectMiddleware(ctx, next) {
- const {
- request: { url },
- } = ctx;
-
- const HASHED_JS_REGEX = /^\/public\/.*[a-fA-F0-9]{12}\.js$/i;
-
- if (STATIC_ASSET_PATHS.includes(url) || HASHED_JS_REGEX.test(url)) {
- ctx.set('Cache-Control', `public, max-age=${SIX_MONTHS_IN_SECONDS}`);
- }
-
- return next();
-}
-
-module.exports = redirectMiddleware;
diff --git a/web/middleware/iframe-destroyer.js b/web/middleware/iframe-destroyer.js
deleted file mode 100644
index 13d6a7b2c..000000000
--- a/web/middleware/iframe-destroyer.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const PAGES = require('../../ui/constants/pages');
-
-async function iframeDestroyerMiddleware(ctx, next) {
- const {
- request: { path },
- } = ctx;
- const decodedPath = decodeURIComponent(path);
-
- if (!decodedPath.startsWith(`/$/${PAGES.EMBED}`)) {
- ctx.set('X-Frame-Options', 'DENY');
- }
-
- return next();
-}
-
-module.exports = iframeDestroyerMiddleware;
diff --git a/web/middleware/redirect.js b/web/middleware/redirect.js
deleted file mode 100644
index 11a13596c..000000000
--- a/web/middleware/redirect.js
+++ /dev/null
@@ -1,67 +0,0 @@
-const PAGES = require('../../ui/constants/pages');
-// const config = require('../../config');
-
-function formatInAppUrl(path) {
- // Determine if we need to add a leading "/$/" for app pages
- const APP_PAGE_REGEX = /(\?)([a-z]*)(.*)/;
- const appPageMatches = APP_PAGE_REGEX.exec(path);
-
- if (appPageMatches && appPageMatches.length) {
- // Definitely an app page (or it's formatted like one)
- const [, , page, queryString] = appPageMatches;
-
- if (Object.values(PAGES).includes(page)) {
- let actualUrl = '/$/' + page;
-
- if (queryString) {
- actualUrl += `?${queryString.slice(1)}`;
- }
-
- return actualUrl;
- }
- }
-
- // Regular claim url
- return path;
-}
-
-async function redirectMiddleware(ctx, next) {
- const requestHost = ctx.host;
- const path = ctx.path;
- const url = ctx.url;
-
- // Getting err: too many redirects on some urls because of this
- // Need a better solution
- // const decodedUrl = decodeURIComponent(url);
-
- // if (decodedUrl !== url) {
- // ctx.redirect(decodedUrl);
- // return;
- // }
-
- if (!path.startsWith('/$/') && path.match(/^([^@/:]+)\/([^:/]+)$/)) {
- ctx.redirect(url.replace(/^([^@/:]+)\/([^:/]+)(:(\/.*))/, '$1:$2')); // test against path, but use ctx.url to retain parameters
- return;
- }
-
- if (requestHost === 'open.lbry.com' || requestHost === 'open.lbry.io') {
- const openQuery = '?src=open';
- // let redirectUrl = config.URL + formatInAppUrl(url);
- // Blame tom for this, not me
- let redirectUrl = 'https://odysee.com' + formatInAppUrl(url);
-
- if (redirectUrl.includes('?')) {
- redirectUrl = redirectUrl.replace('?', `${openQuery}&`);
- } else {
- redirectUrl += openQuery;
- }
- ctx.status = 301;
- ctx.redirect(redirectUrl);
- return;
- }
-
- // No redirects needed
- await next();
-}
-
-module.exports = redirectMiddleware;
diff --git a/web/package.json b/web/package.json
deleted file mode 100644
index a74fb14e0..000000000
--- a/web/package.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
- "name": "lbry.tv",
- "version": "0.0.0",
- "description": "A web based browser for the LBRY network, a digital marketplace controlled by its users.",
- "keywords": [
- "lbry"
- ],
- "license": "MIT",
- "homepage": "https://lbry.com/",
- "bugs": {
- "url": "https://github.com/lbryio/lbry-desktop/issues"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/lbryio/lbry-desktop"
- },
- "author": {
- "name": "LBRY Inc.",
- "email": "hello@lbry.com"
- },
- "main": "./index.js",
- "scripts": {
- "build": "cross-env NODE_ENV=production webpack --progess --config webpack.config.js",
- "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot --progress --config webpack.config.js",
- "dev:server": "nodemon index.js"
- },
- "dependencies": {
- "@koa/router": "^8.0.2",
- "cross-env": "^6.0.3",
- "koa": "^2.11.0",
- "koa-logger": "^3.2.1",
- "koa-send": "^5.0.0",
- "koa-static": "^5.0.0",
- "mysql": "^2.17.1",
- "node-fetch": "^2.6.1",
- "uuid": "^8.3.0"
- },
- "devDependencies": {
- "@babel/core": "^7.0.0",
- "@babel/plugin-proposal-class-properties": "^7.0.0",
- "@babel/plugin-proposal-decorators": "^7.3.0",
- "@babel/plugin-proposal-object-rest-spread": "^7.6.2",
- "@babel/plugin-transform-flow-strip-types": "^7.2.3",
- "@babel/plugin-transform-runtime": "^7.4.3",
- "@babel/polyfill": "^7.2.5",
- "@babel/preset-env": "^7.7.1",
- "@babel/preset-flow": "^7.0.0",
- "@babel/preset-react": "^7.0.0",
- "@babel/register": "^7.0.0",
- "cache-loader": "^4.1.0",
- "nodemon": "^1.19.4",
- "speed-measure-webpack-plugin": "^1.3.1",
- "webpack": "^4.41.2",
- "webpack-bundle-analyzer": "^3.6.0",
- "webpack-dev-server": "^3.9.0",
- "webpack-merge": "^4.2.2",
- "write-file-webpack-plugin": "^4.5.1"
- }
-}
diff --git a/web/page/code2257/index.js b/web/page/code2257/index.js
deleted file mode 100644
index bf0d2b095..000000000
--- a/web/page/code2257/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import PageCode2257 from './view';
-export default PageCode2257;
diff --git a/web/page/code2257/view.jsx b/web/page/code2257/view.jsx
deleted file mode 100644
index 3ee535f25..000000000
--- a/web/page/code2257/view.jsx
+++ /dev/null
@@ -1,47 +0,0 @@
-// @flow
-import React from 'react';
-import Page from 'component/page';
-import Card from 'component/common/card';
-
-const Code2257Page = () => {
- return (
-
-
-
- lbry.tv is not a producer (primary or secondary) of any and all of the content found on the website
- (lbry.tv). With respect to the records as per 18 USC 2257 for any and all content found on this site,
- please kindly direct your request to the site for which the content was produced.
-
-
- lbry.tv is a video sharing site in which allows for the uploading, sharing and general viewing of various
- types of adult content and while lbry.tv does the best it can with verifying compliance, it may not be
- 100% accurate.
-
-
- lbry.tv abides by the following procedures to ensure compliance:
-
- Requiring all users to be 18 years of age to upload videos.
-
- When uploading, user must verify the content; assure he/she is 18 years of age; certify that he/she
- keeps records of the models in the content and that they are over 18 years of age.
-
-
-
-
- For further assistance and/or information in finding the content's originating site, please contact
- lbry.tv compliance at copyright@lbry.com
-
-
- Users of lbry.tv who come across such content are urged to flag it as inappropriate by clicking 'Report
- this video' link found below each video.
-
-
- }
- />
-
- );
-};
-export default Code2257Page;
diff --git a/web/scss/lbrytv.scss b/web/scss/lbrytv.scss
deleted file mode 100644
index 91b6201e5..000000000
--- a/web/scss/lbrytv.scss
+++ /dev/null
@@ -1,71 +0,0 @@
-@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/channel-mention';
-@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';
diff --git a/web/scss/odysee.scss b/web/scss/odysee.scss
deleted file mode 100644
index 1e2cf5ede..000000000
--- a/web/scss/odysee.scss
+++ /dev/null
@@ -1,71 +0,0 @@
-@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/channel-mention';
-@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';
diff --git a/web/scss/themes/lbrytv/dark.scss b/web/scss/themes/lbrytv/dark.scss
deleted file mode 100644
index ab79d895f..000000000
--- a/web/scss/themes/lbrytv/dark.scss
+++ /dev/null
@@ -1,160 +0,0 @@
-[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);
- --color-menu-icon-active: 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;
-
- // 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;
-}
diff --git a/web/scss/themes/lbrytv/light.scss b/web/scss/themes/lbrytv/light.scss
deleted file mode 100644
index 5a4260fca..000000000
--- a/web/scss/themes/lbrytv/light.scss
+++ /dev/null
@@ -1,2 +0,0 @@
-:root {
-}
diff --git a/web/scss/themes/odysee/component/_file-render.scss b/web/scss/themes/odysee/component/_file-render.scss
deleted file mode 100644
index 20560bad1..000000000
--- a/web/scss/themes/odysee/component/_file-render.scss
+++ /dev/null
@@ -1,757 +0,0 @@
-.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 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: 2rem;
- max-height: 2rem; //embed logo height?
- 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 {
- color: white;
- 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;
- }
-}
diff --git a/web/scss/themes/odysee/component/_form-field.scss b/web/scss/themes/odysee/component/_form-field.scss
deleted file mode 100644
index 5c4a3a2c8..000000000
--- a/web/scss/themes/odysee/component/_form-field.scss
+++ /dev/null
@@ -1,713 +0,0 @@
-@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);
-}
diff --git a/web/scss/themes/odysee/dark.scss b/web/scss/themes/odysee/dark.scss
deleted file mode 100644
index ae5d31e53..000000000
--- a/web/scss/themes/odysee/dark.scss
+++ /dev/null
@@ -1,137 +0,0 @@
-[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;
-
- // 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;
-}
diff --git a/web/scss/themes/odysee/init/_base-theme.scss b/web/scss/themes/odysee/init/_base-theme.scss
deleted file mode 100644
index f03b4a055..000000000
--- a/web/scss/themes/odysee/init/_base-theme.scss
+++ /dev/null
@@ -1,200 +0,0 @@
-//
-// 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-primary);
- --color-tag-words-bg: var(--color-primary-alt);
- --color-tag-words-hover: var(--color-primary);
- --color-tag-words-bg-hover: var(--color-primary-alt-3);
-
- // Menu
- --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-icon-active: 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;
-}
diff --git a/web/scss/themes/odysee/init/_color.scss b/web/scss/themes/odysee/init/_color.scss
deleted file mode 100644
index 70573ea47..000000000
--- a/web/scss/themes/odysee/init/_color.scss
+++ /dev/null
@@ -1,57 +0,0 @@
-: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);
-}
diff --git a/web/scss/themes/odysee/init/_gui.scss b/web/scss/themes/odysee/init/_gui.scss
deleted file mode 100644
index 3b55c9ee0..000000000
--- a/web/scss/themes/odysee/init/_gui.scss
+++ /dev/null
@@ -1,521 +0,0 @@
-// 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: auto;
-}
-
-.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);
- }
-
- > .button {
- white-space: initial;
- }
-}
diff --git a/web/scss/themes/odysee/init/_mixins.scss b/web/scss/themes/odysee/init/_mixins.scss
deleted file mode 100644
index 3d626e238..000000000
--- a/web/scss/themes/odysee/init/_mixins.scss
+++ /dev/null
@@ -1,237 +0,0 @@
-@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;
- }
-}
diff --git a/web/scss/themes/odysee/init/_reset.scss b/web/scss/themes/odysee/init/_reset.scss
deleted file mode 100644
index aff7dd756..000000000
--- a/web/scss/themes/odysee/init/_reset.scss
+++ /dev/null
@@ -1,245 +0,0 @@
-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;
- }
- }
-}
diff --git a/web/scss/themes/odysee/init/_vars.scss b/web/scss/themes/odysee/init/_vars.scss
deleted file mode 100644
index 381da76f0..000000000
--- a/web/scss/themes/odysee/init/_vars.scss
+++ /dev/null
@@ -1,108 +0,0 @@
-// 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;
- }
-}
diff --git a/web/scss/themes/odysee/light.scss b/web/scss/themes/odysee/light.scss
deleted file mode 100644
index fe75e4586..000000000
--- a/web/scss/themes/odysee/light.scss
+++ /dev/null
@@ -1,155 +0,0 @@
-: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;
-}
diff --git a/web/setup/publish.js b/web/setup/publish.js
deleted file mode 100644
index e5f880836..000000000
--- a/web/setup/publish.js
+++ /dev/null
@@ -1,104 +0,0 @@
-// @flow
-/*
- https://api.na-backend.odysee.com/api/v1/proxy currently expects publish to consist
- of a multipart/form-data POST request with:
- - 'file' binary
- - 'json_payload' collection of publish params to be passed to the server's sdk.
- */
-import { X_LBRY_AUTH_TOKEN } from '../../ui/constants/token';
-import { doUpdateUploadProgress } from 'lbryinc';
-
-// A modified version of Lbry.apiCall that allows
-// to perform calling methods at arbitrary urls
-// and pass form file fields
-export default function apiPublishCallViaWeb(
- apiCall: (any, any, any, any) => any,
- connectionString: string,
- token: string,
- method: string,
- params: { file_path: string, preview: boolean, remote_url?: string }, // new param for remoteUrl
- resolve: Function,
- reject: Function
-) {
- const { file_path: filePath, preview, remote_url: remoteUrl } = params;
-
- if (!filePath && !remoteUrl) {
- return apiCall(method, params, resolve, reject);
- }
-
- const counter = new Date().getTime();
- let fileField = filePath;
-
- if (preview) {
- // Send dummy file for the preview. The tx-fee calculation does not depend on it.
- const dummyContent = 'x';
- fileField = new File([dummyContent], 'dummy.md', { type: 'text/markdown' });
- }
-
- // Putting a dummy value here, the server is going to process the POSTed file
- // and set the file_path itself
-
- const body = new FormData();
- if (fileField) {
- body.append('file', fileField);
- params.file_path = '__POST_FILE__';
- delete params['remote_url'];
- } else if (remoteUrl) {
- body.append('remote_url', remoteUrl);
- delete params['remote_url'];
- }
-
- const jsonPayload = JSON.stringify({
- jsonrpc: '2.0',
- method,
- params,
- id: counter,
- });
- // no fileData? do the livestream remote publish
- body.append('json_payload', jsonPayload);
-
- function makeRequest(connectionString, method, token, body, params) {
- return new Promise((resolve, reject) => {
- let xhr = new XMLHttpRequest();
- xhr.open(method, connectionString);
- xhr.setRequestHeader(X_LBRY_AUTH_TOKEN, token);
- xhr.responseType = 'json';
- xhr.upload.onprogress = (e) => {
- let percentComplete = Math.ceil((e.loaded / e.total) * 100);
- window.store.dispatch(doUpdateUploadProgress(percentComplete, params, xhr));
- };
- xhr.onload = () => {
- window.store.dispatch(doUpdateUploadProgress(undefined, params));
- resolve(xhr);
- };
- xhr.onerror = () => {
- window.store.dispatch(doUpdateUploadProgress(undefined, params));
- reject(new Error(__('There was a problem with your upload. Please try again.')));
- };
-
- xhr.onabort = () => {
- window.store.dispatch(doUpdateUploadProgress(undefined, params));
- };
- xhr.send(body);
- });
- }
-
- return makeRequest(connectionString, 'POST', token, body, params)
- .then((xhr) => {
- let error;
- if (xhr && xhr.response) {
- if (xhr.status >= 200 && xhr.status < 300 && !xhr.response.error) {
- return resolve(xhr.response.result);
- } else if (xhr.response.error) {
- error = new Error(xhr.response.error.message);
- } else {
- error = new Error(__('Upload likely timed out. Try a smaller file while we work on this.'));
- }
- }
-
- if (error) {
- return Promise.reject(error);
- }
- })
- .catch(reject);
-}
diff --git a/web/src/category-metadata.js b/web/src/category-metadata.js
deleted file mode 100644
index f71e5c796..000000000
--- a/web/src/category-metadata.js
+++ /dev/null
@@ -1,71 +0,0 @@
-const PAGES = require('../../ui/constants/pages');
-
-// Uncomment as you add metadata
-
-module.exports.CATEGORY_METADATA = {
- [PAGES.BIG_HITS]: {
- title: 'Big Hits',
- description: 'Animation, pop culture, comedy, and all the other weird on Odysee',
- image: '',
- },
- [PAGES.COMMUNITY]: {
- title: 'The Universe',
- description: 'Podcasts, life, learning, and everything else on Odysee',
- image: '',
- },
- // [PAGES.ENLIGHTENMENT]: {
- // title: '',
- // description: '',
- // image: '',
- // },
- [PAGES.FINANCE]: {
- title: 'Finance 2.0',
- description: 'Crypto, Money, Economics, Markets on Odysee ',
- image: 'https://spee.ch/category-finance:c.jpg?quality=80&height=1200&width=630',
- },
- [PAGES.GAMING]: {
- title: 'Gaming',
- description: 'Pew pew bzzz gaming on Odysee',
- image: 'https://spee.ch/category-gaming:5.jpg?quality=80&height=1200&width=630',
- },
- [PAGES.GENERAL]: {
- title: 'Cheese',
- description: 'Cheese is the answer to life, the universe, and everything. We have primo cheese on Odysee',
- image: 'https://spee.ch/category-primary1:5.jpg?quality=80&height=1200&width=630',
- },
- [PAGES.LAB]: {
- title: 'Lab',
- description: 'Science - the real kind, on Odysee',
- image: '',
- },
- [PAGES.NEWS]: {
- title: 'News & Politics',
- description: `Stay up to date with all that's happening around the world on Odysee`,
- image: '',
- },
- [PAGES.MOVIES]: {
- title: 'Movies',
- 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 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.TECH]: {
- title: 'Tech',
- description: 'Hardware, software, startups, photography on Odysee',
- image: '',
- },
- [PAGES.TECHNOLOGY]: {
- title: 'Tech',
- description: 'Hardware, software, startups, photography on Odysee',
- image: '',
- },
- [PAGES.WILD_WEST]: {
- title: 'Wild West',
- description: 'Boosted by user credits, this is what the community promotes on Odysee',
- image: 'https://spee.ch/category-wildwest:1.jpg?quality=80&height=1200&width=630',
- },
-};
diff --git a/web/src/chainquery.js b/web/src/chainquery.js
deleted file mode 100644
index bc2fd4a87..000000000
--- a/web/src/chainquery.js
+++ /dev/null
@@ -1,60 +0,0 @@
-const mysql = require('mysql');
-
-const pool = mysql.createPool({
- connectionLimit: 100,
- host: 'chainquery.lbry.com',
- user: 'lbrytv',
- password: process.env.CHAINQUERY_MYSQL_PASSWORD,
- database: 'chainquery',
-});
-
-function queryPool(sql, params) {
- return new Promise((resolve) => {
- pool.query(sql, params, (error, rows) => {
- if (error) {
- console.log('error', error); // eslint-disable-line
- resolve();
- return;
- }
-
- resolve(rows);
- });
- });
-}
-
-module.exports.getClaim = async function getClaim(claimName, claimId, channelName, channelClaimId) {
- let params = [claimName];
-
- let sql =
- 'SELECT channel_claim.name as channel, claim.claim_id, claim.name, claim.description, claim.language, claim.thumbnail_url, claim.title, claim.source_media_type, claim.frame_width, claim.frame_height, claim.fee, claim.release_time, claim.duration, claim.audio_duration, ' +
- 'repost_channel.name as repost_channel, reposted_claim.claim_id as reposted_claim_id, reposted_claim.name as reposted_name, reposted_claim.description as reposted_description, reposted_claim.language as reposted_language, reposted_claim.thumbnail_url as reposted_thumbnail_url, reposted_claim.title as reposted_title, reposted_claim.source_media_type as reposted_source_media_type, reposted_claim.frame_width as reposted_frame_width, reposted_claim.frame_height as reposted_frame_height, reposted_claim.fee as reposted_fee ' +
- 'FROM claim ' +
- 'LEFT JOIN claim channel_claim on claim.publisher_id = channel_claim.claim_id ' +
- 'LEFT JOIN claim as reposted_claim on reposted_claim.claim_id = claim.claim_reference ' +
- 'AND (reposted_claim.bid_state in ("controlling", "active", "accepted", "spent")) ' +
- 'LEFT JOIN claim as repost_channel on repost_channel.claim_id = reposted_claim.publisher_id ' +
- 'WHERE claim.name = ?';
-
- if (claimId) {
- sql += ' AND claim.claim_id LIKE ?';
- params.push(claimId + '%');
- sql += ' AND claim.bid_state in ("controlling", "active", "accepted", "spent")';
- } else {
- sql += ' AND claim.bid_state in ("controlling", "active", "accepted")';
- }
-
- if (claimName[0] !== '@' && channelName) {
- sql += ' AND channel_claim.name = ?';
- params.push('@' + channelName);
- if (channelClaimId) {
- sql += ' AND channel_claim.claim_id LIKE ?';
- params.push(channelClaimId + '%');
- } else {
- sql += ' AND channel_claim.bid_state in ("controlling", "active", "accepted", "spent")';
- }
- }
-
- sql += ' ORDER BY claim.bid_state DESC LIMIT 1';
-
- return queryPool(sql, params);
-};
diff --git a/web/src/getHomepageJSON.js b/web/src/getHomepageJSON.js
deleted file mode 100644
index 345d39711..000000000
--- a/web/src/getHomepageJSON.js
+++ /dev/null
@@ -1,13 +0,0 @@
-const memo = {};
-// this didn't seem to help.
-if (!memo.homepageData) {
- try {
- memo.homepageData = require('../../custom/homepages/v2');
- } catch (err) {
- console.log('homepage data failed');
- }
-}
-const getHomepageJSON = () => {
- return memo.homepageData || {};
-};
-module.exports = { getHomepageJSON };
diff --git a/web/src/html.js b/web/src/html.js
deleted file mode 100644
index 3ff8c6cd8..000000000
--- a/web/src/html.js
+++ /dev/null
@@ -1,370 +0,0 @@
-const {
- URL,
- DOMAIN,
- SITE_TITLE,
- SITE_CANONICAL_URL,
- OG_HOMEPAGE_TITLE,
- OG_TITLE_SUFFIX,
- OG_IMAGE_URL,
- SITE_DESCRIPTION,
- SITE_NAME,
- FAVICON,
- LBRY_WEB_API,
-} = require('../../config.js');
-
-const { lbryProxy: Lbry } = require('../lbry');
-const { generateEmbedUrl, generateStreamUrl, generateDirectUrl } = require('../../ui/util/web');
-const PAGES = require('../../ui/constants/pages');
-const { CATEGORY_METADATA } = require('./category-metadata');
-const { parseURI, normalizeURI } = require('./lbryURI');
-const fs = require('fs');
-const path = require('path');
-const moment = require('moment');
-const removeMd = require('remove-markdown');
-const { getJsBundleId } = require('../bundle-id.js');
-const jsBundleId = getJsBundleId();
-const SDK_API_PATH = `${LBRY_WEB_API}/api/v1`;
-const PROXY_URL = `${SDK_API_PATH}/proxy`;
-Lbry.setDaemonConnectionString(PROXY_URL);
-
-function insertToHead(fullHtml, htmlToInsert) {
- const beginStr = '';
- const finalStr = '';
-
- const beginIndex = fullHtml.indexOf(beginStr);
- const finalIndex = fullHtml.indexOf(finalStr);
-
- if (beginIndex > -1 && finalIndex > -1 && finalIndex > beginIndex) {
- return `${fullHtml.slice(0, beginIndex)}${
- htmlToInsert || buildOgMetadata()
- }${fullHtml.slice(finalIndex + finalStr.length)}`;
- }
-}
-
-function truncateDescription(description, maxChars = 200) {
- // Get list of single-codepoint strings
- const chars = [...description];
- // Use slice array instead of substring to prevent breaking emojis
- let truncated = chars.slice(0, maxChars).join('');
- // Format truncated string
- return chars.length > maxChars ? truncated + '...' : truncated;
-}
-
-function normalizeClaimUrl(url) {
- return normalizeURI(url.replace(/:/g, '#'));
-}
-
-function escapeHtmlProperty(property) {
- return property
- ? String(property)
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
- : '';
-}
-
-function getCategoryMeta(path) {
- const page = Object.keys(CATEGORY_METADATA).find((x) => path === `/$/${x}` || path === `/$/${x}/`);
- return CATEGORY_METADATA[page];
-}
-
-//
-// Normal metadata with option to override certain values
-//
-function buildOgMetadata(overrideOptions = {}) {
- const { title, description, image, path } = overrideOptions;
- const cleanDescription = removeMd(description || SITE_DESCRIPTION);
- const head =
- `${SITE_TITLE} \n` +
- ` \n` +
- ` \n` +
- ` \n` +
- ` \n` +
- ` \n` +
- ` \n` +
- ` \n` +
- ' \n' +
- ` \n` +
- ` \n` +
- ` \n` +
- ' \n' +
- ` ` +
- ` `;
- return head;
-}
-
-function conditionallyAddPWA() {
- let head = '';
- if (DOMAIN === 'odysee.com') {
- head += ' ';
- head += ' ';
- head += '';
- }
- return head;
-}
-
-function addFavicon() {
- let head = '';
- head += ` `;
- return head;
-}
-
-function buildHead() {
- const head =
- '' +
- addFavicon() +
- conditionallyAddPWA() +
- buildOgMetadata() +
- '';
- return head;
-}
-
-function buildBasicOgMetadata() {
- const head = '' + addFavicon() + buildOgMetadata() + '';
- return head;
-}
-
-//
-// Metadata used for urls that need claim information
-// Also has option to override defaults
-//
-function buildClaimOgMetadata(uri, claim, overrideOptions = {}) {
- // Initial setup for claim based og metadata
- const { claimName } = parseURI(uri);
- const { meta, value, signing_channel } = claim;
- const fee = value && value.fee && (Number(value.fee.amount) || 0);
- const tags = value && value.tags;
- const media = value && (value.video || value.audio || value.image);
- const source = value && value.source;
- const channel = signing_channel && signing_channel.name;
- const thumbnail = value && value.thumbnail && value.thumbnail.url;
- const mediaType = source && source.media_type;
- const mediaDuration = media && media.duration;
- const claimTitle = escapeHtmlProperty((value && value.title) || claimName);
- const releaseTime = (value && value.release_time) || (meta && meta.creation_timestamp) || 0;
-
- const claimDescription =
- value && value.description && value.description.length > 0
- ? escapeHtmlProperty(truncateDescription(value.description))
- : `View ${claimTitle} on ${SITE_NAME}`;
-
- const claimLanguage =
- value && value.languages && value.languages.length > 0 ? escapeHtmlProperty(value.languages[0]) : 'en_US';
-
- let imageThumbnail;
-
- if (fee <= 0 && mediaType && mediaType.startsWith('image/')) {
- imageThumbnail = generateStreamUrl(claim.name, claim.claim_id);
- }
-
- const claimThumbnail = escapeHtmlProperty(thumbnail) || imageThumbnail || OG_IMAGE_URL || `${URL}/public/v2-og.png`;
-
- // Allow for ovverriding default claim based og metadata
- const title = overrideOptions.title || claimTitle;
- const description = overrideOptions.description || claimDescription;
- const cleanDescription = removeMd(description);
-
- let head = '';
-
- head += `${addFavicon()}`;
- head += ' ';
- head += `${title} `;
- head += ` `;
-
- if (tags && tags.length > 0) {
- head += ` `;
- }
-
- head += ` `;
- head += ` `;
- head += ` `;
- head += ` `;
- head += ` `;
- head += ` `;
- head += ` `;
- head += ` `;
- // below should be canonical_url, but not provided by chainquery yet
- head += ` `;
- head += ` `;
- head += ` `;
- head += ` `;
-
- if (mediaType && (mediaType.startsWith('video/') || mediaType.startsWith('audio/'))) {
- const videoUrl = generateEmbedUrl(claim.name, claim.claim_id);
- head += ` `;
- head += ` `;
- head += ` `;
- if (channel) {
- head += ` `;
- }
- head += ` `;
- head += ` `;
- if (releaseTime) {
- var release = new Date(releaseTime * 1000).toISOString();
- head += ` `;
- }
- if (mediaDuration) {
- head += ` `;
- }
- if (media && media.width && media.height) {
- head += ` `;
- head += ` `;
- head += ` `;
- head += ` `;
- }
- } else {
- head += ` `;
- }
-
- return head;
-}
-
-function buildGoogleVideoMetadata(uri, claim) {
- const { claimName } = parseURI(uri);
- const { meta, value } = claim;
- const media = value && value.video;
- const source = value && value.source;
- const thumbnail = value && value.thumbnail && value.thumbnail.url;
- const mediaType = source && source.media_type;
- const mediaDuration = media && media.duration;
- const claimTitle = escapeHtmlProperty((value && value.title) || claimName);
- const releaseTime = (value && value.release_time) || (meta && meta.creation_timestamp) || 0;
-
- const claimDescription =
- value && value.description && value.description.length > 0
- ? escapeHtmlProperty(truncateDescription(value.description))
- : `View ${claimTitle} on ${SITE_NAME}`;
-
- if (!mediaType || !mediaType.startsWith('video/')) {
- return '';
- }
-
- const claimThumbnail = escapeHtmlProperty(thumbnail) || OG_IMAGE_URL || `${URL}/public/v2-og.png`;
-
- // https://developers.google.com/search/docs/data-types/video
- const googleVideoMetadata = {
- // --- Must ---
- '@context': 'https://schema.org',
- '@type': 'VideoObject',
- name: `${claimTitle}`,
- description: `${removeMd(claimDescription)}`,
- thumbnailUrl: `${claimThumbnail}`,
- uploadDate: `${new Date(releaseTime * 1000).toISOString()}`,
- // --- Recommended ---
- duration: mediaDuration ? moment.duration(mediaDuration * 1000).toISOString() : undefined,
- contentUrl: generateDirectUrl(claim.name, claim.claim_id),
- embedUrl: generateEmbedUrl(claim.name, claim.claim_id),
- };
-
- if (
- !googleVideoMetadata.description.replace(/\s/g, '').length ||
- googleVideoMetadata.thumbnailUrl.startsWith('data:image') ||
- !googleVideoMetadata.thumbnailUrl.startsWith('http')
- ) {
- return '';
- }
-
- return (
- '\n'
- );
-}
-
-async function resolveClaimOrRedirect(ctx, url, ignoreRedirect = false) {
- let claim;
- try {
- const response = await Lbry.resolve({ urls: [url] });
- if (response && response[url] && !response[url].error) {
- claim = response && response[url];
- const isRepost = claim.reposted_claim && claim.reposted_claim.name && claim.reposted_claim.claim_id;
- if (isRepost && !ignoreRedirect) {
- ctx.redirect(`/${claim.reposted_claim.name}:${claim.reposted_claim.claim_id}`);
- return;
- }
- }
- } catch {}
- return claim;
-}
-
-let html;
-async function getHtml(ctx) {
- if (!html) {
- html = fs.readFileSync(path.join(__dirname, '/../dist/index.html'), 'utf8');
- }
-
- const requestPath = decodeURIComponent(ctx.path);
- if (requestPath.length === 0) {
- const ogMetadata = buildBasicOgMetadata();
- return insertToHead(html, ogMetadata);
- }
-
- const invitePath = `/$/${PAGES.INVITE}/`;
- const embedPath = `/$/${PAGES.EMBED}/`;
-
- if (requestPath.includes(invitePath)) {
- try {
- const inviteChannel = requestPath.slice(invitePath.length);
- const inviteChannelUrl = normalizeClaimUrl(inviteChannel);
- const claim = await resolveClaimOrRedirect(ctx, inviteChannelUrl);
- const invitePageMetadata = buildClaimOgMetadata(inviteChannelUrl, claim, {
- title: `Join ${claim.name} on ${SITE_NAME}`,
- description: `Join ${claim.name} on ${SITE_NAME}, a content wonderland owned by everyone (and no one).`,
- });
-
- return insertToHead(html, invitePageMetadata);
- } catch (e) {
- // Something about the invite channel is messed up
- // Enter generic invite metadata
- const invitePageMetadata = buildOgMetadata({
- title: `Join a friend on ${SITE_NAME}`,
- description: `Join a friend on ${SITE_NAME}, a content wonderland owned by everyone (and no one).`,
- });
- return insertToHead(html, invitePageMetadata);
- }
- }
-
- if (requestPath.includes(embedPath)) {
- const claimUri = normalizeClaimUrl(requestPath.replace(embedPath, '').replace('/', '#'));
- const claim = await resolveClaimOrRedirect(ctx, claimUri, true);
-
- if (claim) {
- const ogMetadata = buildClaimOgMetadata(claimUri, claim);
- const googleVideoMetadata = buildGoogleVideoMetadata(claimUri, claim);
- return insertToHead(html, ogMetadata.concat('\n', googleVideoMetadata));
- }
-
- return insertToHead(html);
- }
-
- const categoryMeta = getCategoryMeta(requestPath);
- if (categoryMeta) {
- const categoryPageMetadata = buildOgMetadata({
- title: categoryMeta.title,
- description: categoryMeta.description,
- image: categoryMeta.image,
- path: requestPath,
- });
- return insertToHead(html, categoryPageMetadata);
- }
-
- if (!requestPath.includes('$')) {
- const claimUri = normalizeClaimUrl(requestPath.slice(1));
- const claim = await resolveClaimOrRedirect(ctx, claimUri);
-
- if (claim) {
- const ogMetadata = buildClaimOgMetadata(claimUri, claim);
- const googleVideoMetadata = buildGoogleVideoMetadata(claimUri, claim);
- return insertToHead(html, ogMetadata.concat('\n', googleVideoMetadata));
- }
- }
-
- const ogMetadataAndPWA = buildHead();
- return insertToHead(html, ogMetadataAndPWA);
-}
-
-module.exports = { insertToHead, buildHead, getHtml };
diff --git a/web/src/lbryURI.js b/web/src/lbryURI.js
deleted file mode 100644
index 80b26f2ce..000000000
--- a/web/src/lbryURI.js
+++ /dev/null
@@ -1,343 +0,0 @@
-// Disabled flow in this copy. This copy is for uncompiled web server ES5 require()s.
-
-const isProduction = process.env.NODE_ENV === 'production';
-const channelNameMinLength = 1;
-const claimIdMaxLength = 40;
-
-// see https://spec.lbry.com/#urls
-const regexInvalidURI = /[ =:$@%?;/\\"<>%{}|^~[\]`\u{0000}-\u{0008}\u{000b}-\u{000c}\u{000e}-\u{001F}\u{D800}-\u{DFFF}\u{FFFE}-\u{FFFF}]/u;
-// const regexAddress = /^(b|r)(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/;
-const regexPartProtocol = '^((?:lbry://)?)';
-const regexPartStreamOrChannelName = '([^:$#/]*)';
-const regexPartModifierSeparator = '([:$#]?)([^/]*)';
-const queryStringBreaker = '^([\\S]+)([?][\\S]*)';
-const separateQuerystring = new RegExp(queryStringBreaker);
-
-const MOD_SEQUENCE_SEPARATOR = '*';
-const MOD_CLAIM_ID_SEPARATOR_OLD = '#';
-const MOD_CLAIM_ID_SEPARATOR = ':';
-const MOD_BID_POSITION_SEPARATOR = '$';
-
-/**
- * Parses a LBRY name into its component parts. Throws errors with user-friendly
- * messages for invalid names.
- *
- * Returns a dictionary with keys:
- * - path (string)
- * - isChannel (boolean)
- * - streamName (string, if present)
- * - streamClaimId (string, if present)
- * - channelName (string, if present)
- * - channelClaimId (string, if present)
- * - primaryClaimSequence (int, if present)
- * - secondaryClaimSequence (int, if present)
- * - primaryBidPosition (int, if present)
- * - secondaryBidPosition (int, if present)
- */
-
-function parseURI(url, requireProto = false) {
- // Break into components. Empty sub-matches are converted to null
-
- const componentsRegex = new RegExp(
- regexPartProtocol + // protocol
- regexPartStreamOrChannelName + // stream or channel name (stops at the first separator or end)
- regexPartModifierSeparator + // modifier separator, modifier (stops at the first path separator or end)
- '(/?)' + // path separator, there should only be one (optional) slash to separate the stream and channel parts
- regexPartStreamOrChannelName +
- regexPartModifierSeparator
- );
- // chop off the querystring first
- let QSStrippedURL, qs;
- const qsRegexResult = separateQuerystring.exec(url);
- if (qsRegexResult) {
- [QSStrippedURL, qs] = qsRegexResult.slice(1).map((match) => match || null);
- }
-
- const cleanURL = QSStrippedURL || url;
- const regexMatch = componentsRegex.exec(cleanURL) || [];
- const [proto, ...rest] = regexMatch.slice(1).map((match) => match || null);
- const path = rest.join('');
- const [
- streamNameOrChannelName,
- primaryModSeparator,
- primaryModValue,
- pathSep, // eslint-disable-line no-unused-vars
- possibleStreamName,
- secondaryModSeparator,
- secondaryModValue,
- ] = rest;
- const searchParams = new URLSearchParams(qs || '');
- const startTime = searchParams.get('t');
-
- // Validate protocol
- if (requireProto && !proto) {
- throw new Error(__('LBRY URLs must include a protocol prefix (lbry://).'));
- }
-
- // Validate and process name
- if (!streamNameOrChannelName) {
- throw new Error(__('URL does not include name.'));
- }
-
- rest.forEach((urlPiece) => {
- if (urlPiece && urlPiece.includes(' ')) {
- throw new Error(__('URL can not include a space'));
- }
- });
-
- const includesChannel = streamNameOrChannelName.startsWith('@');
- const isChannel = streamNameOrChannelName.startsWith('@') && !possibleStreamName;
- const channelName = includesChannel && streamNameOrChannelName.slice(1);
-
- if (includesChannel) {
- if (!channelName) {
- throw new Error(__('No channel name after @.'));
- }
-
- if (channelName.length < channelNameMinLength) {
- throw new Error(
- __(`Channel names must be at least %channelNameMinLength% characters.`, {
- channelNameMinLength,
- })
- );
- }
- }
-
- // Validate and process modifier
- const [primaryClaimId, primaryClaimSequence, primaryBidPosition] = parseURIModifier(
- primaryModSeparator,
- primaryModValue
- );
- const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition] = parseURIModifier(
- secondaryModSeparator,
- secondaryModValue
- );
- const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName;
- const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId;
- const channelClaimId = includesChannel && primaryClaimId;
-
- return {
- isChannel,
- path,
- ...(streamName ? { streamName } : {}),
- ...(streamClaimId ? { streamClaimId } : {}),
- ...(channelName ? { channelName } : {}),
- ...(channelClaimId ? { channelClaimId } : {}),
- ...(primaryClaimSequence ? { primaryClaimSequence: parseInt(primaryClaimSequence, 10) } : {}),
- ...(secondaryClaimSequence ? { secondaryClaimSequence: parseInt(secondaryClaimSequence, 10) } : {}),
- ...(primaryBidPosition ? { primaryBidPosition: parseInt(primaryBidPosition, 10) } : {}),
- ...(secondaryBidPosition ? { secondaryBidPosition: parseInt(secondaryBidPosition, 10) } : {}),
- ...(startTime ? { startTime: parseInt(startTime, 10) } : {}),
-
- // The values below should not be used for new uses of parseURI
- // They will not work properly with canonical_urls
- claimName: streamNameOrChannelName,
- claimId: primaryClaimId,
- ...(streamName ? { contentName: streamName } : {}),
- ...(qs ? { queryString: qs } : {}),
- };
-}
-
-function parseURIModifier(modSeperator, modValue) {
- let claimId;
- let claimSequence;
- let bidPosition;
-
- if (modSeperator) {
- if (!modValue) {
- throw new Error(__(`No modifier provided after separator %modSeperator%.`, { modSeperator }));
- }
-
- if (modSeperator === MOD_CLAIM_ID_SEPARATOR || MOD_CLAIM_ID_SEPARATOR_OLD) {
- claimId = modValue;
- } else if (modSeperator === MOD_SEQUENCE_SEPARATOR) {
- claimSequence = modValue;
- } else if (modSeperator === MOD_BID_POSITION_SEPARATOR) {
- bidPosition = modValue;
- }
- }
-
- if (claimId && (claimId.length > claimIdMaxLength || !claimId.match(/^[0-9a-f]+$/))) {
- throw new Error(__(`Invalid claim ID %claimId%.`, { claimId }));
- }
-
- if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
- throw new Error(__('Claim sequence must be a number.'));
- }
-
- if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) {
- throw new Error(__('Bid position must be a number.'));
- }
-
- return [claimId, claimSequence, bidPosition];
-}
-
-/**
- * Takes an object in the same format returned by parse() and builds a URI.
- *
- * The channelName key will accept names with or without the @ prefix.
- */
-function buildURI(UrlObj, includeProto = true, protoDefault = 'lbry://') {
- const {
- streamName,
- streamClaimId,
- channelName,
- channelClaimId,
- primaryClaimSequence,
- primaryBidPosition,
- secondaryClaimSequence,
- secondaryBidPosition,
- startTime,
- ...deprecatedParts
- } = UrlObj;
- const { claimId, claimName, contentName } = deprecatedParts;
-
- if (!isProduction) {
- if (claimId) {
- console.error(__("'claimId' should no longer be used. Use 'streamClaimId' or 'channelClaimId' instead"));
- }
- if (claimName) {
- console.error(__("'claimName' should no longer be used. Use 'streamClaimName' or 'channelClaimName' instead"));
- }
- if (contentName) {
- console.error(__("'contentName' should no longer be used. Use 'streamName' instead"));
- }
- }
-
- if (!claimName && !channelName && !streamName) {
- console.error(
- __("'claimName', 'channelName', and 'streamName' are all empty. One must be present to build a url.")
- );
- }
-
- const formattedChannelName = channelName && (channelName.startsWith('@') ? channelName : `@${channelName}`);
- const primaryClaimName = claimName || contentName || formattedChannelName || streamName;
- const primaryClaimId = claimId || (formattedChannelName ? channelClaimId : streamClaimId);
- const secondaryClaimName = (!claimName && contentName) || (formattedChannelName ? streamName : null);
- const secondaryClaimId = secondaryClaimName && streamClaimId;
-
- return (
- (includeProto ? protoDefault : '') +
- // primaryClaimName will always exist here because we throw above if there is no "name" value passed in
- // $FlowFixMe
- primaryClaimName +
- (primaryClaimId ? `#${primaryClaimId}` : '') +
- (primaryClaimSequence ? `:${primaryClaimSequence}` : '') +
- (primaryBidPosition ? `${primaryBidPosition}` : '') +
- (secondaryClaimName ? `/${secondaryClaimName}` : '') +
- (secondaryClaimId ? `#${secondaryClaimId}` : '') +
- (secondaryClaimSequence ? `:${secondaryClaimSequence}` : '') +
- (secondaryBidPosition ? `${secondaryBidPosition}` : '') +
- (startTime ? `?t=${startTime}` : '')
- );
-}
-
-/* Takes a parseable LBRY URL and converts it to standard, canonical format */
-function normalizeURI(URL) {
- const {
- streamName,
- streamClaimId,
- channelName,
- channelClaimId,
- primaryClaimSequence,
- primaryBidPosition,
- secondaryClaimSequence,
- secondaryBidPosition,
- startTime,
- } = parseURI(URL);
-
- return buildURI({
- streamName,
- streamClaimId,
- channelName,
- channelClaimId,
- primaryClaimSequence,
- primaryBidPosition,
- secondaryClaimSequence,
- secondaryBidPosition,
- startTime,
- });
-}
-
-function isURIValid(URL) {
- try {
- parseURI(normalizeURI(URL));
- } catch (error) {
- return false;
- }
-
- return true;
-}
-
-function isNameValid(claimName) {
- return !regexInvalidURI.test(claimName);
-}
-
-function isURIClaimable(URL) {
- let parts;
- try {
- parts = parseURI(normalizeURI(URL));
- } catch (error) {
- return false;
- }
-
- return parts && parts.streamName && !parts.streamClaimId && !parts.isChannel;
-}
-
-function convertToShareLink(URL) {
- const {
- streamName,
- streamClaimId,
- channelName,
- channelClaimId,
- primaryBidPosition,
- primaryClaimSequence,
- secondaryBidPosition,
- secondaryClaimSequence,
- } = parseURI(URL);
- return buildURI(
- {
- streamName,
- streamClaimId,
- channelName,
- channelClaimId,
- primaryBidPosition,
- primaryClaimSequence,
- secondaryBidPosition,
- secondaryClaimSequence,
- },
- true,
- 'https://open.lbry.com/'
- );
-}
-
-function splitBySeparator(uri) {
- const protocolLength = 7;
- return uri.startsWith('lbry://') ? uri.slice(protocolLength).split(/[#:*]/) : uri.split(/#:\*\$/);
-}
-
-function isURIEqual(uriA, uriB) {
- const parseA = parseURI(normalizeURI(uriA));
- const parseB = parseURI(normalizeURI(uriB));
- if (parseA.isChannel) {
- if (parseB.isChannel && parseA.channelClaimId === parseB.channelClaimId) {
- return true;
- }
- } else if (parseA.streamClaimId === parseB.streamClaimId) {
- return true;
- } else {
- return false;
- }
-}
-
-module.exports = {
- parseURI,
- buildURI,
- normalizeURI,
- isURIValid,
- isURIEqual,
- isNameValid,
- isURIClaimable,
- splitBySeparator,
- convertToShareLink,
-};
diff --git a/web/src/robots.js b/web/src/robots.js
deleted file mode 100644
index c76140386..000000000
--- a/web/src/robots.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-
-let robots;
-async function getRobots(ctx) {
- if (!robots) {
- robots = fs.readFileSync(path.join(__dirname, '/../dist/public/robots.txt'), 'utf8');
- }
- return robots;
-}
-
-module.exports = { getRobots };
diff --git a/web/src/routes.js b/web/src/routes.js
deleted file mode 100644
index 38f1ceb38..000000000
--- a/web/src/routes.js
+++ /dev/null
@@ -1,76 +0,0 @@
-const { getHtml } = require('./html');
-const { getRss } = require('./rss');
-const { getHomepageJSON } = require('./getHomepageJSON');
-const { generateStreamUrl } = require('../../ui/util/web');
-const fetch = require('node-fetch');
-const Router = require('@koa/router');
-const { CUSTOM_HOMEPAGE } = require('../../config.js');
-
-// So any code from 'lbry-redux'/'lbryinc' that uses `fetch` can be run on the server
-global.fetch = fetch;
-
-const router = new Router();
-
-function getStreamUrl(ctx) {
- const { claimName, claimId } = ctx.params;
-
- const streamUrl = generateStreamUrl(claimName, claimId);
- return streamUrl;
-}
-
-const rssMiddleware = async (ctx) => {
- const rss = await getRss(ctx);
- if (rss.startsWith(' {
- if (!CUSTOM_HOMEPAGE) {
- ctx.status = 404;
- ctx.body = {
- message: 'Not Found',
- };
- } else {
- let content;
- try {
- content = getHomepageJSON();
- ctx.set('Content-Type', 'application/json');
- ctx.body = {
- status: 'success',
- data: content,
- };
- } catch (err) {
- ctx.status = err.statusCode || err.status || 500;
- ctx.body = {
- message: err.message,
- };
- }
- }
-});
-
-router.get(`/$/download/:claimName/:claimId`, async (ctx) => {
- const streamUrl = getStreamUrl(ctx);
- const downloadUrl = `${streamUrl}?download=1`;
- ctx.redirect(downloadUrl);
-});
-
-router.get(`/$/stream/:claimName/:claimId`, async (ctx) => {
- const streamUrl = getStreamUrl(ctx);
- ctx.redirect(streamUrl);
-});
-
-router.get(`/$/activate`, async (ctx) => {
- ctx.redirect(`https://sso.odysee.com/auth/realms/Users/device`);
-});
-
-router.get(`/$/rss/:claimName/:claimId`, rssMiddleware);
-router.get(`/$/rss/:claimName::claimId`, rssMiddleware);
-
-router.get('*', async (ctx) => {
- const html = await getHtml(ctx);
- ctx.body = html;
-});
-
-module.exports = router;
diff --git a/web/src/rss.js b/web/src/rss.js
deleted file mode 100644
index 0974000e3..000000000
--- a/web/src/rss.js
+++ /dev/null
@@ -1,298 +0,0 @@
-const { generateStreamUrl } = require('../../ui/util/web');
-const { URL, SITE_NAME, LBRY_WEB_API } = require('../../config.js');
-const { lbryProxy: Lbry } = require('../lbry');
-const Rss = require('rss');
-const Mime = require('mime-types');
-
-const SDK_API_PATH = `${LBRY_WEB_API}/api/v1`;
-const proxyURL = `${SDK_API_PATH}/proxy`;
-Lbry.setDaemonConnectionString(proxyURL);
-
-const NUM_ENTRIES = 500;
-
-// ****************************************************************************
-// Fetch claim info
-// ****************************************************************************
-
-async function doClaimSearch(options) {
- let results;
- try {
- results = await Lbry.claim_search(options);
- } catch {}
- return results ? results.items : undefined;
-}
-
-async function getChannelClaim(name, claimId) {
- let claim;
- let error;
-
- try {
- const url = `lbry://${name}#${claimId}`;
- const response = await Lbry.resolve({ urls: [url] });
- if (response && response[url] && !response[url].error) {
- claim = response && response[url];
- }
- } catch {}
-
- if (!claim) {
- error = 'The RSS URL is invalid or is not associated with any channel.';
- }
-
- return { claim, error };
-}
-
-async function getClaimsFromChannel(claimId, count) {
- const options = {
- channel_ids: [claimId],
- page_size: count,
- has_source: true,
- claim_type: 'stream',
- order_by: ['release_time'],
- no_totals: true,
- };
-
- return await doClaimSearch(options);
-}
-
-// ****************************************************************************
-// Helpers
-// ****************************************************************************
-
-function encodeWithSpecialCharEncode(string) {
- // encodeURIComponent doesn't encode `'` and others
- // which other services may not like
- return encodeURIComponent(string).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
-}
-
-const generateEnclosureForClaimContent = (claim) => {
- const value = claim.value;
- if (!value || !value.stream_type) {
- return undefined;
- }
- const fileExt = value.source && value.source.media_type && '.' + Mime.extension(value.source.media_type);
-
- switch (value.stream_type) {
- case 'video':
- return {
- url: generateStreamUrl(claim.name, claim.claim_id) + (fileExt || '.mp4'),
- type: (value.source && value.source.media_type) || 'video/mp4',
- size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
- };
-
- case 'audio':
- return {
- url: generateStreamUrl(claim.name, claim.claim_id) + ((fileExt === '.mpga' ? '.mp3' : fileExt) || '.mp3'),
- type: (value.source && value.source.media_type) || 'audio/mpeg',
- size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
- };
- case 'image':
- return {
- url: generateStreamUrl(claim.name, claim.claim_id) + (fileExt || '.jpeg'),
- type: (value.source && value.source.media_type) || 'image/jpeg',
- size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
- };
- case 'document':
- case 'software':
- return {
- url: generateStreamUrl(claim.name, claim.claim_id),
- type: (value.source && value.source.media_type) || undefined,
- size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
- };
-
- default:
- return undefined;
- }
-};
-
-const getLanguageValue = (claim) => {
- if (claim && claim.value && claim.value.languages && claim.value.languages.length > 0) {
- return claim.value.languages[0];
- }
- return 'en';
-};
-
-const replaceLineFeeds = (str) => str.replace(/(?:\r\n|\r|\n)/g, ' ');
-
-const isEmailRoughlyValid = (email) => /^\S+@\S+$/.test(email);
-
-/**
- * 'itunes:owner' is required by castfeedvalidator (w3c allows omission), and
- * both name and email must be defined. The email must also be a "valid" one.
- *
- * Use a fallback email when the creator did not specify one. The email will not
- * be shown to the user; it is just used for administrative purposes.
- *
- * @param claim
- * @returns any
- */
-const generateItunesOwnerElement = (claim) => {
- let email = 'no-reply@odysee.com';
- let name = claim && (claim.value && claim.value.title ? claim.value.title : claim.name);
-
- if (claim && claim.value) {
- if (isEmailRoughlyValid(claim.value.email)) {
- email = claim.value.email;
- }
- }
-
- return {
- 'itunes:owner': [{ 'itunes:name': name }, { 'itunes:email': email }],
- };
-};
-
-const generateItunesExplicitElement = (claim) => {
- const tags = (claim && claim.value && claim.value.tags) || [];
- return { 'itunes:explicit': tags.includes('mature') ? 'yes' : 'no' };
-};
-
-const getItunesCategory = (claim) => {
- const itunesCategories = [
- 'Arts',
- 'Business',
- 'Comedy',
- 'Education',
- 'Fiction',
- 'Government',
- 'History',
- 'Health & Fitness',
- 'Kids & Family',
- 'Leisure',
- 'Music',
- 'News',
- 'Religion & Spirituality',
- 'Science',
- 'Society & Culture',
- 'Sports',
- 'Technology',
- 'True Crime',
- 'TV & Film',
- ];
-
- const tags = (claim && claim.value && claim.value.tags) || [];
-
- for (let i = 0; i < itunesCategories.length; ++i) {
- const itunesCategory = itunesCategories[i];
- if (tags.includes(itunesCategory.toLowerCase())) {
- // "Note: Although you can specify more than one category and subcategory
- // in your RSS feed, Apple Podcasts only recognizes the first category and
- // subcategory."
- // --> The only parse the first found tag.
- return itunesCategory.replace('&', '&');
- }
- }
-
- // itunes will not accept any other categories, and the element is required
- // to pass castfeedvalidator. So, fallback to 'Leisure' (closes to "General")
- // if the creator did not specify a tag.
- return 'Leisure';
-};
-
-const generateItunesDurationElement = (claim) => {
- let duration;
- if (claim && claim.value) {
- if (claim.value.video) {
- duration = claim.value.video.duration;
- } else if (claim.value.audio) {
- duration = claim.value.audio.duration;
- }
- }
-
- if (duration) {
- return { 'itunes:duration': `${duration}` };
- }
-};
-
-const generateItunesImageElement = (claim) => {
- const thumbnailUrl = (claim && claim.value && claim.value.thumbnail && claim.value.thumbnail.url) || '';
- if (thumbnailUrl) {
- return {
- 'itunes:image': { _attr: { href: thumbnailUrl } },
- };
- }
-};
-
-const getFormattedDescription = (claim) => {
- return replaceLineFeeds((claim && claim.value && claim.value.description) || '');
-};
-
-// ****************************************************************************
-// Generate
-// ****************************************************************************
-
-function generateFeed(feedLink, channelClaim, claimsInChannel) {
- // --- Channel ---
- let channelTitle = (channelClaim.value && channelClaim.value.title) || channelClaim.name;
- const feed = new Rss({
- title: channelTitle + ' on ' + SITE_NAME,
- description: getFormattedDescription(channelClaim),
- feed_url: feedLink,
- site_url: (channelClaim.value && channelClaim.value.website_url) || URL,
- image_url: (channelClaim.value && channelClaim.value.thumbnail && channelClaim.value.thumbnail.url) || undefined,
- language: getLanguageValue(channelClaim),
- custom_namespaces: { itunes: 'http://www.itunes.com/dtds/podcast-1.0.dtd' },
- custom_elements: [
- { 'itunes:author': channelTitle },
- {
- 'itunes:category': [
- {
- _attr: {
- text: getItunesCategory(channelClaim),
- },
- },
- ],
- },
- generateItunesImageElement(channelClaim),
- generateItunesOwnerElement(channelClaim),
- generateItunesExplicitElement(channelClaim),
- ],
- });
-
- // --- Content ---
- claimsInChannel.forEach((c) => {
- const title = (c.value && c.value.title) || c.name;
- const thumbnailUrl = (c.value && c.value.thumbnail && c.value.thumbnail.url) || '';
- const thumbnailHtml = thumbnailUrl
- ? `
`
- : '';
- const description = thumbnailHtml + getFormattedDescription(c);
-
- const url = `${URL}/${encodeWithSpecialCharEncode(c.name)}:${c.claim_id}`;
- const date = c.release_time ? c.release_time * 1000 : c.meta && c.meta.creation_timestamp * 1000;
-
- feed.item({
- title: title,
- description: description,
- url: url,
- guid: undefined, // defaults to 'url'
- author: undefined, // defaults feed author property
- date: new Date(date),
- enclosure: generateEnclosureForClaimContent(c),
- custom_elements: [
- { 'itunes:title': title },
- { 'itunes:author': channelTitle },
- generateItunesImageElement(c),
- generateItunesDurationElement(c),
- generateItunesExplicitElement(c),
- ],
- });
- });
-
- return feed;
-}
-
-async function getRss(ctx) {
- if (!ctx.params.claimName || !ctx.params.claimId) {
- return 'Invalid URL';
- }
-
- const { claim: channelClaim, error } = await getChannelClaim(ctx.params.claimName, ctx.params.claimId);
- if (error) {
- return error;
- }
-
- const latestClaimsInChannel = await getClaimsFromChannel(channelClaim.claim_id, NUM_ENTRIES);
- const feed = generateFeed(`${URL}${ctx.request.url}`, channelClaim, latestClaimsInChannel);
- return feed.xml();
-}
-
-module.exports = { getRss };
diff --git a/web/src/xml.js b/web/src/xml.js
deleted file mode 100644
index 9cd48fe44..000000000
--- a/web/src/xml.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const { URL, SITE_TITLE, FAVICON } = require('../../config.js');
-const favicon = FAVICON || `${URL}/public/favicon.png`;
-function getOpenSearchXml() {
- return (
- `${SITE_TITLE} ` +
- `Search ${SITE_TITLE} ` +
- 'UTF-8 ' +
- `${favicon} ` +
- ` ` +
- `${URL} `
- );
-}
-
-function insertVariableXml(fullXml, xmlToInsert) {
- return fullXml.replace(/.*/s, xmlToInsert);
-}
-
-module.exports = { getOpenSearchXml, insertVariableXml };
diff --git a/web/static/pwa/icon-180.png b/web/static/pwa/icon-180.png
deleted file mode 100644
index ea2380843a712979efc88f5ca9f12a3de254c1ca..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 12574
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
F{|A82Rfzxq
diff --git a/web/static/pwa/icon-512.png b/web/static/pwa/icon-512.png
deleted file mode 100644
index fde3b3d72b9c043c3e73c5839df1022fb5222d6d..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 61476
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