1/* ------------------------------------------------------------------------ */ 2/* Javascripts 3/* ------------------------------------------------------------------------ */ 4 5document.addEventListener('DOMContentLoaded', () => { 6 7/* ------------------------------------------------------------------------ */ 8/* Image Hovers for News and Videos 9/* ------------------------------------------------------------------------ */ 10 11const hoverGroups = [ 12 { selector: '.post-image a, .post-gallery a', overlayClass: 'overlay' }, 13 { selector: '.portfolio-item', overlayClass: 'portfolio-overlay', links: ['.overlay-link', '.overlay-lightbox'] }, 14 { selector: '.portfolio-item-one .portfolio-picwrap', overlayClass: 'portfolio-overlay', links: ['.overlay-link', '.overlay-lightbox'] }, 15 { selector: '.blog-item', overlayClass: 'blog-overlay', links: ['.post-icon'] }, 16]; 17 18// PRE-CREATE overlays for all elements on page load 19hoverGroups.forEach(group => { 20 document.querySelectorAll(group.selector).forEach(el => { 21 let overlay = el.querySelector(`.${group.overlayClass}`); 22 if (!overlay) { 23 overlay = document.createElement('div'); 24 overlay.className = group.overlayClass; 25 overlay.style.opacity = 0; 26 overlay.style.transition = 'opacity 0.3s'; 27 el.appendChild(overlay); 28 } 29 30 if (group.links) { 31 group.links.forEach(linkSelector => { 32 const linkEl = el.querySelector(linkSelector); 33 if (linkEl) { 34 linkEl.style.opacity = 0; 35 linkEl.style.transition = 'opacity 0.16s'; 36 } 37 }); 38 } 39 40 // Hover events now only toggle opacity 41 el.addEventListener('mouseenter', () => { 42 overlay.style.opacity = 1; 43 if (group.links) { 44 group.links.forEach(linkSelector => { 45 const linkEl = el.querySelector(linkSelector); 46 if (linkEl) linkEl.style.opacity = 1; 47 }); 48 } 49 }); 50 51 el.addEventListener('mouseleave', () => { 52 overlay.style.opacity = 0; 53 if (group.links) { 54 group.links.forEach(linkSelector => { 55 const linkEl = el.querySelector(linkSelector); 56 if (linkEl) linkEl.style.opacity = 0; 57 }); 58 } 59 }); 60 }); 61}); 62 63 /* ------------------------------------------------------------------------ */ 64 /* Exit site button 65 /* ------------------------------------------------------------------------ */ 66 67 const exitBtn = document.getElementById('exitButton'); 68 if (exitBtn) { 69 exitBtn.addEventListener('click', () => { 70 const newTab = window.open('https://www.bbc.co.uk/scotland', '_blank', 'noopener,noreferrer'); 71 if (newTab) newTab.opener = null; 72 window.location.replace('https://google.com'); 73 }); 74 } 75 76 /* ------------------------------------------------------------------------ */ 77 /* Tooltips 78 /* ------------------------------------------------------------------------ */ 79 80 class Tooltip { 81 constructor(element, options = {}) { 82 this.element = element; 83 this.options = Object.assign({ 84 placement: 'top', 85 trigger: 'hover', 86 animation: true, 87 html: true, 88 delay: 0, 89 template: `<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>` 90 }, options); 91 92 this.enabled = true; 93 this._hoverState = null; 94 this._timeout = null; 95 this.tooltip = null; 96 97 this._setTitle(); 98 this._initEvents(); 99 } 100 101 _setTitle() { 102 if (!this.element.dataset.originalTitle) { 103 this.element.dataset.originalTitle = this.element.getAttribute('title') || ''; 104 this.element.removeAttribute('title'); 105 } 106 } 107 108 _initEvents() { 109 const { trigger } = this.options; 110 111 if (trigger === 'hover') { 112 this.element.addEventListener('mouseenter', () => this._enter()); 113 this.element.addEventListener('mouseleave', () => this._leave()); 114 } else if (trigger === 'focus') { 115 this.element.addEventListener('focus', () => this._enter()); 116 this.element.addEventListener('blur', () => this._leave()); 117 } else if (trigger === 'click') { 118 this.element.addEventListener('click', () => this.toggle()); 119 } 120 } 121 122 _enter() { 123 clearTimeout(this._timeout); 124 if (this.options.delay && this.options.delay.show) { 125 this._hoverState = 'in'; 126 this._timeout = setTimeout(() => { 127 if (this._hoverState === 'in') this.show(); 128 }, this.options.delay.show); 129 } else { 130 this.show(); 131 } 132 } 133 134 _leave() { 135 clearTimeout(this._timeout); 136 if (this.options.delay && this.options.delay.hide) { 137 this._hoverState = 'out'; 138 this._timeout = setTimeout(() => { 139 if (this._hoverState === 'out') this.hide(); 140 }, this.options.delay.hide); 141 } else { 142 this.hide(); 143 } 144 } 145 146 show() { 147 if (!this.enabled || !this.element.dataset.originalTitle) return; 148 149 if (!this.tooltip) { 150 const wrapper = document.createElement('div'); 151 wrapper.innerHTML = this.options.template; 152 this.tooltip = wrapper.firstElementChild; 153 this.tooltip.querySelector('.tooltip-inner')[this.options.html ? 'innerHTML' : 'innerText'] = 154 this.element.dataset.originalTitle; 155 document.body.appendChild(this.tooltip); 156 } 157 158 if (this.options.animation) this.tooltip.classList.add('fade'); 159 160 const rect = this.element.getBoundingClientRect(); 161 const tipRect = this.tooltip.getBoundingClientRect(); 162 let top, left; 163 164 switch (this.options.placement) { 165 case 'top': 166 top = rect.top - tipRect.height; 167 left = rect.left + rect.width / 2 - tipRect.width / 2; 168 break; 169 case 'bottom': 170 top = rect.bottom; 171 left = rect.left + rect.width / 2 - tipRect.width / 2; 172 break; 173 case 'left': 174 top = rect.top + rect.height / 2 - tipRect.height / 2; 175 left = rect.left - tipRect.width; 176 break; 177 case 'right': 178 top = rect.top + rect.height / 2 - tipRect.height / 2; 179 left = rect.right; 180 break; 181 } 182 183 this.tooltip.style.top = `${top + window.scrollY}px`;
184 this.tooltip.style.left = `${left + window.scrollX}px`; 185 this.tooltip.classList.add(this.options.placement, 'in'); 186 } 187 188 hide() { 189 if (this.tooltip) { 190 this.tooltip.classList.remove('in'); 191 setTimeout(() => { 192 if (this.tooltip) this.tooltip.remove(); 193 this.tooltip = null; 194 }, 200); 195 } 196 } 197 198 toggle() { 199 if (this.tooltip) this.hide(); 200 else this.show(); 201 } 202 203 enable() { this.enabled = true; } 204 disable() { this.enabled = false; } 205 } 206 207 const initTooltips = (selector, options = {}) => { 208 document.querySelectorAll(selector).forEach(el => new Tooltip(el, options)); 209 }; 210 211 // Initialize tooltips 212 initTooltips('.meta-category a, .meta-author a', { placement: 'top' }); 213 initTooltips('.tooltips a[rel="tooltip"]'); 214 initTooltips('#topbar .social-icons a', { placement: 'bottom', animation: true }); 215 initTooltips('.member-social a', { placement: 'top' }); 216 initTooltips('#copyright .social-icons a', { placement: 'top' }); 217 initTooltips('.social-icon a', { placement: 'top' }); 218 initTooltips('.videos-widget-item a', { placement: 'bottom' }); 219 220 /* ------------------------------------------------------------------------ */ 221 /* Desktop Navigation (Superfish) 222 /* ------------------------------------------------------------------------ */ 223 224 (function ($) { 225 "use strict"; 226 227 var methods = (function () { 228 229 var c = { 230 bcClass: 'sf-breadcrumb', 231 menuClass: 'sf-js-enabled', 232 anchorClass: 'sf-with-ul', 233 menuArrowClass: 'sf-arrows' 234 }, 235 ios = (function () { 236 var ios = /iPhone|iPad|iPod/i.test(navigator.userAgent); 237 if (ios) { 238 $(window).on('load', function () { 239 $('body').children().on('click', $.noop); 240 }); 241 } 242 return ios; 243 })(), 244 wp7 = (function () { 245 var style = document.documentElement.style; 246 return ('behavior' in style && 'fill' in style && /iemobile/i.test(navigator.userAgent)); 247 })(), 248 toggleMenuClasses = function ($menu, o) { 249 var classes = c.menuClass; 250 if (o.cssArrows) { 251 classes += ' ' + c.menuArrowClass; 252 } 253 $menu.toggleClass(classes); 254 }, 255 setPathToCurrent = function ($menu, o) { 256 return $menu.find('li.' + o.pathClass).slice(0, o.pathLevels) 257 .addClass(o.hoverClass + ' ' + c.bcClass) 258 .filter(function () { 259 return ($(this).children(o.popUpSelector).hide().show().length); 260 }).removeClass(o.pathClass); 261 }, 262 toggleAnchorClass = function ($li) { 263 $li.children('a').toggleClass(c.anchorClass); 264 }, 265 toggleTouchAction = function ($menu) { 266 var touchAction = $menu.css('ms-touch-action'); 267 touchAction = (touchAction === 'pan-y') ? 'auto' : 'pan-y'; 268 $menu.css('ms-touch-action', touchAction); 269 }, 270 applyHandlers = function ($menu, o) { 271 var targets = 'li:has(' + o.popUpSelector + ')'; 272 if ($.fn.hoverIntent && !o.disableHI) { 273 $menu.hoverIntent(over, out, targets); 274 } 275 else { 276 $menu 277 .on('mouseenter.superfish', targets, over) 278 .on('mouseleave.superfish', targets, out); 279 } 280 var touchevent = 'MSPointerDown.superfish'; 281 if (!ios) { 282 touchevent += ' touchend.superfish'; 283 } 284 if (wp7) { 285 touchevent += ' mousedown.superfish'; 286 } 287 $menu 288 .on('focusin.superfish', 'li', over) 289 .on('focusout.superfish', 'li', out) 290 .on(touchevent, 'a', o, touchHandler); 291 }, 292 touchHandler = function (e) { 293 var $this = $(this), 294 $ul = $this.siblings(e.data.popUpSelector); 295 296 if ($ul.length > 0 && $ul.is(':hidden')) { 297 $this.one('click.superfish', false); 298 if (e.type === 'MSPointerDown') { 299 $this.trigger('focus'); 300 } else { 301 $.proxy(over, $this.parent('li'))(); 302 } 303 } 304 }, 305 over = function () { 306 var $this = $(this), 307 o = getOptions($this); 308 clearTimeout(o.sfTimer); 309 $this.siblings().superfish('hide').end().superfish('show'); 310 }, 311 out = function () { 312 var $this = $(this), 313 o = getOptions($this); 314 if (ios) { 315 $.proxy(close, $this, o)(); 316 } 317 else { 318 clearTimeout(o.sfTimer); 319 o.sfTimer = setTimeout($.proxy(close, $this, o), o.delay); 320 } 321 }, 322 close = function (o) { 323 o.retainPath = ($.inArray(this[0], o.$path) > -1); 324 this.superfish('hide'); 325 326 if (!this.parents('.' + o.hoverClass).length) { 327 o.onIdle.call(getMenu(this)); 328 if (o.$path.length) { 329 $.proxy(over, o.$path)(); 330 } 331 } 332 }, 333 getMenu = function ($el) { 334 return $el.closest('.' + c.menuClass); 335 }, 336 getOptions = function ($el) { 337 return getMenu($el).data('sf-options'); 338 }; 339 340 return { 341 342 hide: function (instant) { 343 if (this.length) { 344 var $this = this, 345 o = getOptions($this); 346 if (!o) { 347 return this; 348 } 349 var not = (o.retainPath === true) ? o.$path : '', 350 $ul = $this.find('li.' + o.hoverClass).add(this).not(not).removeClass(o.hoverClass).children(o.popUpSelector), 351 speed = o.speedOut; 352 353 if (instant) { 354 $ul.show(); 355 speed = 0; 356 } 357 o.retainPath = false;
358 o.onBeforeHide.call($ul); 359 $ul.stop(true, true).animate(o.animationOut, speed, function () { 360 var $this = $(this); 361 o.onHide.call($this); 362 }); 363 } 364 return this; 365 }, 366 show: function () { 367 var o = getOptions(this); 368 if (!o) { 369 return this; 370 } 371 var $this = this.addClass(o.hoverClass), 372 $ul = $this.children(o.popUpSelector); 373 374 o.onBeforeShow.call($ul); 375 $ul.stop(true, true).animate(o.animation, o.speed, function () { 376 o.onShow.call($ul); 377 }); 378 return this; 379 }, 380 destroy: function () { 381 return this.each(function () { 382 var $this = $(this), 383 o = $this.data('sf-options'), 384 $hasPopUp; 385 if (!o) { 386 return false; 387 } 388 $hasPopUp = $this.find(o.popUpSelector).parent('li'); 389 clearTimeout(o.sfTimer); 390 toggleMenuClasses($this, o); 391 toggleAnchorClass($hasPopUp); 392 toggleTouchAction($this); 393 394 $this.off('.superfish').off('.hoverIntent'); 395 396 $hasPopUp.children(o.popUpSelector).attr('style', function (i, style) { 397 return style.replace(/display[^;]+;?/g, ''); 398 }); 399 400 o.$path.removeClass(o.hoverClass + ' ' + c.bcClass).addClass(o.pathClass); 401 $this.find('.' + o.hoverClass).removeClass(o.hoverClass); 402 o.onDestroy.call($this); 403 $this.removeData('sf-options'); 404 }); 405 }, 406 init: function (op) { 407 return this.each(function () { 408 var $this = $(this); 409 if ($this.data('sf-options')) { 410 return false; 411 } 412 var o = $.extend({}, $.fn.superfish.defaults, op), 413 $hasPopUp = $this.find(o.popUpSelector).parent('li'); 414 o.$path = setPathToCurrent($this, o); 415 416 $this.data('sf-options', o); 417 418 toggleMenuClasses($this, o); 419 toggleAnchorClass($hasPopUp); 420 toggleTouchAction($this); 421 applyHandlers($this, o); 422 423 $hasPopUp.not('.' + c.bcClass).superfish('hide', true); 424 425 o.onInit.call(this); 426 }); 427 } 428 }; 429 })(); 430 431 $.fn.superfish = function (method, args) { 432 if (methods[method]) { 433 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 434 } 435 else if (typeof method === 'object' || ! method) { 436 return methods.init.apply(this, arguments); 437 } 438 else { 439 return $.error('Method ' + method + ' does not exist on jQuery.fn.superfish'); 440 } 441 }; 442 443 $.fn.superfish.defaults = { 444 popUpSelector: 'ul,.sf-mega', 445 hoverClass: 'sfHover', 446 pathClass: 'overrideThisToUse', 447 pathLevels: 1, 448 delay: 600, 449 animation: {opacity: 'show'}, 450 animationOut: {opacity: 'hide'}, 451 speed: 'normal', 452 speedOut: 'fast', 453 cssArrows: true, 454 disableHI: false, 455 onInit: $.noop, 456 onBeforeShow: $.noop, 457 onShow: $.noop, 458 onBeforeHide: $.noop, 459 onHide: $.noop, 460 onIdle: $.noop, 461 onDestroy: $.noop 462 }; 463 464 $.fn.extend({ 465 hideSuperfishUl: methods.hide, 466 showSuperfishUl: methods.show 467 }); 468 469 })(jQuery); 470 471 // Initialise Superfish only if the menu exists on this page 472 if (window.jQuery && jQuery.fn && jQuery.fn.superfish) { 473 const $menu = jQuery("#navigation ul.menu"); 474 if ($menu.length) { 475 $menu.superfish({ 476 delay: 300, 477 hoverClass: 'sfHover', 478 animation: {height:'show', height:'show'}, 479 animationOut: {opacity: 'hide'}, 480 speed: 300, 481 speedOut: 300, 482 cssArrows: true 483 }); 484 } 485 } 486 487}); 488 489/* ------------------------------------------------------------------------ */ 490/* EOF 491/* ------------------------------------------------------------------------ */
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.