Show ads in feed
This commit is contained in:
parent
6d1539cf9c
commit
b02141874e
4 changed files with 79 additions and 6 deletions
|
@ -6,13 +6,17 @@ import { FormattedMessage } from 'react-intl';
|
|||
import LoadGap from 'soapbox/components/load_gap';
|
||||
import ScrollableList from 'soapbox/components/scrollable_list';
|
||||
import StatusContainer from 'soapbox/containers/status_container';
|
||||
import Ad from 'soapbox/features/ads/components/ad';
|
||||
import FeedSuggestions from 'soapbox/features/feed-suggestions/feed-suggestions';
|
||||
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
|
||||
import PendingStatus from 'soapbox/features/ui/components/pending_status';
|
||||
import { useSoapboxConfig } from 'soapbox/hooks';
|
||||
import useAds from 'soapbox/queries/ads';
|
||||
|
||||
import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import type { VirtuosoHandle } from 'react-virtuoso';
|
||||
import type { IScrollableList } from 'soapbox/components/scrollable_list';
|
||||
import type { Ad as AdEntity } from 'soapbox/features/ads/providers';
|
||||
|
||||
interface IStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children'> {
|
||||
/** Unique key to preserve the scroll position when navigating back. */
|
||||
|
@ -37,6 +41,8 @@ interface IStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children'> {
|
|||
timelineId?: string,
|
||||
/** Whether to display a gap or border between statuses in the list. */
|
||||
divideType?: 'space' | 'border',
|
||||
/** Whether to display ads. */
|
||||
showAds?: boolean,
|
||||
}
|
||||
|
||||
/** Feed of statuses, built atop ScrollableList. */
|
||||
|
@ -49,8 +55,12 @@ const StatusList: React.FC<IStatusList> = ({
|
|||
timelineId,
|
||||
isLoading,
|
||||
isPartial,
|
||||
showAds = false,
|
||||
...other
|
||||
}) => {
|
||||
const { data: ads } = useAds();
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
const adsInterval = Number(soapboxConfig.extensions.getIn(['ads', 'interval'], 40)) || 0;
|
||||
const node = useRef<VirtuosoHandle>(null);
|
||||
|
||||
const getFeaturedStatusCount = () => {
|
||||
|
@ -123,6 +133,15 @@ const StatusList: React.FC<IStatusList> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const renderAd = (ad: AdEntity) => {
|
||||
return (
|
||||
<Ad
|
||||
card={ad.card}
|
||||
impression={ad.impression}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderPendingStatus = (statusId: string) => {
|
||||
const idempotencyKey = statusId.replace(/^末pending-/, '');
|
||||
|
||||
|
@ -156,17 +175,27 @@ const StatusList: React.FC<IStatusList> = ({
|
|||
|
||||
const renderStatuses = (): React.ReactNode[] => {
|
||||
if (isLoading || statusIds.size > 0) {
|
||||
return statusIds.toArray().map((statusId, index) => {
|
||||
return statusIds.toList().reduce((acc, statusId, index) => {
|
||||
const adIndex = ads ? Math.floor((index + 1) / adsInterval) % ads.length : 0;
|
||||
const ad = ads ? ads[adIndex] : undefined;
|
||||
const showAd = (index + 1) % adsInterval === 0;
|
||||
|
||||
if (statusId === null) {
|
||||
return renderLoadGap(index);
|
||||
acc.push(renderLoadGap(index));
|
||||
} else if (statusId.startsWith('末suggestions-')) {
|
||||
return renderFeedSuggestions();
|
||||
acc.push(renderFeedSuggestions());
|
||||
} else if (statusId.startsWith('末pending-')) {
|
||||
return renderPendingStatus(statusId);
|
||||
acc.push(renderPendingStatus(statusId));
|
||||
} else {
|
||||
return renderStatus(statusId);
|
||||
acc.push(renderStatus(statusId));
|
||||
}
|
||||
});
|
||||
|
||||
if (showAds && ad && showAd) {
|
||||
acc.push(renderAd(ad));
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [] as React.ReactNode[]);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
|
||||
import type { RootState } from 'soapbox/store';
|
||||
import type { Card } from 'soapbox/types/entities';
|
||||
|
||||
/** Map of available provider modules. */
|
||||
const PROVIDERS: Record<string, () => Promise<AdProvider>> = {
|
||||
soapbox: async() => (await import(/* webpackChunkName: "features/ads/soapbox" */'./soapbox-config')).default,
|
||||
};
|
||||
|
||||
/** Ad server implementation. */
|
||||
interface AdProvider {
|
||||
getAds(getState: () => RootState): Promise<Ad[]>,
|
||||
|
@ -14,4 +21,17 @@ interface Ad {
|
|||
impression?: string,
|
||||
}
|
||||
|
||||
/** Gets the current provider based on config. */
|
||||
const getProvider = async(getState: () => RootState): Promise<AdProvider | undefined> => {
|
||||
const state = getState();
|
||||
const soapboxConfig = getSoapboxConfig(state);
|
||||
const isEnabled = soapboxConfig.extensions.getIn(['ads', 'enabled'], false) === true;
|
||||
const providerName = soapboxConfig.extensions.getIn(['ads', 'provider'], 'soapbox') as string;
|
||||
|
||||
if (isEnabled && PROVIDERS[providerName]) {
|
||||
return PROVIDERS[providerName]();
|
||||
}
|
||||
};
|
||||
|
||||
export { getProvider };
|
||||
export type { Ad, AdProvider };
|
||||
|
|
|
@ -90,6 +90,7 @@ const HomeTimeline: React.FC = () => {
|
|||
onLoadMore={handleLoadMore}
|
||||
timelineId='home'
|
||||
divideType='space'
|
||||
showAds
|
||||
emptyMessage={
|
||||
<Stack space={1}>
|
||||
<Text size='xl' weight='medium' align='center'>
|
||||
|
|
23
app/soapbox/queries/ads.ts
Normal file
23
app/soapbox/queries/ads.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { Ad, getProvider } from 'soapbox/features/ads/providers';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
|
||||
export default function useAds() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const getAds = async() => {
|
||||
return dispatch(async(_, getState) => {
|
||||
const provider = await getProvider(getState);
|
||||
if (provider) {
|
||||
return provider.getAds(getState);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return useQuery<Ad[]>(['ads'], getAds, {
|
||||
placeholderData: [],
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue