/** * ============================================================ * SONGSHOVE V5 — CAMPAIGN PAGE * ============================================================ * * Dedicated behavior for the new V4 Campaign Workspace. * * EXISTING campaign-progress.js CONTINUES TO OWN: * - Completed progress saving * - Check All / Uncheck All saving * - Favorites saving * - Campaign Notes saving * - Idea Notes saving * - All / To Do / Completed / Favorites filtering * - Platform filtering * - Platform completion counts * - Search * - Original / Easiest / Hardest sorting * - Reset Filters * - Progress percentage * - Campaign summary values * - Milestones * - Campaign Complete state * - Confetti * * THIS FILE ADDS: * - Live Campaign Archive filtering * - Live Campaign Archive search * - Live Campaign Archive sorting * - Live Campaign Archive pagination * - Expand All * - Collapse All * - Jump to Next Incomplete * - Random Incomplete Idea * - Copy Idea * - Print Campaign * - Clickable summary statistics * - Live filter counts * - Live search/result count * - No-results message * - Live Notes indicator * * ============================================================ */ document.addEventListener( 'DOMContentLoaded', function () { const campaignPage = document.querySelector( '.songshove-campaign-page' ); if (!campaignPage) { return; } /** * ==================================================== * LIVE CAMPAIGN ARCHIVE FILTERING * ==================================================== * * All archive cards are rendered by PHP. * * JavaScript handles: * - Status * - Artist * - Platform * - Genre * - Search * - Sort * - Reset * - Pagination * * No page refresh is required. * ==================================================== */ const archive = campaignPage.querySelector( '.songshove-campaign-archive' ); if (archive) { const form = archive.querySelector( '.songshove-campaign-archive-filter-form' ); const grid = archive.querySelector( '.songshove-campaign-card-grid' ); const cards = grid ? Array.from( grid.querySelectorAll( '.songshove-campaign-card' ) ) : []; const statusButtons = Array.from( archive.querySelectorAll( '.songshove-campaign-archive-status-button' ) ); const artistSelect = archive.querySelector( '.songshove-campaign-archive-artist' ); const platformSelect = archive.querySelector( '.songshove-campaign-archive-platform' ); const genreSelect = archive.querySelector( '.songshove-campaign-archive-genre' ); const searchInput = archive.querySelector( '.songshove-campaign-archive-search' ); const sortSelect = archive.querySelector( '.songshove-campaign-archive-sort' ); const resetButton = archive.querySelector( '.songshove-campaign-archive-reset' ); const noResultsReset = archive.querySelector( '.songshove-campaign-archive-no-results-reset' ); const resultCount = archive.querySelector( '.songshove-campaign-archive-result-count' ); const noResults = archive.querySelector( '.songshove-campaign-archive-no-results' ); const pagination = archive.querySelector( '.songshove-campaign-pagination' ); const perPage = Math.max( 1, parseInt( archive.dataset.campaignsPerPage || '12', 10 ) || 12 ); let activeStatus = 'all'; let currentPage = 1; /** * ------------------------------------------------ * Normalize values * ------------------------------------------------ */ function normalizeArchiveValue( value ) { return String( value || '' ) .trim() .toLowerCase(); } /** * ------------------------------------------------ * Read numeric card data * ------------------------------------------------ */ function cardNumber( card, key ) { return parseInt( card.dataset[ key ] || '0', 10 ) || 0; } /** * ------------------------------------------------ * Status filter * ------------------------------------------------ */ function matchesArchiveStatus( card ) { const total = cardNumber( card, 'campaignTotal' ); const completed = cardNumber( card, 'campaignCompleted' ); const favorites = cardNumber( card, 'campaignFavorites' ); if ( activeStatus === 'completed' ) { return ( total > 0 && completed >= total ); } if ( activeStatus === 'in_progress' ) { return ( total > 0 && completed < total ); } if ( activeStatus === 'favorites' ) { return ( favorites > 0 ); } return true; } /** * ------------------------------------------------ * Main filter matching * ------------------------------------------------ */ function matchesArchiveFilters( card ) { if ( !matchesArchiveStatus( card ) ) { return false; } /** * Artist */ const selectedArtist = normalizeArchiveValue( artistSelect ? artistSelect.value : '' ); if ( selectedArtist !== '' && normalizeArchiveValue( card.dataset.campaignArtist ) !== selectedArtist ) { return false; } /** * Platform */ const selectedPlatform = normalizeArchiveValue( platformSelect ? platformSelect.value : '' ); if ( selectedPlatform !== '' ) { const cardPlatforms = normalizeArchiveValue( card.dataset.campaignPlatforms ) .split('|') .filter( Boolean ); if ( !cardPlatforms.includes( selectedPlatform ) ) { return false; } } /** * Genre */ const selectedGenre = normalizeArchiveValue( genreSelect ? genreSelect.value : '' ); if ( selectedGenre !== '' && normalizeArchiveValue( card.dataset.campaignGenre ) !== selectedGenre ) { return false; } /** * Search */ const searchTerm = normalizeArchiveValue( searchInput ? searchInput.value : '' ); if ( searchTerm !== '' ) { const searchableText = [ card.dataset.campaignTitle, card.dataset.campaignArtist, card.dataset.campaignSong, card.dataset.campaignOrder, card.dataset.campaignGenre, card.dataset.campaignPlatforms ] .map( normalizeArchiveValue ) .join( ' ' ); if ( !searchableText.includes( searchTerm ) ) { return false; } } return true; } /** * ------------------------------------------------ * Sort cards * ------------------------------------------------ */ function sortArchiveCards( matchingCards ) { const sortValue = sortSelect ? sortSelect.value : 'newest'; const sorted = matchingCards.slice(); sorted.sort( function ( cardA, cardB ) { const titleA = normalizeArchiveValue( cardA.dataset.campaignTitle ); const titleB = normalizeArchiveValue( cardB.dataset.campaignTitle ); const artistA = normalizeArchiveValue( cardA.dataset.campaignArtist ); const artistB = normalizeArchiveValue( cardB.dataset.campaignArtist ); const favoritesA = cardNumber( cardA, 'campaignFavorites' ); const favoritesB = cardNumber( cardB, 'campaignFavorites' ); const percentageA = cardNumber( cardA, 'campaignPercentage' ); const percentageB = cardNumber( cardB, 'campaignPercentage' ); const createdA = parseInt( cardA.dataset.campaignCreated || '0', 10 ) || 0; const createdB = parseInt( cardB.dataset.campaignCreated || '0', 10 ) || 0; switch ( sortValue ) { case 'oldest': return ( createdA - createdB ); case 'title': return titleA.localeCompare( titleB ); case 'artist': return artistA.localeCompare( artistB ); case 'complete': return ( percentageB - percentageA ); case 'favorites': return ( favoritesB - favoritesA ); case 'newest': default: return ( createdB - createdA ); } } ); return sorted; } /** * ------------------------------------------------ * Pagination * ------------------------------------------------ */ function renderArchivePagination( totalMatches ) { if ( !pagination ) { return; } const totalPages = Math.max( 1, Math.ceil( totalMatches / perPage ) ); if ( currentPage > totalPages ) { currentPage = totalPages; } pagination.innerHTML = ''; if ( totalPages <= 1 ) { pagination.hidden = true; return; } pagination.hidden = false; /** * Previous */ const previousButton = document.createElement( 'button' ); previousButton.type = 'button'; previousButton.className = 'songshove-campaign-pagination-arrow'; previousButton.textContent = '← Previous'; previousButton.disabled = currentPage <= 1; previousButton.addEventListener( 'click', function () { if ( currentPage <= 1 ) { return; } currentPage--; applyArchiveView(); } ); pagination.appendChild( previousButton ); /** * Page buttons */ const pageWrap = document.createElement( 'div' ); pageWrap.className = 'songshove-campaign-pagination-pages'; for ( let pageNumber = 1; pageNumber <= totalPages; pageNumber++ ) { const pageButton = document.createElement( 'button' ); pageButton.type = 'button'; pageButton.className = 'songshove-campaign-pagination-page'; pageButton.textContent = String( pageNumber ); if ( pageNumber === currentPage ) { pageButton.classList.add( 'is-current' ); pageButton.setAttribute( 'aria-current', 'page' ); } pageButton.addEventListener( 'click', function () { currentPage = pageNumber; applyArchiveView(); } ); pageWrap.appendChild( pageButton ); } pagination.appendChild( pageWrap ); /** * Next */ const nextButton = document.createElement( 'button' ); nextButton.type = 'button'; nextButton.className = 'songshove-campaign-pagination-arrow'; nextButton.textContent = 'Next →'; nextButton.disabled = currentPage >= totalPages; nextButton.addEventListener( 'click', function () { if ( currentPage >= totalPages ) { return; } currentPage++; applyArchiveView(); } ); pagination.appendChild( nextButton ); } /** * ------------------------------------------------ * Apply view * ------------------------------------------------ */ function applyArchiveView() { if ( !grid ) { return; } const matchingCards = cards.filter( matchesArchiveFilters ); const sortedCards = sortArchiveCards( matchingCards ); /** * Reorder matching cards. */ sortedCards.forEach( function ( card ) { grid.appendChild( card ); } ); /** * Hide everything. */ cards.forEach( function ( card ) { card.hidden = true; } ); const totalMatches = sortedCards.length; const totalPages = Math.max( 1, Math.ceil( totalMatches / perPage ) ); if ( currentPage > totalPages ) { currentPage = totalPages; } const startIndex = ( currentPage - 1 ) * perPage; const endIndex = startIndex + perPage; /** * Show current page. */ sortedCards .slice( startIndex, endIndex ) .forEach( function ( card ) { card.hidden = false; } ); /** * Result count. */ if ( resultCount ) { resultCount.textContent = 'Showing ' + totalMatches + ' of ' + cards.length + ' Campaigns'; } /** * No results. */ if ( noResults ) { noResults.hidden = totalMatches > 0; } grid.hidden = totalMatches < 1; /** * Pagination. */ renderArchivePagination( totalMatches ); } /** * ------------------------------------------------ * Reset * ------------------------------------------------ */ function resetArchiveFilters() { activeStatus = 'all'; currentPage = 1; statusButtons.forEach( function ( button ) { const isActive = normalizeArchiveValue( button.dataset.campaignStatus ) === 'all'; button.classList.toggle( 'is-active', isActive ); button.setAttribute( 'aria-pressed', isActive ? 'true' : 'false' ); } ); if ( artistSelect ) { artistSelect.value = ''; } if ( platformSelect ) { platformSelect.value = ''; } if ( genreSelect ) { genreSelect.value = ''; } if ( searchInput ) { searchInput.value = ''; } if ( sortSelect ) { sortSelect.value = 'newest'; } applyArchiveView(); } /** * ------------------------------------------------ * Prevent normal form submission * ------------------------------------------------ */ if ( form ) { form.addEventListener( 'submit', function ( event ) { event.preventDefault(); currentPage = 1; applyArchiveView(); } ); } /** * ------------------------------------------------ * Status buttons * ------------------------------------------------ */ statusButtons.forEach( function ( button ) { button.addEventListener( 'click', function ( event ) { event.preventDefault(); activeStatus = normalizeArchiveValue( button.dataset.campaignStatus ) || 'all'; currentPage = 1; statusButtons.forEach( function ( otherButton ) { const isActive = otherButton === button; otherButton.classList.toggle( 'is-active', isActive ); otherButton.setAttribute( 'aria-pressed', isActive ? 'true' : 'false' ); } ); applyArchiveView(); } ); } ); /** * ------------------------------------------------ * Select filters * ------------------------------------------------ */ [ artistSelect, platformSelect, genreSelect ] .filter( Boolean ) .forEach( function ( select ) { select.addEventListener( 'change', function () { currentPage = 1; applyArchiveView(); } ); } ); /** * ------------------------------------------------ * Search * ------------------------------------------------ */ if ( searchInput ) { searchInput.addEventListener( 'input', function () { currentPage = 1; applyArchiveView(); } ); } /** * ------------------------------------------------ * Sort * ------------------------------------------------ */ if ( sortSelect ) { sortSelect.addEventListener( 'change', function () { currentPage = 1; applyArchiveView(); } ); } /** * ------------------------------------------------ * Reset * ------------------------------------------------ */ if ( resetButton ) { resetButton.addEventListener( 'click', function ( event ) { event.preventDefault(); resetArchiveFilters(); } ); } if ( noResultsReset ) { noResultsReset.addEventListener( 'click', function ( event ) { event.preventDefault(); resetArchiveFilters(); } ); } /** * ------------------------------------------------ * Initial archive display * ------------------------------------------------ */ applyArchiveView(); } /** * ==================================================== * SINGLE CAMPAIGN WORKSPACE * ==================================================== */ const workspace = campaignPage.querySelector( '.songshove-v4-workspace' ); /** * Archive page has no workspace. * * Live archive behavior above has already run there. */ if (!workspace) { return; } /** * ==================================================== * ELEMENT HELPERS * ==================================================== */ function getIdeas() { return Array.from( workspace.querySelectorAll( '.songshove-marketing-idea' ) ); } function getIdeaDetails() { return Array.from( workspace.querySelectorAll( '.songshove-v4-idea-details' ) ); } function getCheckbox( idea ) { if (!idea) { return null; } return idea.querySelector( '.songshove-progress-checkbox' ); } function getFavoriteButton( idea ) { if (!idea) { return null; } return idea.querySelector( '.songshove-favorite-idea' ); } function isIdeaCompleted( idea ) { const checkbox = getCheckbox( idea ); return !!( checkbox && checkbox.checked ); } function isIdeaFavorite( idea ) { const favoriteButton = getFavoriteButton( idea ); return !!( favoriteButton && favoriteButton.classList.contains( 'is-favorited' ) ); } function isIdeaVisible( idea ) { return !!( idea && !idea.hidden ); } /** * ==================================================== * EXPAND ALL / COLLAPSE ALL * ==================================================== * * Only opens/closes the MAIN Marketing Idea dropdowns. * * Idea Notes remain independent. * ==================================================== */ const expandAllButton = workspace.querySelector( '.songshove-expand-all' ); const collapseAllButton = workspace.querySelector( '.songshove-collapse-all' ); if (expandAllButton) { expandAllButton.addEventListener( 'click', function () { getIdeaDetails().forEach( function (details) { details.open = true; } ); } ); } if (collapseAllButton) { collapseAllButton.addEventListener( 'click', function () { getIdeaDetails().forEach( function (details) { details.open = false; } ); } ); } /** * ==================================================== * INDIVIDUAL VIEW / CLOSE IDEA BUTTON * ==================================================== */ workspace.addEventListener( 'click', function (event) { const viewButton = event.target.closest( '.songshove-view-idea' ); if (!viewButton) { return; } const idea = viewButton.closest( '.songshove-marketing-idea' ); if (!idea) { return; } const details = idea.querySelector( '.songshove-v4-idea-details' ); if (!details) { return; } event.preventDefault(); details.open = !details.open; } ); /** * ==================================================== * JUMP TO NEXT INCOMPLETE * ==================================================== */ const nextIncompleteButton = workspace.querySelector( '.songshove-jump-next-incomplete' ); function findNextIncompleteIdea() { const ideas = getIdeas(); if (!ideas.length) { return null; } const incompleteIdeas = ideas.filter( function (idea) { return ( !isIdeaCompleted( idea ) && isIdeaVisible( idea ) ); } ); if ( !incompleteIdeas.length ) { return null; } const viewportMiddle = window.scrollY + ( window.innerHeight / 2 ); let nextIdea = incompleteIdeas.find( function (idea) { const rect = idea.getBoundingClientRect(); const absoluteTop = window.scrollY + rect.top; return ( absoluteTop > viewportMiddle ); } ); if (!nextIdea) { nextIdea = incompleteIdeas[0]; } return nextIdea; } function focusCampaignIdea( idea ) { if (!idea) { return; } const details = idea.querySelector( '.songshove-v4-idea-details' ); if (details) { details.open = true; } idea.scrollIntoView( { behavior: 'smooth', block: 'center' } ); idea.classList.add( 'songshove-idea-highlight' ); window.setTimeout( function () { idea.classList.remove( 'songshove-idea-highlight' ); }, 1400 ); } if ( nextIncompleteButton ) { nextIncompleteButton.addEventListener( 'click', function () { const idea = findNextIncompleteIdea(); if (!idea) { return; } focusCampaignIdea( idea ); } ); } /** * ==================================================== * RANDOM INCOMPLETE IDEA * ==================================================== */ const randomIncompleteButton = workspace.querySelector( '.songshove-random-incomplete' ); function getRandomIncompleteIdea() { const incompleteIdeas = getIdeas().filter( function (idea) { return ( !isIdeaCompleted( idea ) && isIdeaVisible( idea ) ); } ); if ( !incompleteIdeas.length ) { return null; } const randomIndex = Math.floor( Math.random() * incompleteIdeas.length ); return incompleteIdeas[ randomIndex ]; } if ( randomIncompleteButton ) { randomIncompleteButton.addEventListener( 'click', function () { const idea = getRandomIncompleteIdea(); if (!idea) { return; } focusCampaignIdea( idea ); } ); } /** * ==================================================== * COPY IDEA * ==================================================== */ function escapeCopyHtml( value ) { return String( value || '' ) .replace( /&/g, '&' ) .replace( //g, '>' ) .replace( /"/g, '"' ) .replace( /'/g, ''' ); } function getCleanText( element ) { if (!element) { return ''; } return String( element.textContent || '' ) .replace( /\s+/g, ' ' ) .trim(); } function getIdeaCopyData( idea ) { const titleElement = idea.querySelector( '.songshove-idea-title' ); const platformElement = idea.querySelector( '.songshove-idea-platform' ); const effortElement = idea.querySelector( '.songshove-idea-effort' ); const contentTypeElement = idea.querySelector( '.songshove-idea-content-type' ); const difficultyElement = idea.querySelector( '.songshove-difficulty-badge' ); const ideaBody = idea.querySelector( '.songshove-idea-body' ); return { title: getCleanText( titleElement ), platform: getCleanText( platformElement ), effort: getCleanText( effortElement ), contentType: getCleanText( contentTypeElement ), difficulty: getCleanText( difficultyElement ), body: getCleanText( ideaBody ) }; } function buildIdeaCopyHtml( data ) { const sections = []; if ( data.title ) { sections.push( '
' + escapeCopyHtml( data.title ) + '
' ); } if ( data.body ) { sections.push( '
' + escapeCopyHtml( data.body ) + '
' ); } const metaParts = []; if ( data.platform ) { metaParts.push( 'Platform - ' + escapeCopyHtml( data.platform ) ); } if ( data.effort ) { metaParts.push( 'Artist Effort - ' + escapeCopyHtml( data.effort ) ); } if ( data.difficulty ) { metaParts.push( 'Difficulty - ' + escapeCopyHtml( data.difficulty ) ); } if ( metaParts.length ) { sections.push( '
' + metaParts.join( '
' ) + '
' ); } if ( data.contentType ) { sections.push( '
' + 'Content Type - ' + escapeCopyHtml( data.contentType ) + '
' ); } return sections.join( '' ); } function buildIdeaCopyText( data ) { const lines = []; if ( data.title ) { lines.push( data.title ); } if ( data.body ) { lines.push( '' ); lines.push( data.body ); } const meta = []; if ( data.platform ) { meta.push( 'Platform - ' + data.platform ); } if ( data.effort ) { meta.push( 'Artist Effort - ' + data.effort ); } if ( data.difficulty ) { meta.push( 'Difficulty - ' + data.difficulty ); } if ( data.contentType ) { meta.push( 'Content Type - ' + data.contentType ); } if ( meta.length ) { lines.push( '' ); meta.forEach( function (line) { lines.push( line ); } ); } return lines.join( '\n' ); } function copyIdeaToClipboard( idea, button ) { const data = getIdeaCopyData( idea ); const plainText = buildIdeaCopyText( data ); const htmlText = buildIdeaCopyHtml( data ); function showCopiedState() { if (!button) { return; } const originalText = button.textContent; button.textContent = 'Copied'; button.classList.add( 'is-copied' ); window.setTimeout( function () { button.textContent = originalText; button.classList.remove( 'is-copied' ); }, 1200 ); } if ( navigator.clipboard && window.ClipboardItem && htmlText ) { const clipboardItem = new ClipboardItem( { 'text/plain': new Blob( [ plainText ], { type: 'text/plain' } ), 'text/html': new Blob( [ htmlText ], { type: 'text/html' } ) } ); navigator.clipboard .write( [ clipboardItem ] ) .then( showCopiedState ) .catch( function () { if ( navigator.clipboard ) { navigator.clipboard .writeText( plainText ) .then( showCopiedState ); } } ); return; } if ( navigator.clipboard ) { navigator.clipboard .writeText( plainText ) .then( showCopiedState ); return; } const textarea = document.createElement( 'textarea' ); textarea.value = plainText; textarea.setAttribute( 'readonly', '' ); textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild( textarea ); textarea.select(); document.execCommand( 'copy' ); document.body.removeChild( textarea ); showCopiedState(); } workspace.addEventListener( 'click', function (event) { const copyButton = event.target.closest( '.songshove-copy-idea' ); if (!copyButton) { return; } const idea = copyButton.closest( '.songshove-marketing-idea' ); if (!idea) { return; } event.preventDefault(); copyIdeaToClipboard( idea, copyButton ); } ); /** * ==================================================== * PRINT CAMPAIGN * ==================================================== */ const printButton = workspace.querySelector( '.songshove-print-campaign' ); if ( printButton ) { printButton.addEventListener( 'click', function () { window.print(); } ); } /** * ==================================================== * CLICKABLE SUMMARY STATISTICS * ==================================================== */ const summaryItems = Array.from( workspace.querySelectorAll( '.songshove-v4-summary-item' ) ); summaryItems.forEach( function ( summaryItem ) { summaryItem.addEventListener( 'click', function () { const filter = summaryItem.dataset .campaignFilter; if (!filter) { return; } const targetFilterButton = workspace.querySelector( '[data-filter="' + filter + '"]' ); if ( targetFilterButton ) { targetFilterButton.click(); } } ); } ); /** * ==================================================== * LIVE FILTER COUNTS * ==================================================== */ function updateFilterCounts() { const ideas = getIdeas(); const total = ideas.length; const completed = ideas.filter( function (idea) { return isIdeaCompleted( idea ); } ).length; const todo = total - completed; const favorites = ideas.filter( function (idea) { return isIdeaFavorite( idea ); } ).length; const countMap = { all: total, todo: todo, completed: completed, favorites: favorites }; Object.keys( countMap ).forEach( function ( filterName ) { const button = workspace.querySelector( '[data-filter="' + filterName + '"]' ); if (!button) { return; } const countElement = button.querySelector( '.songshove-filter-count' ); if ( countElement ) { countElement.textContent = String( countMap[ filterName ] ); } } ); } /** * ==================================================== * LIVE SEARCH / RESULT COUNT * ==================================================== */ function updateVisibleResultCount() { const visibleIdeas = getIdeas().filter( function (idea) { return isIdeaVisible( idea ); } ).length; const totalIdeas = getIdeas().length; const resultCounter = workspace.querySelector( '.songshove-live-result-count' ); if ( resultCounter ) { resultCounter.textContent = 'Showing ' + visibleIdeas + ' of ' + totalIdeas; } } /** * ==================================================== * NO RESULTS MESSAGE * ==================================================== */ function updateNoResultsMessage() { const visibleIdeas = getIdeas().filter( function (idea) { return isIdeaVisible( idea ); } ); let message = workspace.querySelector( '.songshove-no-results-message' ); if ( visibleIdeas.length ) { if ( message ) { message.hidden = true; } return; } if ( !message ) { message = document.createElement( 'div' ); message.className = 'songshove-no-results-message'; message.textContent = 'No marketing ideas match the current filters.'; const ideaList = workspace.querySelector( '.songshove-marketing-ideas-list' ); if ( ideaList && ideaList.parentNode ) { ideaList.parentNode.insertBefore( message, ideaList.nextSibling ); } } message.hidden = false; } https://songshove.com/wp-sitemap-posts-post-1.xmlhttps://songshove.com/wp-sitemap-posts-page-1.xmlhttps://songshove.com/wp-sitemap-posts-product-1.xmlhttps://songshove.com/wp-sitemap-taxonomies-category-1.xmlhttps://songshove.com/wp-sitemap-taxonomies-product_cat-1.xmlhttps://songshove.com/wp-sitemap-users-1.xml