1/** 2 * @file plugins/themes/modern/js/main.js 3 * 4 * Copyright (c) 2014-2021 Simon Fraser University 5 * Copyright (c) 2003-2021 John Willinsky 6 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. 7 * 8 * @brief Modern theme JavaScript functionality 9 */ 10 11(function ($) { 12 'use strict'; 13 14 $(document).ready(function () { 15 initSmoothScroll(); 16 initHoverEffects(); 17 initCopyrightYear(); 18 initReviewerInterestsToggle(); 19 initNavigation(); 20 initContextPrivacyToggle(); 21 initUsageStatsChartOptions(); 22 initLatestCover(); 23 }); 24 25 /** 26 * Auto-update copyright year 27 */ 28 function initCopyrightYear() { 29 var yearElement = document.getElementById('copyright-year'); 30 if (yearElement) { 31 yearElement.textContent = new Date().getFullYear(); 32 } 33 } 34 35 /** 36 * Smooth scroll for anchor links 37 */ 38 function initSmoothScroll() { 39 $('a[href^="#"]:not([href="#"])').on('click', function (e) { 40 var target = $(this.hash); 41 if (target.length) { 42 e.preventDefault(); 43 $('html, body').animate({ 44 scrollTop: target.offset().top - 100 45 }, 400); 46 } 47 }); 48 } 49 50 /** 51 * Add hover effects for interactive elements 52 */ 53 function initHoverEffects() { 54 // Only if user hasn't requested reduced motion 55 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 56 return; 57 } 58 59 // Add subtle hover animation to cards 60 $('.obj_article_summary, .obj_issue_summary, .pkp_block').on('mouseenter', function () { 61 $(this).css('transition', 'all 0.2s ease'); 62 }); 63 } 64 65 /** 66 * Reviewer interests toggle 67 */ 68 function initReviewerInterestsToggle() { 69 function reviewerInterestsToggle() { 70 var isChecked = false; 71 $('#reviewerOptinGroup').find('input').each(function () { 72 if ($(this).is(':checked')) { 73 isChecked = true; 74 } 75 }); 76 if (isChecked) { 77 $('#reviewerInterests').addClass('is_visible'); 78 } else { 79 $('#reviewerInterests').removeClass('is_visible'); 80 } 81 } 82 83 // Handle case where registration form is loaded 84 if ($('#reviewerInterests').length) { 85 reviewerInterestsToggle(); 86 $('#reviewerOptinGroup input').on('click', reviewerInterestsToggle); 87 } 88 } 89 90 /** 91 * Main navigation menu toggle (mobile + dropdowns) 92 */ 93 function toggleDropdowns() { 94 let $nav = $('#navigationPrimary, #navigationUser'), 95 $submenus = $('ul', $nav); 96 if (window.innerWidth > 992) { 97 $submenus.each(function (i) { 98 let id = 'pkpDropdown' + i; 99 $(this).addClass('dropdown-menu').attr('aria-labelledby', id); 100 $(this) 101 .siblings('a') 102 .attr('data-toggle', 'dropdown') 103 .attr('aria-haspopup', true) 104 .attr('aria-expanded', false) 105 .attr('id', id) 106 .attr('href', '#'); 107 }); 108 $('[data-toggle="dropdown"]').dropdown(); 109 } else { 110 $('[data-toggle="dropdown"]').dropdown('dispose'); 111 $submenus.each(function (i) { 112 $(this).removeClass('dropdown-menu').removeAttr('aria-labelledby'); 113 $(this) 114 .siblings('a') 115 .removeAttr('data-toggle') 116 .removeAttr('aria-haspopup') 117 .removeAttr('aria-expanded') 118 .removeAttr('id') 119 .attr('href', '#'); 120 }); 121 } 122 } 123 124 function initNavigation() { 125 // Initialize dropdown navigation menus on large screens 126 if (typeof $.fn.dropdown !== 'undefined') { 127 window.addEventListener('resize', toggleDropdowns); 128 toggleDropdowns(); 129 } 130 131 // Toggle nav menu on small screens 132 $('.pkp_site_nav_toggle').click(function (e) { 133 $('.pkp_site_nav_menu').toggleClass('pkp_site_nav_menu--isOpen'); 134 $('.pkp_site_nav_toggle').toggleClass('pkp_site_nav_toggle--transform'); 135 }); 136 } 137 138 /** 139 * Toggle display of consent checkboxes in site-wide registration 140 */
141 function initContextPrivacyToggle() { 142 var $contextOptinGroup = $('#contextOptinGroup'); 143 if ($contextOptinGroup.length) { 144 var $roles = $contextOptinGroup.find('.roles :checkbox'); 145 $roles.change(function () { 146 var $thisRoles = $(this).closest('.roles'); 147 if ($thisRoles.find(':checked').length) { 148 $thisRoles.siblings('.context_privacy').addClass('context_privacy_visible'); 149 } else { 150 $thisRoles.siblings('.context_privacy').removeClass('context_privacy_visible'); 151 } 152 }); 153 } 154 } 155 156 /** 157 * Modify Chart.js display options used by UsageStats plugin 158 */ 159 function initUsageStatsChartOptions() { 160 document.addEventListener('usageStatsChartOptions.pkp', function (e) { 161 // Adopt standard primary color for UsageStats Chart 162 e.chartOptions.elements.line.backgroundColor = 'rgba(0, 122, 178, 0.6)'; 163 e.chartOptions.elements.bar.backgroundColor = 'rgba(0, 122, 178, 0.6)'; 164 }); 165 } 166 167 /** 168 * Load latest issue cover from server-side API 169 * 170 * More efficient than client-side file searching: 171 * - Single server request instead of multiple HTTP HEAD requests 172 * - Gets data from database instead of filesystem 173 * - Locale-aware (gets cover in user's locale if available) 174 */ 175 async function initLatestCover() { 176 const imgElement = document.getElementById("latestCover"); 177 if (!imgElement) { 178 return; 179 } 180 181 try { 182 // Call the server-side API endpoint to get latest issue 183 // This endpoint queries the database for the latest published issue 184 // and returns the cover filename 185 const apiUrl = '/plugins/themes/modern/api-latest-issue.php' 186 187 const response = await fetch(apiUrl); 188 189 if (!response.ok) { 190 console.warn('Latest cover API returned non-OK status:', response.status); 191 return; 192 } 193 194 // Guard against non-JSON responses (server errors often return HTML) 195 const contentType = response.headers.get('content-type') || ''; 196 let data = null; 197 if (contentType.indexOf('application/json') !== -1) { 198 try { 199 data = await response.json(); 200 } catch (e) { 201 console.error('Failed to parse JSON from latest cover API:', e); 202 return; 203 } 204 } else { 205 console.error('Latest cover API returned non-JSON response. Status:', response.status); 206 return; 207 } 208 209 if (data && data.success && data.coverFilename) { 210 imgElement.src = data.issuePath + data.coverFilename.en; 211 } else { 212 console.warn('No latest issue cover available:', data ? data.message : 'no data'); 213 } 214 } catch (error) { 215 console.error('Error loading latest issue cover:', error); 216 } 217 } 218 219})(jQuery); 220
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.