Ads: replace DOM manipulations with React components

- Instead of 2 ways to display ads (DOM injection + React method) and having both of them clash, just do it the predictable React way.
    - Augment the existing React version to support tile layout + ability to place in last visible slot.
    - Consolidate styling code to scss ... DOM manipulations were making it even harder to maintain.
    - Removed the need to check for ad-blockers for now. It was being executed every time an ad is displayed, and now that we are displaying ads in more places, the gains doesn't justify the performance loss. Also, it wasn't being done for Recommended ads anyway, so the inconsistency probably means it's not needed in the first place.

Other known issues fixed:
- double ad injection when changing language via nag.
- additional "total-blocking-time" due to ads at startup removed.
- fixed ads not appearing in mobile homepage until navigated away and back to homepage.
- enable ads in channel page.
- support for both List and Tile layout.
This commit is contained in:
infinite-persistence 2022-03-07 20:11:28 +08:00 committed by Thomas Zarebczan
parent e2f73a30c6
commit 0143b63c74
12 changed files with 270 additions and 232 deletions

View file

@ -175,8 +175,6 @@ function ChannelContent(props: Props) {
{!channelIsMine && claimsInChannel > 0 && <HiddenNsfwClaims uri={uri} />}
{/* <Ads type="homepage" /> */}
{!fetching && (
<ClaimListDiscover
ignoreSearchInLanguage
@ -196,7 +194,7 @@ function ChannelContent(props: Props) {
defaultOrderBy={CS.ORDER_BY_NEW}
pageSize={defaultPageSize}
infiniteScroll={defaultInfiniteScroll}
injectedItem={SHOW_ADS && !isAuthenticated && IS_WEB && <Ads type="video" />}
injectedItem={SHOW_ADS && !isAuthenticated && { node: <Ads type="video" tileLayout={tileLayout} small /> }}
meta={
showFilters && (
<Form onSubmit={() => {}} className="wunderbar--inline">

View file

@ -7,6 +7,7 @@ import ClaimPreview from 'component/claimPreview';
import Spinner from 'component/spinner';
import { FormField } from 'component/common/form';
import usePersistedState from 'effects/use-persisted-state';
import useLastVisibleItem from 'effects/use-last-visible-item';
import debounce from 'util/debounce';
import ClaimPreviewTile from 'component/claimPreviewTile';
@ -40,7 +41,7 @@ type Props = {
renderActions?: (Claim) => ?Node,
renderProperties?: (Claim) => ?Node,
includeSupportAction?: boolean,
injectedItem: ?Node,
injectedItem?: { node: Node, index?: number, replace?: boolean },
timedOutMessage?: Node,
tileLayout?: boolean,
searchInLanguage: boolean,
@ -99,6 +100,9 @@ export default function ClaimList(props: Props) {
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
const listRef = React.useRef();
const injectedIndex = useLastVisibleItem(injectedItem, listRef);
// Exclude prefix uris in these results variables. We don't want to show
// anything if the search failed or timed out.
const timedOut = uris === null;
@ -142,6 +146,12 @@ export default function ClaimList(props: Props) {
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch';
}, []);
// @if process.env.NODE_ENV!='production'
if (injectedItem && injectedItem.replace) {
throw new Error('claimList: "injectedItem.replace" is not implemented yet');
}
// @endif
useEffect(() => {
const handleScroll = debounce((e) => {
if (page && pageSize && onScrollBottom) {
@ -188,19 +198,28 @@ export default function ClaimList(props: Props) {
/>
);
const getInjectedItem = (index) => {
if (injectedItem && injectedItem.node && injectedIndex === index) {
return injectedItem.node;
}
return null;
};
return tileLayout && !header ? (
<section className={classnames('claim-grid', { 'swipe-list': swipeLayout })}>
<section ref={listRef} className={classnames('claim-grid', { 'swipe-list': swipeLayout })}>
{urisLength > 0 &&
tileUris.map((uri) => (
<ClaimPreviewTile
key={uri}
uri={uri}
showHiddenByUser={showHiddenByUser}
properties={renderProperties}
collectionId={collectionId}
showNoSourceClaims={showNoSourceClaims}
swipeLayout={swipeLayout}
/>
tileUris.map((uri, index) => (
<React.Fragment key={uri}>
{getInjectedItem(index)}
<ClaimPreviewTile
uri={uri}
showHiddenByUser={showHiddenByUser}
properties={renderProperties}
collectionId={collectionId}
showNoSourceClaims={showNoSourceClaims}
swipeLayout={swipeLayout}
/>
</React.Fragment>
))}
{loading && useLoadingSpinner && <ClaimPreviewTile placeholder="loading" swipeLayout={swipeLayout} />}
{!timedOut && urisLength === 0 && !loading && <div className="empty main--empty">{empty || noResultMsg}</div>}
@ -248,11 +267,10 @@ export default function ClaimList(props: Props) {
'swipe-list': swipeLayout,
})}
{...(droppableProvided && droppableProvided.droppableProps)}
ref={droppableProvided && droppableProvided.innerRef}
ref={droppableProvided ? droppableProvided.innerRef : listRef}
>
{droppableProvided ? (
<>
{injectedItem && <li>{injectedItem}</li>}
{sortedUris.map((uri, index) => (
<React.Suspense fallback={null} key={uri}>
<Draggable draggableId={uri} index={index}>
@ -285,7 +303,7 @@ export default function ClaimList(props: Props) {
) : (
sortedUris.map((uri, index) => (
<React.Fragment key={uri}>
{injectedItem && index === 4 && <li>{injectedItem}</li>}
{getInjectedItem(index)}
{getClaimPreview(uri, index)}
</React.Fragment>
))

View file

@ -74,7 +74,7 @@ type Props = {
header?: Node,
headerLabel?: string | Node,
hiddenNsfwMessage?: Node,
injectedItem: ?Node,
injectedItem?: { node: Node, index?: number, replace?: boolean },
meta?: Node,
subSection?: Node, // Additional section below [Header|Meta]
renderProperties?: (Claim) => Node,

View file

@ -3,6 +3,7 @@ import type { Node } from 'react';
import React from 'react';
import ClaimPreviewTile from 'component/claimPreviewTile';
import useFetchViewCount from 'effects/use-fetch-view-count';
import useLastVisibleItem from 'effects/use-last-visible-item';
function urisEqual(prev: ?Array<string>, next: ?Array<string>) {
if (!prev || !next) {
@ -25,6 +26,7 @@ type Props = {
prefixUris?: Array<string>,
pinUrls?: Array<string>,
uris: Array<string>,
injectedItem?: { node: Node, index?: number, replace?: boolean },
showNoSourceClaims?: boolean,
renderProperties?: (Claim) => ?Node,
fetchViewCount?: boolean,
@ -67,14 +69,17 @@ function ClaimTilesDiscover(props: Props) {
renderProperties,
pinUrls,
prefixUris,
injectedItem,
showNoSourceClaims,
doFetchViewCount,
pageSize = 8,
optionsStringified,
} = props;
const prevUris = React.useRef();
const sectionRef = React.useRef();
const injectedIndex = useLastVisibleItem(injectedItem, sectionRef);
const prevUris = React.useRef();
const claimSearchUris = claimSearchResults || [];
const isUnfetchedClaimSearch = claimSearchResults === undefined;
@ -118,17 +123,23 @@ function ClaimTilesDiscover(props: Props) {
}, [doClaimSearch, shouldPerformSearch, optionsStringified]);
return (
<ul className="claim-grid">
<ul ref={sectionRef} className="claim-grid">
{finalUris && finalUris.length
? finalUris.map((uri, i) => {
if (uri) {
if (injectedIndex === i && injectedItem && injectedItem.replace) {
return <React.Fragment key={uri}>{injectedItem.node}</React.Fragment>;
}
return (
<ClaimPreviewTile
showNoSourceClaims={hasNoSource || showNoSourceClaims}
key={uri}
uri={uri}
properties={renderProperties}
/>
<React.Fragment key={uri}>
{injectedIndex === i && injectedItem && injectedItem.node}
<ClaimPreviewTile
showNoSourceClaims={hasNoSource || showNoSourceClaims}
uri={uri}
properties={renderProperties}
/>
</React.Fragment>
);
} else {
return <ClaimPreviewTile showNoSourceClaims={hasNoSource || showNoSourceClaims} key={i} placeholder />;

View file

@ -70,6 +70,14 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
const isMedium = useIsMediumScreen();
const { onRecsLoaded: onRecommendationsLoaded, onClickedRecommended: onRecommendationClicked } = RecSys;
const InjectedAd =
injectAds && !blacklistTriggered
? {
node: <Ads small type="video" className="ads__claim-item--recommended" />,
index: isMobile ? 0 : 3,
}
: null;
React.useEffect(() => {
doFetchRecommendedContent(uri);
}, [uri, doFetchRecommendedContent]);
@ -133,7 +141,7 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
loading={isSearching}
uris={recommendedContentUris}
hideMenu={isMobile}
injectedItem={injectAds && !blacklistTriggered && <Ads small type={'video'} />}
injectedItem={InjectedAd}
empty={__('No related content found')}
onClick={handleRecommendationClicked}
/>
@ -152,7 +160,7 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
channelIds={[signingChannel.claim_id]}
loading={isSearching}
hideMenu={isMobile}
injectedItem={SHOW_ADS && IS_WEB && !isAuthenticated && <Ads small type={'video'} />}
injectedItem={InjectedAd}
empty={__('No related content found')}
/>
)}

View file

@ -0,0 +1,46 @@
// @flow
import React from 'react';
import type { Node } from 'react';
type InjectedItem = { node: Node, index?: number, replace?: boolean };
export default function useLastVisibleItem(injectedItem: ?InjectedItem, listRef: any) {
const [injectedIndex, setInjectedIndex] = React.useState(injectedItem?.index);
React.useEffect(() => {
// Move to default injection index (last visible item)
if (injectedItem && injectedItem.index === undefined) {
// AD_INJECTION_DELAY_MS = average total-blocking-time incurred for
// loading ads. Delay to let higher priority tasks run first. Ideally,
// should use 'requestIdleCallback/requestAnimationFrame'.
const AD_INJECTION_DELAY_MS = 1500;
const timer = setTimeout(() => {
if (listRef.current) {
const screenBottom = window.innerHeight;
const items = listRef.current.children;
if (items.length) {
let i = 2; // Start from 2, so that the min possible is index-1
for (; i < items.length; ++i) {
const rect = items[i].getBoundingClientRect();
if (rect.top > screenBottom || rect.bottom > screenBottom) {
break;
}
}
setInjectedIndex(i - 1);
return;
}
}
// Fallback to index-1 (2nd item) for failures. No retries.
setInjectedIndex(1);
}, AD_INJECTION_DELAY_MS);
return () => clearTimeout(timer);
}
}, []);
return injectedIndex;
}

View file

@ -12,7 +12,7 @@ import { useIsMobile } from 'effects/use-screensize';
import analytics from 'analytics';
import HiddenNsfw from 'component/common/hidden-nsfw';
import Icon from 'component/common/icon';
import Ads, { injectAd } from 'web/component/ads';
import Ads from 'web/component/ads';
import LbcSymbol from 'component/common/lbc-symbol';
import I18nMessage from 'component/i18nMessage';
import moment from 'moment';
@ -54,6 +54,7 @@ function DiscoverPage(props: Props) {
const buttonRef = useRef();
const isHovering = useHover(buttonRef);
const isMobile = useIsMobile();
const isWildWest = window.location.pathname === `/$/${PAGES.WILD_WEST}`;
const urlParams = new URLSearchParams(search);
const langParam = urlParams.get(CS.LANGUAGE_KEY) || null;
@ -175,18 +176,8 @@ function DiscoverPage(props: Props) {
);
}
React.useEffect(() => {
const hasAdOnPage = document.querySelector('.homepageAdContainer');
if (hasAdOnPage || isAuthenticated || !SHOW_ADS || window.location.pathname === `/$/${PAGES.WILD_WEST}`) {
return;
}
injectAd();
}, [isAuthenticated]);
return (
<Page noFooter fullWidthPage={tileLayout} className="main__discover">
<Ads type="homepage" />
<ClaimListDiscover
pins={getPins(dynamicRouteProps)}
hideFilters={SIMPLE_SITE ? !(dynamicRouteProps || tags) : undefined}
@ -200,7 +191,7 @@ function DiscoverPage(props: Props) {
hiddenNsfwMessage={<HiddenNsfw type="page" />}
repostedClaimId={repostedClaim ? repostedClaim.claim_id : null}
injectedItem={
SHOW_ADS && IS_WEB ? (SIMPLE_SITE ? false : !isAuthenticated && <Ads small type={'video'} />) : false
SHOW_ADS && !isAuthenticated && !isWildWest && { node: <Ads small type="video" tileLayout={tileLayout} /> }
}
// Assume wild west page if no dynamicRouteProps
// Not a very good solution, but just doing it for now

View file

@ -1,8 +1,7 @@
// @flow
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import { SITE_NAME, SIMPLE_SITE, ENABLE_NO_SOURCE_CLAIMS, SHOW_ADS } from 'config';
import Ads, { injectAd } from 'web/component/ads';
import { SHOW_ADS, SITE_NAME, SIMPLE_SITE, ENABLE_NO_SOURCE_CLAIMS } from 'config';
import React, { useState } from 'react';
import Page from 'component/page';
import Button from 'component/button';
@ -16,6 +15,7 @@ import { getLivestreamUris } from 'util/livestream';
import ScheduledStreams from 'component/scheduledStreams';
import { splitBySeparator } from 'util/lbryURI';
import classnames from 'classnames';
import Ads from 'web/component/ads';
// @if TARGET='web'
import Meme from 'web/component/meme';
@ -99,6 +99,9 @@ function HomePage(props: Props) {
hasSource
prefixUris={getLivestreamUris(activeLivestreams, options.channelIds)}
pinUrls={pinUrls}
injectedItem={
index === 0 && SHOW_ADS && !authenticated && { node: <Ads small type="video" tileLayout />, replace: true }
}
/>
);
@ -139,12 +142,6 @@ function HomePage(props: Props) {
doFetchActiveLivestreams();
}, []);
React.useEffect(() => {
const shouldShowAds = SHOW_ADS && !authenticated;
// inject ad into last visible card
injectAd(shouldShowAds);
}, []);
const [hasScheduledStreams, setHasScheduledStreams] = useState(false);
const scheduledStreamsLoaded = (total) => setHasScheduledStreams(total > 0);
@ -167,7 +164,6 @@ function HomePage(props: Props) {
{/* @if TARGET='web' */}
{SIMPLE_SITE && <Meme />}
<Ads type="homepage" />
{/* @endif */}
{!fetchingActiveLivestreams && (

View file

@ -104,7 +104,11 @@ export default function SearchPage(props: Props) {
/>
}
injectedItem={
SHOW_ADS && IS_WEB ? (SIMPLE_SITE ? false : !isAuthenticated && <Ads small type={'video'} />) : false
SHOW_ADS &&
!isAuthenticated && {
node: <Ads small type="video" />,
index: 3,
}
}
/>

View file

@ -16,43 +16,145 @@
}
// Inline Video Ads
// The default is coded for list-layout;
// --tile and other modifiers adjust accordingly.
.ads__claim-item {
border-bottom: 1px solid var(--color-border);
margin-top: var(--spacing-m);
margin-bottom: var(--spacing-m);
padding: var(--spacing-m);
background-color: var(--color-ads-background);
border-radius: var(--border-radius);
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
> div,
ins {
width: 100%;
position: relative !important;
max-width: 30rem;
min-width: 15rem;
}
.ad__container {
aspect-ratio: 16 / 9;
}
$minWidth: calc(var(--file-list-thumbnail-width) * 0.8);
min-width: $minWidth;
.avp-p-gui {
z-index: 1 !important;
}
video {
width: 100% !important;
height: 100% !important;
}
@media (max-width: $breakpoint-small) {
flex-direction: column;
@media (max-width: $breakpoint-small) {
$width: calc(var(--file-list-thumbnail-width) * 0.8);
width: $width;
> div {
width: 100%;
#aniBox,
#av-container {
// Only needed on actual mobile. Their mobile script does something
// different that makes it 100%, so have to counter that here.
width: unset !important;
}
}
@media (min-width: $breakpoint-small) and (max-width: $breakpoint-large) {
$width: calc(var(--file-list-thumbnail-width) * 1.2);
width: $width;
}
@media (min-width: $breakpoint-large) {
$width: calc(var(--file-list-thumbnail-width) * 1.2);
width: $width;
}
//div[style*='position: fixed; transform: scale(1);'] {
// // This is the floating ad (when tile goes off screen).
// // The sidebar is covering it, so move to the right a bit:
// left: unset !important;
//
// // It's a bit jarring at times on a busy page, so add border:
// background-color: var(--color-ads-background);
// border-radius: var(--border-radius);
// padding: var(--spacing-s);
//}
div[style*='position: fixed; transform: scale(1);'] {
// [Floating ad]
// Hide it in entirely. Couldn't reconcile with the changes needed to make
// tile layout fit on browser-resize and also with their mobile script
// changes. Else, the block above can be used.
transform: none !important;
transform-origin: unset !important;
position: unset !important;
div:first-child {
// [Floating ad close button]
display: none !important;
}
}
#aniBox,
#av-container {
// Handle the scenario of ads not resizing when switching from small to
// medium/large layout:
height: unset !important;
aspect-ratio: 16 / 9 !important;
}
}
}
.ads__claim-item--tile {
@extend .card;
@extend .claim-preview--tile;
flex-direction: column;
padding: var(--spacing-s);
.ads__claim-text {
margin: var(--spacing-s) 0 0 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.ad__container {
width: 100% !important;
max-width: 100 !important;
min-width: unset !important;
box-sizing: border-box !important;
div:not(#sound):not(.off):not(#timeline) {
width: 100% !important;
max-width: unset !important;
min-width: unset !important;
box-sizing: border-box !important;
}
video {
width: 100% !important;
height: 100% !important;
}
#timeline,
#buttons {
width: calc(100% - (var(--spacing-s) * 2)) !important;
}
}
p {
width: calc(100% - (var(--spacing-m) * 3)) !important;
}
.ads__claim-text {
max-width: 100%;
}
}
.ads__claim-item--recommended {
padding: var(--spacing-s);
@media (min-width: $breakpoint-medium) {
margin-bottom: 0;
}
}
.ads__claim-text {
margin-top: var(--spacing-m);
overflow: hidden;
max-width: 50%;
margin: var(--spacing-m) 0 var(--spacing-m) var(--spacing-s);
display: flex;
flex-direction: column;
justify-content: center;
@ -60,6 +162,10 @@
.ads__claim-text--small {
font-size: var(--font-small);
@media (max-width: $breakpoint-small) {
font-size: var(--font-xsmall);
}
}
// Pre-roll ads

View file

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { selectTheme } from 'redux/selectors/settings';
import { makeSelectClaimForUri, selectClaimIsNsfwForUri } from 'redux/selectors/claims';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import Ads, { injectAd } from './view';
import Ads from './view';
const select = (state, props) => ({
theme: selectTheme(state),
@ -12,4 +12,3 @@ const select = (state, props) => ({
});
export default connect(select)(Ads);
export { injectAd };

View file

@ -28,9 +28,7 @@ const IS_IOS =
// for iOS 13+ , platform is MacIntel, so use this to test
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
!window.MSStream;
const IS_ANDROID = /Android/i.test(navigator.userAgent);
const IS_FIREFOX = /Firefox/i.test(navigator.userAgent);
const isFirefoxAndroid = IS_ANDROID && IS_FIREFOX;
@ -38,10 +36,12 @@ const isFirefoxAndroid = IS_ANDROID && IS_FIREFOX;
type Props = {
location: { pathname: string },
type: string,
tileLayout?: boolean,
small: boolean,
claim: Claim,
isMature: boolean,
authenticated: boolean,
className?: string,
};
function removeIfExists(querySelector) {
@ -53,11 +53,13 @@ function Ads(props: Props) {
const {
location: { pathname },
type = 'video',
tileLayout,
small,
authenticated,
className,
} = props;
const shouldShowAds = SHOW_ADS && !authenticated;
const shouldShowAds = SHOW_ADS && !authenticated && !isFirefoxAndroid;
const mobileAds = IS_ANDROID || IS_IOS;
// this is populated from app based on location
@ -66,8 +68,6 @@ function Ads(props: Props) {
// add script to DOM
useEffect(() => {
if (isFirefoxAndroid) return;
if (shouldShowAds) {
let script;
try {
@ -97,7 +97,6 @@ function Ads(props: Props) {
}
}, []);
// display to say "sign up to not see these"
const adsSignInDriver = (
<I18nMessage
tokens={{
@ -114,167 +113,29 @@ function Ads(props: Props) {
</I18nMessage>
);
// ad shown in the related videos area
const videoAd = (
<div className="ads__claim-item">
<div className="ad__container">
<div id={adConfig.tag} style={{ display: 'none' }} />
</div>
if (shouldShowAds && type === 'video') {
return (
<div
className={classnames('ads__claim-text', {
'ads__claim-text--small': small,
className={classnames('ads ads__claim-item', className, {
'ads__claim-item--tile': tileLayout,
})}
>
<div>Ad</div>
<p>{adsSignInDriver}</p>
<div className="ad__container">
<div id={adConfig.tag} />
</div>
<div
className={classnames('ads__claim-text', {
'ads__claim-text--small': small,
})}
>
<div>Ad</div>
<p>{adsSignInDriver}</p>
</div>
</div>
</div>
);
// homepage ad in a card
const homepageCardAd = (
<div className="homepageAdContainer media__thumb" style={{ display: 'none' }}>
<div id={adConfig.tag} className="homepageAdDiv media__thumb" style={{ display: 'none' }} />
</div>
);
if (!SHOW_ADS) {
return false;
);
}
// disable ads for firefox android because they don't work properly
if (isFirefoxAndroid) return false;
// sidebar ad (in recommended videos)
if (type === 'video') {
return videoAd;
}
if (type === 'homepage') {
return homepageCardAd;
}
}
// returns true if passed element is fully visible on screen
function isScrolledIntoView(el) {
const rect = el.getBoundingClientRect();
const elemTop = rect.top;
const elemBottom = rect.bottom;
// Only completely visible elements return true:
const isVisible = elemTop >= 0 && elemBottom <= window.innerHeight;
return isVisible;
}
async function injectAd(shouldShowAds: boolean) {
// don't inject on firefox android or for authenticated users or no ads on instance
if (isFirefoxAndroid || !shouldShowAds) return;
// test if adblock is enabled
let adBlockEnabled = false;
const googleAdUrl = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
try {
await fetch(new Request(googleAdUrl)).catch((_) => {
adBlockEnabled = true;
});
} catch (e) {
adBlockEnabled = true;
} finally {
if (!adBlockEnabled) {
// select the cards on page
let cards = document.getElementsByClassName('card claim-preview--tile');
// eslint-disable-next-line no-inner-declarations
function checkFlag() {
if (cards.length === 0) {
window.setTimeout(checkFlag, 100);
} else {
// find the last fully visible card
let lastCard;
// width of browser window
const windowWidth = window.innerWidth;
// on small screens, grab the second item
if (windowWidth <= 900) {
lastCard = cards[1];
} else {
// otherwise, get the last fully visible card
for (const card of cards) {
const isFullyVisible = isScrolledIntoView(card);
if (!isFullyVisible) break;
lastCard = card;
}
// if no last card was found, just exit the function to not cause errors
if (!lastCard) return;
}
// clone the last card
// $FlowFixMe
const clonedCard = lastCard.cloneNode(true);
// insert cloned card
// $FlowFixMe
lastCard.parentNode.insertBefore(clonedCard, lastCard);
// change the appearance of the cloned card
// $FlowFixMe
clonedCard.querySelector('.claim__menu-button').remove();
// $FlowFixMe
clonedCard.querySelector('.truncated-text').innerHTML = __(
'Hate these? Login to Odysee for an ad free experience'
);
// $FlowFixMe
clonedCard.querySelector('.claim-tile__info').remove();
// $FlowFixMe
clonedCard.querySelector('[role="none"]').removeAttribute('href');
// $FlowFixMe
clonedCard.querySelector('.claim-tile__header').firstChild.href = '/$/signin';
// $FlowFixMe
clonedCard.querySelector('.claim-tile__title').firstChild.removeAttribute('aria-label');
// $FlowFixMe
clonedCard.querySelector('.claim-tile__title').firstChild.removeAttribute('title');
// $FlowFixMe
clonedCard.querySelector('.claim-tile__header').firstChild.removeAttribute('aria-label');
// $FlowFixMe
clonedCard
.querySelector('.media__thumb')
.replaceWith(document.getElementsByClassName('homepageAdContainer')[0]);
// show the homepage ad which is not displayed at first
document.getElementsByClassName('homepageAdContainer')[0].style.display = 'block';
const thumbnail = window.getComputedStyle(lastCard.querySelector('.media__thumb'));
const styles = `#av-container, #AVcontent, #aniBox {
height: ${thumbnail.height} !important;
width: ${thumbnail.width} !important;
}`;
const styleSheet = document.createElement('style');
styleSheet.type = 'text/css';
styleSheet.id = 'customAniviewStyling';
styleSheet.innerText = styles;
// $FlowFixMe
document.head.appendChild(styleSheet);
// delete last card to not introduce layout shifts
lastCard.remove();
// addresses bug where ad doesn't show up until a scroll event
document.dispatchEvent(new CustomEvent('scroll'));
}
}
checkFlag();
}
}
return null;
}
export default withRouter(Ads);
export { injectAd };