PageSourceSearch

https://najfnr.com/plugins/themes/modern/js/main.js?v=3.5.0.5

js najfnr.com collected 2026-10-02 16:37:04 UTC 6,977 bytes, 220 lines download raw bytes

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.