diff --git a/app/assets/stylesheets/billboards.scss b/app/assets/stylesheets/billboards.scss index f0fe2b1e2..0a62b2a2a 100644 --- a/app/assets/stylesheets/billboards.scss +++ b/app/assets/stylesheets/billboards.scss @@ -53,6 +53,21 @@ display: block; } +.popover-billboard { + z-index: var(--z-popover); + animation: popoverEnter 0.5s; + box-shadow: 0 0 100px 60px rgba(0, 0, 0, 0.2) !important; + .text-styles { + padding-top: var(--su-4); + padding-bottom: var(--su-4); + font-size: 1.2em; + } + .sponsorship-dropdown { + top: auto; + bottom: var(--su-8); + } +} + .hero-billboard { margin: var(--su-2) 0 0; h1, @@ -99,3 +114,15 @@ padding-right: var(--su-7); padding-left: var(--su-7); } + +// fadeIn +@keyframes popoverEnter { + from { + opacity: 0; + transform: translateY(50px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/app/controllers/api/v1/billboards_controller.rb b/app/controllers/api/v1/billboards_controller.rb index bb7ecdfdb..c4fcdd7cb 100644 --- a/app/controllers/api/v1/billboards_controller.rb +++ b/app/controllers/api/v1/billboards_controller.rb @@ -50,7 +50,7 @@ module Api params.permit :approved, :body_markdown, :creator_id, :display_to, :name, :organization_id, :placement_area, :published, :tag_list, :type_of, :exclude_article_ids, :weight, :requires_cookies, - :audience_segment_type, :audience_segment_id, :priority, + :audience_segment_type, :audience_segment_id, :priority, :special_behavior, :custom_display_label, :template, :render_mode, :preferred_article_ids, # Permitting twice allows both comma-separated string and array values :target_geolocations, target_geolocations: [] diff --git a/app/javascript/packs/admin/billboards.jsx b/app/javascript/packs/admin/billboards.jsx index 85a692098..88421b577 100644 --- a/app/javascript/packs/admin/billboards.jsx +++ b/app/javascript/packs/admin/billboards.jsx @@ -140,8 +140,9 @@ function clearExcludeIds() { */ document.ready.then(() => { const select = document.getElementsByClassName('js-placement-area')[0]; - const articleSpecificPlacement = ['post_comments', 'post_sidebar']; + const articleSpecificPlacement = ['post_comments', 'post_sidebar', 'post_fixed_bottom']; const targetedTagPlacements = [ + 'post_fixed_bottom', 'post_comments', 'post_sidebar', 'sidebar_right', diff --git a/app/javascript/packs/articlePage.jsx b/app/javascript/packs/articlePage.jsx index 2af59f5f9..5fe4dcc01 100644 --- a/app/javascript/packs/articlePage.jsx +++ b/app/javascript/packs/articlePage.jsx @@ -2,7 +2,7 @@ import { h, render } from 'preact'; import { Snackbar, addSnackbarItem } from '../Snackbar'; import { addFullScreenModeControl } from '../utilities/codeFullscreenModeSwitcher'; import { initializeDropdown } from '../utilities/dropdownUtils'; -import { setupBillboardDropdown } from '../utilities/billboardDropdown'; +import { setupBillboardInteractivity } from '../utilities/billboardInteractivity'; import { embedGists } from '../utilities/gist'; import { initializeUserSubscriptionLiquidTagContent } from '../liquidTags/userSubscriptionLiquidTag'; import { trackCommentClicks } from '@utilities/ahoy/trackEvents'; @@ -159,7 +159,7 @@ getCsrfToken().then(async () => { const targetNode = document.querySelector('#comments'); targetNode && embedGists(targetNode); -setupBillboardDropdown(); +setupBillboardInteractivity(); initializeUserSubscriptionLiquidTagContent(); focusOnComments(); // Temporary Ahoy Stats for comment section clicks on controls diff --git a/app/javascript/packs/billboard.js b/app/javascript/packs/billboard.js index 8ee5a0a81..e3cc9282f 100644 --- a/app/javascript/packs/billboard.js +++ b/app/javascript/packs/billboard.js @@ -1,7 +1,8 @@ -import { setupBillboardDropdown } from '../utilities/billboardDropdown'; +import { setupBillboardInteractivity } from '../utilities/billboardInteractivity'; import { observeBillboards, executeBBScripts, + implementSpecialBehavior, } from './billboardAfterRenderActions'; export async function getBillboard() { @@ -29,10 +30,8 @@ async function generateBillboard(element) { try { const response = await window.fetch(asyncUrl); const htmlContent = await response.text(); - const generatedElement = document.createElement('div'); generatedElement.innerHTML = htmlContent; - element.innerHTML = ''; element.appendChild(generatedElement); element.querySelectorAll('img').forEach((img) => { @@ -41,7 +40,8 @@ async function generateBillboard(element) { }; }); executeBBScripts(element); - setupBillboardDropdown(); + implementSpecialBehavior(element); + setupBillboardInteractivity(); // This is called here because the ad is loaded asynchronously. // The original code is still in the asset pipeline, so is not importable. // This could be refactored to be importable as we continue that migration. diff --git a/app/javascript/packs/billboardAfterRenderActions.js b/app/javascript/packs/billboardAfterRenderActions.js index 16068d502..41d9276f7 100644 --- a/app/javascript/packs/billboardAfterRenderActions.js +++ b/app/javascript/packs/billboardAfterRenderActions.js @@ -42,6 +42,22 @@ export function executeBBScripts(el) { } } +export function implementSpecialBehavior(element) { + if (element.querySelector('.js-billboard') && element.querySelector('.js-billboard').dataset.special === 'delayed') { + element.classList.add('hidden'); + const observerOptions = { + root: null, + rootMargin: '-150px', + threshold: 0.2 + }; + + const observer = new IntersectionObserver(showDelayed, observerOptions); + + const target = document.getElementById('comments'); + observer.observe(target); + } +} + export function observeBillboards() { const observer = new IntersectionObserver( (entries) => { @@ -66,7 +82,20 @@ export function observeBillboards() { document.querySelectorAll('[data-display-unit]').forEach((ad) => { observer.observe(ad); ad.removeEventListener('click', trackAdClick, false); - ad.addEventListener('click', () => trackAdClick(ad)); + ad.addEventListener('click', () => trackAdClick(ad, event)); + }); +} + +function showDelayed(entries) { + entries.forEach(entry => { + if (entry.isIntersecting) { + // The target element has come into the viewport + // Place the behavior you want to trigger here + // query for data-special "delayed" + document.querySelectorAll("[data-special='delayed']").forEach((el) => { + el.closest('.hidden').classList.remove('hidden'); + }); + } }); } @@ -107,7 +136,10 @@ function trackAdImpression(adBox) { adBox.dataset.impressionRecorded = true; } -function trackAdClick(adBox) { +function trackAdClick(adBox, event) { + if (!event.target.closest('a')) { + return; + } const isBot = /bot|google|baidu|bing|msn|duckduckbot|teoma|slurp|yandex/i.test( navigator.userAgent, diff --git a/app/javascript/packs/homePage.jsx b/app/javascript/packs/homePage.jsx index 2ec4ddde6..1f96d21ff 100644 --- a/app/javascript/packs/homePage.jsx +++ b/app/javascript/packs/homePage.jsx @@ -6,7 +6,7 @@ import { initializeBillboardVisibility, } from '../packs/billboardAfterRenderActions'; import { observeFeedElements } from '../packs/feedEvents'; -import { setupBillboardDropdown } from '@utilities/billboardDropdown'; +import { setupBillboardInteractivity } from '@utilities/billboardInteractivity'; import { trackCreateAccountClicks } from '@utilities/ahoy/trackEvents'; /* global userData */ @@ -82,7 +82,7 @@ function renderSidebar() { .then((res) => res.text()) .then((response) => { sidebarContainer.innerHTML = response; - setupBillboardDropdown(); + setupBillboardInteractivity(); }); } } @@ -105,7 +105,7 @@ if (!document.getElementById('featured-story-marker')) { const callback = () => { initializeBillboardVisibility(); observeBillboards(); - setupBillboardDropdown(); + setupBillboardInteractivity(); observeFeedElements(); }; @@ -128,7 +128,7 @@ if (!document.getElementById('featured-story-marker')) { const callback = () => { initializeBillboardVisibility(); observeBillboards(); - setupBillboardDropdown(); + setupBillboardInteractivity(); observeFeedElements(); }; diff --git a/app/javascript/utilities/__tests__/billboardInteractivity.test.js b/app/javascript/utilities/__tests__/billboardInteractivity.test.js new file mode 100644 index 000000000..0878f635c --- /dev/null +++ b/app/javascript/utilities/__tests__/billboardInteractivity.test.js @@ -0,0 +1,38 @@ +// Import the function to test and any necessary parts +import { setupBillboardInteractivity } from '@utilities/billboardInteractivity'; + +describe('billboard close functionality', () => { + beforeEach(() => { + // Setup a simple DOM structure that includes only the elements needed for the close functionality + document.body.innerHTML = ` +
+