218 lines
6.7 KiB
TypeScript
218 lines
6.7 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from "react";
|
|
import ArtistPatternBg from "./artist-pattern-bg";
|
|
|
|
type AdFormat = "auto" | "fluid" | "rectangle" | "horizontal" | "vertical" | "autorelaxed";
|
|
|
|
interface AdSenseProps {
|
|
slot: string;
|
|
format?: AdFormat;
|
|
responsive?: boolean;
|
|
className?: string;
|
|
style?: Record<string, string>;
|
|
layout?: string;
|
|
layoutKey?: string;
|
|
onAdStatus?: (filled: boolean) => void;
|
|
}
|
|
|
|
export default function AdSense({
|
|
slot,
|
|
format = "auto",
|
|
responsive = true,
|
|
className = "",
|
|
style,
|
|
layout,
|
|
layoutKey,
|
|
onAdStatus,
|
|
}: AdSenseProps) {
|
|
const adRef = useRef<HTMLModElement>(null);
|
|
const pushed = useRef(false);
|
|
|
|
useEffect(() => {
|
|
if (pushed.current) return;
|
|
try {
|
|
const adsbygoogle = (window as any).adsbygoogle || [];
|
|
adsbygoogle.push({});
|
|
pushed.current = true;
|
|
} catch (e) {
|
|
}
|
|
|
|
if (!onAdStatus) return;
|
|
const ins = adRef.current;
|
|
if (!ins) return;
|
|
|
|
// Google postavi data-ad-status na "filled", "unfilled" ali "unfill-optimized".
|
|
// Ce statusa ni, se zanesemo na dejansko visino. Odlocimo se enkrat.
|
|
let decided = false;
|
|
const decide = (filled: boolean) => {
|
|
if (decided) return;
|
|
decided = true;
|
|
obs.disconnect();
|
|
clearTimeout(timer);
|
|
onAdStatus(filled);
|
|
};
|
|
// Visina samega <ins> ni dokaz - nekatere enote imajo rezerviran prostor.
|
|
// Napolnjen oglas ima notri okvir (iframe) z dejansko visino.
|
|
const hasFrame = () => {
|
|
const f = ins.querySelector("iframe");
|
|
return !!f && (f as HTMLIFrameElement).clientHeight > 10;
|
|
};
|
|
const check = () => {
|
|
const st = ins.getAttribute("data-ad-status") || "";
|
|
if (st === "filled") return decide(true);
|
|
if (st.startsWith("unfill")) return decide(false);
|
|
if (hasFrame()) return decide(true);
|
|
};
|
|
const obs = new MutationObserver(check);
|
|
obs.observe(ins, { attributes: true, childList: true, subtree: true, attributeFilter: ["data-ad-status", "style"] });
|
|
const timer = setTimeout(() => decide(hasFrame()), 6000);
|
|
check();
|
|
return () => { obs.disconnect(); clearTimeout(timer); };
|
|
}, []);
|
|
|
|
return (
|
|
<div className={`ad-container ${className}`} data-testid={`ad-slot-${slot}`}>
|
|
<ins
|
|
ref={adRef}
|
|
className="adsbygoogle"
|
|
style={style || { display: "block" }}
|
|
data-ad-client="ca-pub-4465464714854276"
|
|
data-ad-slot={slot}
|
|
data-ad-format={format}
|
|
data-full-width-responsive={responsive ? "true" : undefined}
|
|
{...(layout ? { "data-ad-layout": layout } : {})}
|
|
{...(layoutKey ? { "data-ad-layout-key": layoutKey } : {})}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
let adSeedCounter = 1;
|
|
|
|
export function ArticleCardAd() {
|
|
const seed = useMemo(() => adSeedCounter++, []);
|
|
const [filled, setFilled] = useState<boolean | null>(null);
|
|
if (filled === false) return null;
|
|
return (
|
|
<ArtistPatternBg className="rounded-md border border-card-border h-full bg-card" seed={seed}>
|
|
<AdSense
|
|
slot="3854634730"
|
|
format="fluid"
|
|
layoutKey="-6r+cy-10+8a-3"
|
|
style={{ display: "block" }}
|
|
onAdStatus={setFilled}
|
|
/>
|
|
</ArtistPatternBg>
|
|
);
|
|
}
|
|
|
|
export function TopBannerAd() {
|
|
const [filled, setFilled] = useState<boolean | null>(null);
|
|
if (filled === false) return null;
|
|
const shown = filled === true;
|
|
return (
|
|
<div
|
|
className={shown
|
|
? "rounded-md overflow-hidden bg-card border border-card-border mb-4 flex flex-col items-center"
|
|
: "flex flex-col items-center"}
|
|
data-testid="ad-top-banner"
|
|
>
|
|
{shown && (
|
|
<div className="text-[9px] text-muted-foreground/40 text-center pt-1 uppercase tracking-widest w-full">Anzeige</div>
|
|
)}
|
|
<AdSense
|
|
slot="4154017639"
|
|
format="horizontal"
|
|
className="w-full"
|
|
style={{ display: "block", width: "100%", height: "90px", textAlign: "center", margin: "0 auto" }}
|
|
responsive={false}
|
|
onAdStatus={setFilled}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function InArticleAd() {
|
|
// Prava AdSense In-article enota (fluid + data-ad-layout="in-article").
|
|
// Kadar oglasa ni, se okvir in rezerviran prostor ne izriseta.
|
|
const [filled, setFilled] = useState<boolean | null>(null);
|
|
if (filled === false) return null;
|
|
const shown = filled === true;
|
|
return (
|
|
<div
|
|
className={shown ? "my-6 rounded-md overflow-hidden bg-card border border-card-border" : ""}
|
|
style={shown ? { minHeight: "260px" } : undefined}
|
|
>
|
|
{shown && (
|
|
<div className="text-[9px] text-muted-foreground/40 text-center pt-1 uppercase tracking-widest">Anzeige</div>
|
|
)}
|
|
<AdSense
|
|
slot="7672178246"
|
|
format="fluid"
|
|
layout="in-article"
|
|
style={{ display: "block", textAlign: "center" }}
|
|
onAdStatus={setFilled}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function MultiplexAd() {
|
|
const [filled, setFilled] = useState<boolean | null>(null);
|
|
if (filled === false) return null;
|
|
const shown = filled === true;
|
|
return (
|
|
<div className={shown ? "bg-card rounded-md border border-card-border overflow-hidden" : ""}>
|
|
<AdSense
|
|
slot="4593001335"
|
|
format="autorelaxed"
|
|
style={{ display: "block" }}
|
|
className={shown ? "w-full min-h-[250px]" : "w-full"}
|
|
onAdStatus={setFilled}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PageSideAds({ contentHalfWidth = 640 }: { contentHalfWidth?: number }) {
|
|
const gap = 10;
|
|
const offset = contentHalfWidth + gap;
|
|
return (
|
|
<div className="relative">
|
|
<div className="hidden 2xl:flex fixed top-0 bottom-0 w-[160px] z-10 items-center justify-center pointer-events-none" style={{ left: `calc(50% - ${offset}px - 160px)` }} data-testid="ad-left-sidebar">
|
|
<div className="pointer-events-auto">
|
|
<AdSense
|
|
slot="2398082838"
|
|
format="vertical"
|
|
style={{ display: "block", width: "160px", height: "600px" }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="hidden 2xl:flex fixed top-0 bottom-0 w-[160px] z-10 items-center justify-center pointer-events-none" style={{ right: `calc(50% - ${offset}px - 160px)` }} data-testid="ad-right-sidebar">
|
|
<div className="pointer-events-auto">
|
|
<AdSense
|
|
slot="2398082838"
|
|
format="vertical"
|
|
style={{ display: "block", width: "160px", height: "600px" }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SidebarAd() {
|
|
const [visible, setVisible] = useState(false);
|
|
|
|
return (
|
|
<div className={`sticky top-4 ${visible ? "" : "hidden"}`}>
|
|
<div className="bg-card rounded-md border border-card-border p-4 mt-4">
|
|
<AdSense
|
|
slot="2398082838"
|
|
format="auto"
|
|
onAdStatus={(filled) => setVisible(filled)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|