Skip to content

Commit cc24af1

Browse files
authored
EthicalAd: fixed footer placement for specific themes (#618)
This is a POC to show how we can use fixed footer ad on specific known themes. The logic is as follows: - the theme has to be a known theme - the image ad added to the navbar has to be below the fold - show a fixed footer ad and we add some `padding-bottom` CSS style to the content container (theme-specific selector) with the height of the fixed footer ad (using fixed `47.2px` for now but it can be dynamically calculated) The technique of adding the `padding-bottom` to a theme-specific element solves the problem highlighted in the previous approach done in #447 ### Sphinx Furo theme [Peek 2025-07-07 12-59.webm](https://github.com/user-attachments/assets/62c455d5-3fbd-4e8c-b2b9-9e5f0987e502) ### Material for MkDocs [Peek 2025-07-07 13-10.webm](https://github.com/user-attachments/assets/ec2cf1f1-47d3-4670-ae42-d8b4efcaa1e4) Closes #616
1 parent 3be1497 commit cc24af1

File tree

3 files changed

+54
-13
lines changed

3 files changed

+54
-13
lines changed

src/ethicalads.js

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export class EthicalAdsAddon extends AddonBase {
4444

4545
createAdPlacement() {
4646
let placement;
47+
let selectors;
48+
49+
const placementIdSuffix = docTool.getDocumentationTool() || "nodoctool";
4750

4851
// TODO: fix this on testing. It works fine on production/regular browser.
4952
// TypeError: Failed to execute 'indexed value' on 'ObservableArray<CSSStyleSheet>': Failed to convert value to 'CSSStyleSheet'.
@@ -67,7 +70,6 @@ export class EthicalAdsAddon extends AddonBase {
6770
} else {
6871
// Inject our own floating element
6972
placement = document.createElement("div");
70-
placement.setAttribute("id", "readthedocs-ea");
7173
placement.classList.add("raised");
7274

7375
// Define where to inject the Ad based on the theme and if it's above the fold or not.
@@ -84,6 +86,12 @@ export class EthicalAdsAddon extends AddonBase {
8486
placement.classList.add("ethical-rtd");
8587
placement.classList.add("ethical-dark-theme");
8688
knownPlacementFound = true;
89+
} else {
90+
// We know it's RTD theme and the ad in the navbar is not above the fold at this point.
91+
// Then, we render the ad as fixed footer.
92+
selectors = ["section", "nav"];
93+
this.setFixedFooterAdProperties(selectors, placement);
94+
knownPlacementFound = true;
8795
}
8896
} else if (docTool.isSphinxFuroLikeTheme()) {
8997
// NOTE: The code to handle furo theme shouldn't be required,
@@ -97,7 +105,6 @@ export class EthicalAdsAddon extends AddonBase {
97105
if (this.elementAboveTheFold(element)) {
98106
placement.classList.add("ethical-alabaster");
99107
placement.setAttribute("data-ea-type", "readthedocs-sidebar");
100-
placement.setAttribute("id", "furo-sidebar-ad-placement");
101108
knownPlacementFound = true;
102109
}
103110
} else if (docTool.isSphinxBookThemeLikeTheme()) {
@@ -201,11 +208,6 @@ export class EthicalAdsAddon extends AddonBase {
201208
} else {
202209
// Default to a text ad appended to the root selector when no known placement found
203210
placement.setAttribute("data-ea-type", "text");
204-
// TODO: Check this placement on the dashboard,
205-
// and see how this is performing.
206-
const docToolName = docTool.getDocumentationTool();
207-
const idSuffix = docToolName ? `-${docToolName}` : "";
208-
placement.setAttribute("id", `readthedocs-ea-text-footer${idSuffix}`);
209211

210212
const rootSelector = docTool.getRootSelector();
211213
const rootElement = document.querySelector(rootSelector);
@@ -240,6 +242,43 @@ export class EthicalAdsAddon extends AddonBase {
240242
campaign_types.join("|"),
241243
);
242244
}
245+
246+
const placementStyle =
247+
placement.getAttribute("data-ea-style") || "nostyle";
248+
const placementType = placement.getAttribute("data-ea-type") || "notype";
249+
const placementIdPrefix = `${placementType}-${placementStyle}`;
250+
if (!placement.getAttribute("id")) {
251+
// Set a standardized id attribute
252+
placement.setAttribute(
253+
"id",
254+
`readthedocs-ea-${placementIdPrefix}-${placementIdSuffix}`,
255+
);
256+
}
257+
258+
if (placementStyle == "fixedfooter") {
259+
// Use a ``MutationObserver`` to listen to style changes in the fixed footer ad.
260+
// Grab the height of it and use to add some ``padding-bottom`` to the required elements.
261+
const config = { attributes: true, childList: false, subtree: false };
262+
const callback = (mutationList, observer) => {
263+
for (const mutation of mutationList) {
264+
if (mutation.type === "attributes") {
265+
const fixedFooterAdHeight = window.getComputedStyle(
266+
mutation.target,
267+
).height;
268+
console.debug("fixedFooterAdHeight", fixedFooterAdHeight);
269+
for (const selector of selectors) {
270+
const element = document.querySelector(selector);
271+
element.style.setProperty(
272+
"padding-bottom",
273+
fixedFooterAdHeight,
274+
);
275+
}
276+
}
277+
}
278+
};
279+
const observer = new MutationObserver(callback);
280+
observer.observe(placement, config);
281+
}
243282
}
244283

245284
return placement;
@@ -271,6 +310,11 @@ export class EthicalAdsAddon extends AddonBase {
271310
return true;
272311
}
273312

313+
setFixedFooterAdProperties(selectors, placement) {
314+
placement.setAttribute("data-ea-type", "text");
315+
placement.setAttribute("data-ea-style", "fixedfooter");
316+
}
317+
274318
addEaPlacementToElement(element) {
275319
// Add `ea-placement-bottom` to the element only if the flyout is enabled.
276320
const flyoutEnabled = objectPath.get(

tests/__snapshots__/ethicalads.test.snap.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,16 @@ snapshots["EthicalAd addon ad placement defined by the user"] = `<div
1313
</div>
1414
`;
1515
/* end snapshot EthicalAd addon ad placement defined by the user */
16-
snapshots["EthicalAd addon ad placement injected"] =
16+
snapshots["EthicalAd addon ad placement injected"] =
1717
`<div
1818
class="raised"
1919
data-ea-campaign-types="community|paid"
2020
data-ea-keywords="docs|data-science"
2121
data-ea-manual="true"
2222
data-ea-publisher="readthedocs"
2323
data-ea-type="text"
24-
id="readthedocs-ea-text-footer"
24+
id="readthedocs-ea-text-nostyle-nodoctool"
2525
>
2626
</div>
2727
`;
2828
/* end snapshot EthicalAd addon ad placement injected */
29-

tests/ethicalads.test.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@
6262

6363
it("ad placement injected", async () => {
6464
const addon = new ethicalads.EthicalAdsAddon(config);
65-
const element = document.querySelector(
66-
"#readthedocs-ea-text-footer",
67-
);
65+
const element = document.querySelector("[id^=readthedocs-ea");
6866

6967
expect(element).to.exist;
7068
expect(element).to.have.attribute("data-ea-type", "text");

0 commit comments

Comments
 (0)