1/** 2 * Article Statistics Display Plugin - Main JavaScript 3 * 4 * Copyright (c) 2026 Kerim Sarıgül 5 * Distributed under the GNU GPL v3. 6 * 7 * Positions stats in three locations: 8 * 1. Under title (horizontal) 9 * 2. Under abstract (horizontal) 10 * 3. Article sidebar (vertical - each stat on its own line) 11 * 12 * Also handles summary view: moves stats next to galley buttons 13 */ 14 15(function() { 16 'use strict'; 17 18 document.addEventListener('DOMContentLoaded', function() { 19 var settings = window.asdSettings || { position: 'underTitle', style: 'badge' }; 20 21 // Handle article detail page 22 positionStats(settings.position); 23 24 // Handle summary/list views - move stats next to galley buttons 25 moveSummaryStatsToGalleys(); 26 }); 27 28 /** 29 * Move summary stats next to galley buttons in list views 30 * Handles different theme structures intelligently 31 */ 32 function moveSummaryStatsToGalleys() { 33 var summaryStats = document.querySelectorAll('.asd-stats-summary'); 34 35 summaryStats.forEach(function(statsEl) { 36 // Find the parent article container - expanded selectors for more themes 37 var articleContainer = statsEl.closest('.obj_article_summary, .article_summary, .article-summary, article, .media, .card, .article-item'); 38 if (!articleContainer) { 39 articleContainer = statsEl.parentElement; 40 } 41 42 if (!articleContainer) { 43 statsEl.classList.add('asd-moved'); 44 return; 45 } 46 47 // Strategy 1: Find galleys_links ul (Default/Manuscript theme) 48 var galleysUl = articleContainer.querySelector('ul.galleys_links, .galleys_links'); 49 if (galleysUl && galleysUl.tagName === 'UL') { 50 var li = document.createElement('li'); 51 li.style.display = 'inline'; 52 li.appendChild(statsEl); 53 galleysUl.appendChild(li); 54 statsEl.classList.add('asd-moved'); 55 return; 56 } 57 58 // Strategy 2: Find btn-group (Bootstrap theme) 59 var btnGroup = articleContainer.querySelector('.btn-group'); 60 if (btnGroup) { 61 // Insert after btn-group, not inside 62 if (btnGroup.nextSibling) { 63 btnGroup.parentNode.insertBefore(statsEl, btnGroup.nextSibling); 64 } else { 65 btnGroup.parentNode.appendChild(statsEl); 66 } 67 statsEl.style.marginLeft = '8px'; 68 statsEl.classList.add('asd-moved'); 69 return; 70 } 71 72 // Strategy 3: Find article-summary-galleys (Health Science theme) 73 var healthScienceGalleys = articleContainer.querySelector('.article-summary-galleys'); 74 if (healthScienceGalleys) { 75 healthScienceGalleys.appendChild(statsEl); 76 statsEl.style.marginLeft = '8px'; 77 statsEl.style.display = 'inline-block'; 78 statsEl.classList.add('asd-moved'); 79 return; 80 } 81 82 // Strategy 4: Find other galley containers 83 var galleyContainers = articleContainer.querySelectorAll('.galleys, .galley-links, .galley_links, .entry_details .galleys, .download-links'); 84 if (galleyContainers.length > 0) { 85 var container = galleyContainers[0]; 86 container.appendChild(statsEl); 87 statsEl.style.marginLeft = '8px'; 88 statsEl.classList.add('asd-moved'); 89 return; 90 } 91 92 // Strategy 5: Find last galley link and insert after 93 var galleyLinks = articleContainer.querySelectorAll('.obj_galley_link, a.galley-link, a.galley_link, .btn.galley-link, a[href*="/article/view/"][href*="/galley/"], a[href*="view"][class*="galley"]'); 94 if (galleyLinks.length > 0) { 95 var lastGalley = galleyLinks[galleyLinks.length - 1]; 96 var parent = lastGalley.parentElement; 97 98 // If parent is li, go up one more level 99 if (parent.tagName === 'LI') { 100 parent = parent.parentElement; 101 } 102 103 // Insert after the last galley or its container 104 if (lastGalley.nextSibling) { 105 lastGalley.parentNode.insertBefore(statsEl, lastGalley.nextSibling); 106 } else { 107 lastGalley.parentNode.appendChild(statsEl); 108 } 109 statsEl.style.marginLeft = '8px'; 110 statsEl.classList.add('asd-moved'); 111 return; 112 } 113 114 // Fallback: just show in place 115 statsEl.classList.add('asd-moved'); 116 }); 117 } 118 119 /** 120 * Position stats based on selected option (for detail page) 121 */ 122 function positionStats(position) { 123 var statsContainer = document.querySelector('.asd-stats-detail'); 124 if (!statsContainer) return; 125 126 var articlePage = document.querySelector('.obj_article_details, .article-details, .article_details'); 127 if (!articlePage) return; 128 129 var targetElement = null; 130 131 switch (position) { 132 case 'underTitle': 133 targetElement = findTitleElement(articlePage); 134 if (targetElement) { 135 targetElement.parentNode.insertBefore(statsContainer, targetElement.nextSibling); 136 statsContainer.style.marginTop = '15px'; 137 statsContainer.style.marginBottom = '20px';
138 statsContainer.classList.remove('asd-vertical'); 139 } 140 break; 141 142 case 'underAbstract': 143 targetElement = findAbstractElement(articlePage); 144 if (targetElement) { 145 targetElement.parentNode.insertBefore(statsContainer, targetElement.nextSibling); 146 statsContainer.style.marginTop = '20px'; 147 statsContainer.classList.remove('asd-vertical'); 148 } 149 break; 150 151 case 'sidebar': 152 var sidebar = findSidebarElement(articlePage); 153 if (sidebar) { 154 // Add vertical class for sidebar display 155 statsContainer.classList.add('asd-vertical'); 156 157 // Create a wrapper for sidebar 158 var wrapper = document.createElement('div'); 159 wrapper.className = 'item asd-sidebar-item'; 160 wrapper.appendChild(statsContainer); 161 162 // Try to find galley element to insert after 163 var galleyEl = findGalleyInSidebar(sidebar); 164 if (galleyEl) { 165 // Insert after galley 166 if (galleyEl.nextSibling) { 167 galleyEl.parentNode.insertBefore(wrapper, galleyEl.nextSibling); 168 } else { 169 galleyEl.parentNode.appendChild(wrapper); 170 } 171 } else { 172 // Fallback: insert at beginning 173 var firstItem = sidebar.querySelector('.item, .block'); 174 if (firstItem) { 175 sidebar.insertBefore(wrapper, firstItem); 176 } else { 177 sidebar.insertBefore(wrapper, sidebar.firstChild); 178 } 179 } 180 } 181 break; 182 } 183 } 184 185 /** 186 * Find title element 187 */ 188 function findTitleElement(articlePage) { 189 var selectors = [ 190 '.obj_article_details .page_title', 191 '.article-details h1', 192 'h1.page_title', 193 '.page_title' 194 ]; 195 196 for (var i = 0; i < selectors.length; i++) { 197 var el = document.querySelector(selectors[i]); 198 if (el) return el; 199 } 200 201 return articlePage.querySelector('h1, .title'); 202 } 203 204 /** 205 * Find abstract element 206 */ 207 function findAbstractElement(articlePage) { 208 var selectors = [ 209 '.obj_article_details .abstract', 210 '.article-details .abstract', 211 '.item.abstract', 212 'section.abstract', 213 '.abstract' 214 ]; 215 216 for (var i = 0; i < selectors.length; i++) { 217 var el = document.querySelector(selectors[i]); 218 if (el) return el; 219 } 220 221 return null; 222 } 223 224 /** 225 * Find sidebar element and insert after galleys 226 */ 227 function findSidebarElement(articlePage) { 228 var selectors = [ 229 '.article_sidebar', 230 '.article-sidebar', 231 '.sidebar', 232 '.obj_article_details .entry_details', 233 '.entry_details', 234 '.article-details aside', 235 'aside.sidebar', 236 '.col-md-4', // Bootstrap sidebar column 237 '.pkp_structure_sidebar' 238 ]; 239 240 var sidebar = null; 241 for (var i = 0; i < selectors.length; i++) { 242 var el = document.querySelector(selectors[i]); 243 if (el) { 244 sidebar = el; 245 break; 246 } 247 } 248 249 if (!sidebar) { 250 // Fallback: try to find sidebar by structure 251 var mainContent = document.querySelector('.obj_article_details .main_entry, .article-details .main'); 252 if (mainContent && mainContent.nextElementSibling) { 253 sidebar = mainContent.nextElementSibling; 254 } 255 } 256 257 return sidebar; 258 } 259 260 /** 261 * Find galley element in sidebar to insert after 262 */ 263 function findGalleyInSidebar(sidebar) { 264 var galleySelectors = [ 265 '.item.galleys', 266 '.galleys', 267 '.galley-links', 268 '.download' 269 ]; 270 271 for (var i = 0; i < galleySelectors.length; i++) { 272 var el = sidebar.querySelector(galleySelectors[i]); 273 if (el) return el; 274 } 275 276 // Try to find by PDF link 277 var pdfLink = sidebar.querySelector('a[href*="/galley/"], .obj_galley_link'); 278 if (pdfLink) { 279 return pdfLink.closest('.item') || pdfLink.parentElement; 280 } 281 282 return null; 283 } 284 285})();
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.