docbrown/app/javascript/packs/admin/displayAds.jsx
Ridhwana ccacf0bc3a
Refactor the home feed + add user tag filtering on in-feed billboards (#19600)
* feat: fix of there is no article

* feat: set the feed order in a data structure rather than in a view

* feat: update yarn.lock

* feat: push the items to the array

* feat: update feed to only show billboards if we have the correct length of items in the feed

* fix: organizedFeedItems.legth

* feat: rename 'featured' to 'image'

* feat: rename 'featured' to 'image'

* feat: rename 'featured' to 'image'

* feat: add some utilities for the feed that we can use in the FeedTest

* feat/WIP: the setup for the feed tests and a first working test

* test: imageItem

* fix: the podcasts can be passed through as an array of objects in the feedItems so that they can be grouped in one card

* feat: remove podcastEpisode state and the attribute in the object

* fix: check that the items exist before trying to slice them

* feat: setup the userdata and the podcast data in the test

* whoops - commit the podcast episodes

* feat: write soem more tests for the feed and including the podcasts

* feat: add some more tests

* feat: add more billboards tests to chcek the order of stuff

* feat: set the timeframe not empty

* feat: update the logic for organizaed feed by inserting the last on first

* doc: jsdoc for functions

* refactor: break the code up into smaller functions

* refactor: make the code more readable and easier to follow

* refactor: pull function out into a utlity

* feat: add specs to utility

* feat: chcek if pinned post

* test the latest timeframe correctly

* chore: update var name

* move the podcast items out of the object clause

* feat: update text

* feat: add a pack file that duplicates initializeDisplayAdVisibility

* feat: create callbacks that will help us to determine when the feed has been rendered so that we can observe the dsplay ads accordingly

* chore: rename to billboards instead of display ad

* feat: abstract out a function that will work for any tagged resource and also limit the article tags within a scope of the article_id

* feat: update the spec for the previous changes

* feat: write tests for the user_tags

* feat: add user_tags to for_display adn the query

* feat: add user_tags to the endpoint where we query the tags to send it through to for_display

* feat: pass through the suer tags to the async request

* feat: update the user_tags fiter query + tests

* feat: update the admin view to show targeted tags on the new feed option billboards too

* feat: add tests for targeted tags field on admin

* refactor: consolidate all of the options into one

* feat: move comment close to attribute

* feat: use a helper method

* feat: include helper on ads query

* feat: move the query from the frontend to live on the backend

* feat: first pass at some error handling whilst maintaining the order of the items

* test: for feed error

* feat: abstract out some code

* update the feed items for errors

* chore: comment

* feat: update the variable names

* feat: update honeybadger message

* feat: update the name of the variable

* refactor: do not conflate the duty of the untagged_ads

* refactor: rename the variables + add note for clarity

* fix: update var
2023-07-06 16:47:08 +02:00

189 lines
4.6 KiB
JavaScript

import { h, render } from 'preact';
import { Tags } from '../../display-ad/tags';
Document.prototype.ready = new Promise((resolve) => {
if (document.readyState !== 'loading') {
return resolve();
}
document.addEventListener('DOMContentLoaded', () => resolve());
return null;
});
/**
* A callback that sets the hidden 'js-tags-textfield' with the selection string that was chosen via the
* MultiSelectAutocomplete component.
*
* @param {String} selectionString The selected tags represented as a string (e.g. "webdev, git, career")
*/
function saveTags(selectionString) {
document.getElementsByClassName('js-tags-textfield')[0].value =
selectionString;
}
/**
* Shows and Renders a Tags preact component for the Targeted Tag(s) field
*/
function showTagsField() {
const displayAdsTargetedTags = document.getElementById(
'display-ad-targeted-tags',
);
if (displayAdsTargetedTags) {
displayAdsTargetedTags.classList.remove('hidden');
render(
<Tags onInput={saveTags} defaultValue={defaultTagValues()} />,
displayAdsTargetedTags,
);
}
}
/**
* Hides the Targeted Tag(s) field
*/
function hideTagsField() {
const displayAdsTargetedTags = document.getElementById(
'display-ad-targeted-tags',
);
displayAdsTargetedTags?.classList.add('hidden');
}
/**
* Clears the content (i.e. value) of the hidden tags textfield
*/
function clearTagList() {
const hiddenTagsField =
document.getElementsByClassName('js-tags-textfield')[0];
hiddenTagsField.value = ' ';
}
/**
* Returns the value of the hidden text field to eventually pass as
* default values to the MultiSelectAutocomplete component.
*/
function defaultTagValues() {
let defaultValue = '';
const hiddenTagsField =
document.getElementsByClassName('js-tags-textfield')[0];
if (hiddenTagsField) {
defaultValue = hiddenTagsField.value.trim();
}
return defaultValue;
}
function displayUserTargets() {
const userTargetField = document.getElementsByClassName(
'js-audience-segment',
)[0].parentElement;
if (userTargetField) {
userTargetField.classList.remove('hidden');
}
}
function hideUserTargets() {
const userTargetField = document.getElementsByClassName(
'js-audience-segment',
)[0].parentElement;
if (userTargetField) {
userTargetField.classList.add('hidden');
}
}
function clearUserTargetSelection() {
const userTargetSelect = document.getElementsByClassName(
'js-audience-segment',
)[0];
if (userTargetSelect) {
userTargetSelect.value = '';
}
}
/**
* Shows and Renders Exclude Article IDs group
*/
function showExcludeIds() {
const excludeField = document.getElementsByClassName(
'js-exclude-ids-textfield',
)[0].parentElement;
excludeField?.classList.remove('hidden');
}
/**
* Hides the Exclude Article IDs group
*/
function hideExcludeIds() {
const excludeField = document.getElementsByClassName(
'js-exclude-ids-textfield',
)[0].parentElement;
excludeField?.classList.add('hidden');
}
/**
* Clears the content (i.e. value) of the Exclude Article IDs group
*/
function clearExcludeIds() {
const excludeField = document.getElementsByClassName(
'js-exclude-ids-textfield',
)[0];
if (excludeField) {
excludeField.value = '';
}
}
/**
* Shows and sets up the Targeted Tag(s) field if the placement area value is "post_comments".
* Listens for change events on the select placement area dropdown
* and shows and hides the Targeted Tag(s) appropriately.
*/
document.ready.then(() => {
const select = document.getElementsByClassName('js-placement-area')[0];
const articleSpecificPlacement = ['post_comments', 'post_sidebar'];
const targetedTagPlacements = [
'post_comments',
'post_sidebar',
'feed_first',
'feed_second',
'feed_third',
];
if (targetedTagPlacements.includes(select.value)) {
showTagsField();
}
select.addEventListener('change', (event) => {
if (targetedTagPlacements.includes(event.target.value)) {
showTagsField();
} else {
hideTagsField();
clearTagList();
}
});
if (articleSpecificPlacement.includes(select.value)) {
showExcludeIds();
}
select.addEventListener('change', (event) => {
if (articleSpecificPlacement.includes(event.target.value)) {
showExcludeIds();
} else {
hideExcludeIds();
clearExcludeIds();
}
});
const userRadios = document.querySelectorAll('input[name=display_to]');
userRadios.forEach((radio) => {
radio.addEventListener('change', (event) => {
if (event.target.value == 'logged_in') {
displayUserTargets();
} else {
hideUserTargets();
clearUserTargetSelection();
}
});
});
});