1/** 2 * Wings Universal Anchor System - Frontend JavaScript 3 */ 4 5(function ($) { 6 'use strict'; 7 8 // Initialize when DOM is ready 9 $(document).ready(function () { 10 WingsAnchors.init(); 11 }); 12 13 var WingsAnchors = { 14 init: function () { 15 this.bindEvents(); 16 this.handleInitialAnchor(); 17 this.setupHoverEffects(); 18 }, 19 20 bindEvents: function () { 21 // Copy link functionality 22 $(document).on('click', '.wings-anchor-copy', function (e) { 23 e.preventDefault(); 24 WingsAnchors.copyAnchorLink($(this)); 25 }); 26 27 // Smooth scroll for anchor links 28 $(document).on('click', 'a[href^="#"]', function (e) { 29 const href = $(this).attr('href'); 30 if (href !== '#' && href.length > 1) { 31 e.preventDefault(); 32 WingsAnchors.smoothScrollTo(href); 33 } 34 }); 35 36 // Handle hash changes 37 $(window).on('hashchange', function () { 38 WingsAnchors.handleHashChange(); 39 }); 40 }, 41 42 setupHoverEffects: function () { 43 // Add hover effects for better UX 44 $(document).on( 45 'mouseenter', 46 'h1, h2, h3, h4, h5, h6, .wings-cta, .wings-faq, .wings-image, .wings-blockquote, .wings-callout, .wings-single-node, .wings-file-download, .wings-nodes-grid, .wings-people-organizations-grid', 47 function () { 48 const $element = $(this); 49 const $anchorButton = $element.find('.wings-anchor-copy'); 50 51 if ($anchorButton.length) { 52 $anchorButton.addClass('hover-active'); 53 } 54 } 55 ); 56 57 $(document).on( 58 'mouseleave', 59 'h1, h2, h3, h4, h5, h6, .wings-cta, .wings-faq, .wings-image, .wings-blockquote, .wings-callout, .wings-single-node, .wings-file-download, .wings-nodes-grid, .wings-people-organizations-grid', 60 function () { 61 const $element = $(this); 62 const $anchorButton = $element.find('.wings-anchor-copy'); 63 64 if ($anchorButton.length && !$anchorButton.hasClass('copied')) { 65 $anchorButton.removeClass('hover-active'); 66 } 67 } 68 ); 69 }, 70 71 copyAnchorLink: function ($button) { 72 const anchor = $button.data('anchor'); 73 const url = window.location.href.split('#')[0] + '#' + anchor; 74 75 // Use modern clipboard API if available 76 if (navigator.clipboard && window.isSecureContext) { 77 navigator.clipboard 78 .writeText(url) 79 .then(function () {
80 WingsAnchors.showCopyFeedback($button); 81 }) 82 .catch(function () { 83 WingsAnchors.fallbackCopy(url, $button); 84 }); 85 } else { 86 WingsAnchors.fallbackCopy(url, $button); 87 } 88 }, 89 90 fallbackCopy: function (text, $button) { 91 // Fallback for older browsers 92 const textArea = document.createElement('textarea'); 93 textArea.value = text; 94 textArea.style.position = 'fixed'; 95 textArea.style.left = '-999999px'; 96 textArea.style.top = '-999999px'; 97 document.body.appendChild(textArea); 98 textArea.focus(); 99 textArea.select(); 100 101 try { 102 document.execCommand('copy'); 103 WingsAnchors.showCopyFeedback($button); 104 } catch (err) { 105 console.error('Failed to copy: ', err); 106 } 107 108 document.body.removeChild(textArea); 109 }, 110 111 showCopyFeedback: function ($button) { 112 const originalText = $button.text(); 113 const originalClass = $button.attr('class'); 114 115 $button.text('â').addClass('copied'); 116 117 setTimeout(function () { 118 $button.text(originalText).removeClass('copied'); 119 }, 2000); 120 }, 121 122 smoothScrollTo: function (target) { 123 const $target = $(target); 124 if ($target.length) { 125 const offset = $target.offset().top - 80; // Account for fixed header 126 127 $('html, body').animate( 128 { 129 scrollTop: offset, 130 }, 131 800, 132 'easeInOutQuart' 133 ); 134 135 // Update URL without triggering hashchange 136 if (history.pushState) { 137 history.pushState(null, null, target); 138 } 139 } 140 }, 141 142 handleInitialAnchor: function () { 143 if (window.location.hash) { 144 // Small delay to ensure DOM is fully loaded 145 setTimeout(function () { 146 WingsAnchors.smoothScrollTo(window.location.hash); 147 }, 100); 148 } 149 }, 150 151 handleHashChange: function () { 152 WingsAnchors.smoothScrollTo(window.location.hash); 153 }, 154 155 // Generate table of contents 156 generateTOC: function ($container) { 157 const $headings = $container.find('h1, h2, h3, h4, h5, h6'); 158 const toc = []; 159 160 $headings.each(function () { 161 const $heading = $(this); 162 const level = parseInt($heading.prop('tagName').substr(1)); 163 const text = $heading.text(); 164 const id = $heading.attr('id'); 165 166 if (id) { 167 toc.push({ 168 level: level, 169 text: text, 170 id: id, 171 }); 172 } 173 }); 174 175 return toc; 176 }, 177 178 renderTOC: function (toc) { 179 if (toc.length === 0) { 180 return '<p>No headings found.</p>'; 181 } 182 183 let html = '<ul class="wings-toc-list">'; 184 let currentLevel = 0; 185 186 toc.forEach(function (item) { 187 // Close previous levels 188 while (currentLevel > item.level) { 189 html += '</ul>'; 190 currentLevel--; 191 } 192 193 // Open new levels 194 while (currentLevel < item.level) { 195 html += '<ul>'; 196 currentLevel++; 197 } 198 199 html += '<li><a href="#' + item.id + '">' + item.text + '</a></li>'; 200 }); 201 202 // Close remaining levels 203 while (currentLevel > 0) { 204 html += '</ul>'; 205 currentLevel--; 206 } 207 208 html += '</ul>'; 209 return html; 210 }, 211 }; 212 213 // Add easing function for smooth scrolling 214 $.easing.easeInOutQuart = function (x, t, b, c, d) { 215 if ((t /= d / 2) < 1) return (c / 2) * t * t * t * t + b; 216 return (-c / 2) * ((t -= 2) * t * t * t - 2) + b; 217 }; 218})(jQuery);
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.