1/*jslint browser: true */ /*global jQuery: true */ 2 3/** 4 * jQuery Cookie plugin 5 * 6 * Copyright (c) 2010 Klaus Hartl (stilbuero.de) 7 * Dual licensed under the MIT and GPL licenses: 8 * http://www.opensource.org/licenses/mit-license.php 9 * http://www.gnu.org/licenses/gpl.html 10 * 11 */ 12 13// TODO JsDoc 14 15/** 16 * Create a cookie with the given key and value and other optional parameters. 17 * 18 * @example $.cookie('the_cookie', 'the_value'); 19 * @desc Set the value of a cookie. 20 * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); 21 * @desc Create a cookie with all available options. 22 * @example $.cookie('the_cookie', 'the_value'); 23 * @desc Create a session cookie. 24 * @example $.cookie('the_cookie', null); 25 * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain 26 * used when the cookie was set. 27 * 28 * @param String key The key of the cookie. 29 * @param String value The value of the cookie. 30 * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. 31 * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. 32 * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. 33 * If set to null or omitted, the cookie will be a session cookie and will not be retained 34 * when the the browser exits. 35 * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). 36 * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). 37 * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will 38 * require a secure protocol (like HTTPS). 39 * @type undefined 40 * 41 * @name $.cookie 42 * @cat Plugins/Cookie 43 * @author Klaus Hartl/[email protected] 44 */ 45 46/** 47 * Get the value of a cookie with the given key. 48 * 49 * @example $.cookie('the_cookie'); 50 * @desc Get the value of a cookie. 51 * 52 * @param String key The key of the cookie. 53 * @return The value of the cookie. 54 * @type String 55 * 56 * @name $.cookie 57 * @cat Plugins/Cookie 58 * @author Klaus Hartl/[email protected] 59 */ 60jQuery.cookie = function (key, value, options) { 61 62 // key and value given, set cookie... 63 if (arguments.length > 1 && (value === null || typeof value !== "object")) { 64 options = jQuery.extend({}, options); 65 66 if (value === null) { 67 options.expires = -1; 68 } 69 70 if (typeof options.expires === 'number') { 71 var days = options.expires, t = options.expires = new Date(); 72 t.setDate(t.getDate() + days); 73 } 74 75 return (document.cookie = [ 76 encodeURIComponent(key), '=', 77 options.raw ? String(value) : encodeURIComponent(String(value)), 78 options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 79 options.path ? '; path=' + options.path : '', 80 options.domain ? '; domain=' + options.domain : '', 81 options.secure ? '; secure' : '' 82 ].join('')); 83 } 84 85 // key and possibly options given, get cookie... 86 options = value || {}; 87 var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent; 88 return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null; 89}; 90;/*})'"*/;/*})'"*/ 91(function ($) { 92 93/** 94 * Provides Ajax page updating via jQuery $.ajax (Asynchronous JavaScript and XML). 95 * 96 * Ajax is a method of making a request via JavaScript while viewing an HTML 97 * page. The request returns an array of commands encoded in JSON, which is 98 * then executed to make any changes that are necessary to the page. 99 * 100 * Drupal uses this file to enhance form elements with #ajax['path'] and 101 * #ajax['wrapper'] properties. If set, this file will automatically be included 102 * to provide Ajax capabilities. 103 */ 104 105Drupal.ajax = Drupal.ajax || {}; 106 107Drupal.settings.urlIsAjaxTrusted = Drupal.settings.urlIsAjaxTrusted || {}; 108 109/** 110 * Attaches the Ajax behavior to each Ajax form element. 111 */ 112Drupal.behaviors.AJAX = { 113 attach: function (context, settings) { 114 // Load all Ajax behaviors specified in the settings. 115 for (var base in settings.ajax) { 116 if (!$('#' + base + '.ajax-processed').length) { 117 var element_settings = settings.ajax[base]; 118 119 if (typeof element_settings.selector == 'undefined') { 120 element_settings.selector = '#' + base; 121 } 122 $(element_settings.selector).each(function () { 123 element_settings.element = this; 124 Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); 125 }); 126 127 $('#' + base).addClass('ajax-processed'); 128 } 129 } 130 131 // Bind Ajax behaviors to all items showing the class.
132 $('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () { 133 var element_settings = {}; 134 // Clicked links look better with the throbber than the progress bar. 135 element_settings.progress = { 'type': 'throbber' }; 136 137 // For anchor tags, these will go to the target of the anchor rather 138 // than the usual location. 139 if ($(this).attr('href')) { 140 element_settings.url = $(this).attr('href'); 141 element_settings.event = 'click'; 142 } 143 var base = $(this).attr('id'); 144 Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); 145 }); 146 147 // This class means to submit the form to the action using Ajax. 148 $('.use-ajax-submit:not(.ajax-processed)').addClass('ajax-processed').each(function () { 149 var element_settings = {}; 150 151 // Ajax submits specified in this manner automatically submit to the 152 // normal form action. 153 element_settings.url = $(this.form).attr('action'); 154 // Form submit button clicks need to tell the form what was clicked so 155 // it gets passed in the POST request. 156 element_settings.setClick = true; 157 // Form buttons use the 'click' event rather than mousedown. 158 element_settings.event = 'click'; 159 // Clicked form buttons look better with the throbber than the progress bar. 160 element_settings.progress = { 'type': 'throbber' }; 161 162 var base = $(this).attr('id'); 163 Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); 164 }); 165 } 166}; 167 168/** 169 * Ajax object. 170 * 171 * All Ajax objects on a page are accessible through the global Drupal.ajax 172 * object and are keyed by the submit button's ID. You can access them from 173 * your module's JavaScript file to override properties or functions. 174 * 175 * For example, if your Ajax enabled button has the ID 'edit-submit', you can 176 * redefine the function that is called to insert the new content like this 177 * (inside a Drupal.behaviors attach block): 178 * @code 179 * Drupal.behaviors.myCustomAJAXStuff = { 180 * attach: function (context, settings) { 181 * Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) { 182 * new_content = $(response.data); 183 * $('#my-wrapper').append(new_content); 184 * alert('New content was appended to #my-wrapper'); 185 * } 186 * } 187 * }; 188 * @endcode 189 */ 190Drupal.ajax = function (base, element, element_settings) { 191 var defaults = { 192 url: 'system/ajax', 193 event: 'mousedown', 194 keypress: true, 195 selector: '#' + base, 196 effect: 'none', 197 speed: 'none', 198 method: 'replaceWith', 199 progress: { 200 type: 'throbber', 201 message: Drupal.t('Please wait...') 202 }, 203 submit: { 204 'js': true 205 } 206 }; 207 208 $.extend(this, defaults, element_settings); 209 210 this.element = element; 211 this.element_settings = element_settings; 212 213 // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let 214 // the server detect when it needs to degrade gracefully. 215 // There are five scenarios to check for: 216 // 1. /nojs/ 217 // 2. /nojs$ - The end of a URL string. 218 // 3. /nojs? - Followed by a query (with clean URLs enabled). 219 // E.g.: path/nojs?destination=foobar 220 // 4. /nojs& - Followed by a query (without clean URLs enabled). 221 // E.g.: ?q=path/nojs&destination=foobar 222 // 5. /nojs# - Followed by a fragment. 223 // E.g.: path/nojs#myfragment 224 this.url = element_settings.url.replace(/\/nojs(\/|$|\?|&|#)/g, '/ajax$1'); 225 // If the 'nojs' version of the URL is trusted, also trust the 'ajax' version. 226 if (Drupal.settings.urlIsAjaxTrusted[element_settings.url]) { 227 Drupal.settings.urlIsAjaxTrusted[this.url] = true; 228 } 229 230 this.wrapper = '#' + element_settings.wrapper; 231 232 // If there isn't a form, jQuery.ajax() will be used instead, allowing us to 233 // bind Ajax to links as well. 234 if (this.element.form) { 235 this.form = $(this.element.form); 236 } 237 238 // Set the options for the ajaxSubmit function. 239 // The 'this' variable will not persist inside of the options object. 240 var ajax = this; 241 ajax.options = { 242 url: Drupal.sanitizeAjaxUrl(ajax.url), 243 data: ajax.submit, 244 beforeSerialize: function (element_settings, options) { 245 return ajax.beforeSerialize(element_settings, options); 246 }, 247 beforeSubmit: function (form_values, element_settings, options) { 248 ajax.ajaxing = true; 249 return ajax.beforeSubmit(form_values, element_settings, options); 250 }, 251 beforeSend: function (xmlhttprequest, options) { 252 ajax.ajaxing = true; 253 return ajax.beforeSend(xmlhttprequest, options); 254 }, 255 success: function (response, status, xmlhttprequest) { 256 // Sanity check for browser support (object expected). 257 // When using iFrame uploads, responses must be returned as a string. 258 if (typeof response == 'string') { 259 response = $.parseJSON(response); 260 } 261 262 // Prior to invoking the response's commands, verify that they can be 263 // trusted by checking for a response header. See 264 // ajax_set_verification_header() for details. 265 // - Empty responses are harmless so can bypass verification. This avoids 266 // an alert message for server-generated no-op responses that skip Ajax 267 // rendering. 268 // - Ajax objects with trusted URLs (e.g., ones defined server-side via 269 // #ajax) can bypass header verification. This is especially useful for 270 // Ajax with multipart forms. Because IFRAME transport is used, the 271 // response headers cannot be accessed for verification. 272 if (response !== null && !Drupal.settings.urlIsAjaxTrusted[ajax.url]) { 273 if (xmlhttprequest.getResponseHeader('X-Drupal-Ajax-Token') !== '1') { 274 var customMessage = Drupal.t("The response failed verification so will not be processed."); 275 return ajax.error(xmlhttprequest, ajax.url, customMessage); 276 } 277 } 278 279 return ajax.success(response, status); 280 }, 281 complete: function (xmlhttprequest, status) { 282 ajax.ajaxing = false;
283 if (status == 'error' || status == 'parsererror') { 284 return ajax.error(xmlhttprequest, ajax.url); 285 } 286 }, 287 dataType: 'json', 288 jsonp: false, 289 type: 'POST' 290 }; 291 292 // For multipart forms (e.g., file uploads), jQuery Form targets the form 293 // submission to an iframe instead of using an XHR object. The initial "src" 294 // of the iframe, prior to the form submission, is set to options.iframeSrc. 295 // "about:blank" is the semantically correct, standards-compliant, way to 296 // initialize a blank iframe; however, some old IE versions (including 6, 297 // unclear for 7-10, but not 11) incorrectly report a mixed content warning 298 // when iframes with an "about:blank" src are added to a parent document with 299 // an https:// origin. jQuery Form works around this by defaulting to 300 // "javascript:false" instead, but that breaks on Chrome 83, so here we 301 // force the semantically correct behavior for all browsers except old IE. 302 // @see https://www.drupal.org/project/drupal/issues/3143016 303 // @see https://github.com/jquery-form/form/blob/df9cb101b9c9c085c8d75ad980c7ff1cf62063a1/jquery.form.js#L68 304 // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1084874 305 // @see https://html.spec.whatwg.org/multipage/browsers.html#creating-browsing-contexts 306 // @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy 307 if (navigator.userAgent.indexOf("MSIE") === -1) { 308 ajax.options.iframeSrc = 'about:blank'; 309 } 310 311 // For multipart forms (e.g., file uploads), jQuery Form targets the form 312 // submission to an iframe instead of using an XHR object. The initial "src" 313 // of the iframe, prior to the form submission, is set to options.iframeSrc. 314 // "about:blank" is the semantically correct, standards-compliant, way to 315 // initialize a blank iframe; however, some old IE versions (possibly only 6) 316 // incorrectly report a mixed content warning when iframes with an 317 // "about:blank" src are added to a parent document with an https:// origin. 318 // jQuery Form works around this by defaulting to "javascript:false" instead, 319 // but that breaks on Chrome 83, so here we force the semantically correct 320 // behavior for all browsers except old IE. 321 // @see https://www.drupal.org/project/drupal/issues/3143016 322 // @see https://github.com/jquery-form/form/blob/df9cb101b9c9c085c8d75ad980c7ff1cf62063a1/jquery.form.js#L68 323 // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1084874 324 // @see https://html.spec.whatwg.org/multipage/browsers.html#creating-browsing-contexts 325 // @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy 326 if (navigator.userAgent.indexOf("MSIE") === -1) { 327 ajax.options.iframeSrc = 'about:blank'; 328 } 329 330 // Bind the ajaxSubmit function to the element event. 331 $(ajax.element).bind(element_settings.event, function (event) { 332 if (!Drupal.settings.urlIsAjaxTrusted[ajax.url] && !Drupal.urlIsLocal(ajax.url)) { 333 throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', {'!url': ajax.url})); 334 } 335 return ajax.eventResponse(this, event); 336 }); 337 338 // If necessary, enable keyboard submission so that Ajax behaviors 339 // can be triggered through keyboard input as well as e.g. a mousedown 340 // action. 341 if (element_settings.keypress) { 342 $(ajax.element).keypress(function (event) { 343 return ajax.keypressResponse(this, event); 344 }); 345 } 346 347 // If necessary, prevent the browser default action of an additional event. 348 // For example, prevent the browser default action of a click, even if the 349 // AJAX behavior binds to mousedown. 350 if (element_settings.prevent) { 351 $(ajax.element).bind(element_settings.prevent, false); 352 } 353}; 354 355/** 356 * Handle a key press. 357 * 358 * The Ajax object will, if instructed, bind to a key press response. This 359 * will test to see if the key press is valid to trigger this event and 360 * if it is, trigger it for us and prevent other keypresses from triggering. 361 * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13 362 * and 32. RETURN is often used to submit a form when in a textfield, and 363 * SPACE is often used to activate an element without submitting. 364 */ 365Drupal.ajax.prototype.keypressResponse = function (element, event) {
366 // Create a synonym for this to reduce code confusion. 367 var ajax = this; 368 369 // Detect enter key and space bar and allow the standard response for them, 370 // except for form elements of type 'text' and 'textarea', where the 371 // spacebar activation causes inappropriate activation if #ajax['keypress'] is 372 // TRUE. On a text-type widget a space should always be a space. 373 if (event.which == 13 || (event.which == 32 && element.type != 'text' && element.type != 'textarea')) { 374 $(ajax.element_settings.element).trigger(ajax.element_settings.event); 375 return false; 376 } 377}; 378 379/** 380 * Handle an event that triggers an Ajax response. 381 * 382 * When an event that triggers an Ajax response happens, this method will 383 * perform the actual Ajax call. It is bound to the event using 384 * bind() in the constructor, and it uses the options specified on the 385 * ajax object. 386 */ 387Drupal.ajax.prototype.eventResponse = function (element, event) { 388 // Create a synonym for this to reduce code confusion. 389 var ajax = this; 390 391 // Do not perform another ajax command if one is already in progress. 392 if (ajax.ajaxing) { 393 return false; 394 } 395 396 try { 397 if (ajax.form) { 398 // If setClick is set, we must set this to ensure that the button's 399 // value is passed. 400 if (ajax.setClick) { 401 // Mark the clicked button. 'form.clk' is a special variable for 402 // ajaxSubmit that tells the system which element got clicked to 403 // trigger the submit. Without it there would be no 'op' or 404 // equivalent. 405 element.form.clk = element; 406 } 407 408 ajax.form.ajaxSubmit(ajax.options); 409 } 410 else { 411 ajax.beforeSerialize(ajax.element, ajax.options); 412 $.ajax(ajax.options); 413 } 414 } 415 catch (e) { 416 // Unset the ajax.ajaxing flag here because it won't be unset during 417 // the complete response. 418 ajax.ajaxing = false; 419 alert("An error occurred while attempting to process " + ajax.options.url + ": " + e.message); 420 } 421 422 // For radio/checkbox, allow the default event. On IE, this means letting 423 // it actually check the box. 424 if (typeof element.type != 'undefined' && (element.type == 'checkbox' || element.type == 'radio')) { 425 return true; 426 } 427 else { 428 return false; 429 } 430 431}; 432 433/** 434 * Handler for the form serialization. 435 * 436 * Runs before the beforeSend() handler (see below), and unlike that one, runs 437 * before field data is collected. 438 */ 439Drupal.ajax.prototype.beforeSerialize = function (element, options) { 440 // Allow detaching behaviors to update field values before collecting them. 441 // This is only needed when field values are added to the POST data, so only 442 // when there is a form such that this.form.ajaxSubmit() is used instead of 443 // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize() 444 // isn't called, but don't rely on that: explicitly check this.form. 445 if (this.form) { 446 var settings = this.settings || Drupal.settings; 447 Drupal.detachBehaviors(this.form, settings, 'serialize'); 448 } 449 450 // Prevent duplicate HTML ids in the returned markup. 451 // @see drupal_html_id() 452 options.data['ajax_html_ids[]'] = []; 453 $('[id]').each(function () { 454 options.data['ajax_html_ids[]'].push(this.id); 455 }); 456 457 // Allow Drupal to return new JavaScript and CSS files to load without 458 // returning the ones already loaded. 459 // @see ajax_base_page_theme() 460 // @see drupal_get_css() 461 // @see drupal_get_js() 462 options.data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme; 463 options.data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token; 464 for (var key in Drupal.settings.ajaxPageState.css) { 465 options.data['ajax_page_state[css][' + key + ']'] = 1; 466 } 467 for (var key in Drupal.settings.ajaxPageState.js) { 468 options.data['ajax_page_state[js][' + key + ']'] = 1; 469 } 470}; 471 472/** 473 * Modify form values prior to form submission. 474 */ 475Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) { 476 // This function is left empty to make it simple to override for modules 477 // that wish to add functionality here. 478}; 479 480/** 481 * Prepare the Ajax request before it is sent. 482 */ 483Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) { 484 // For forms without file inputs, the jQuery Form plugin serializes the form 485 // values, and then calls jQuery's $.ajax() function, which invokes this 486 // handler. In this circumstance, options.extraData is never used. For forms 487 // with file inputs, the jQuery Form plugin uses the browser's normal form 488 // submission mechanism, but captures the response in a hidden IFRAME. In this 489 // circumstance, it calls this handler first, and then appends hidden fields 490 // to the form to submit the values in options.extraData. There is no simple 491 // way to know which submission mechanism will be used, so we add to extraData 492 // regardless, and allow it to be ignored in the former case. 493 if (this.form) { 494 options.extraData = options.extraData || {}; 495 496 // Let the server know when the IFRAME submission mechanism is used. The 497 // server can use this information to wrap the JSON response in a TEXTAREA, 498 // as per http://jquery.malsup.com/form/#file-upload. 499 options.extraData.ajax_iframe_upload = '1'; 500 501 // The triggering element is about to be disabled (see below), but if it 502 // contains a value (e.g., a checkbox, textfield, select, etc.), ensure that 503 // value is included in the submission. As per above, submissions that use 504 // $.ajax() are already serialized prior to the element being disabled, so 505 // this is only needed for IFRAME submissions. 506 var v = $.fieldValue(this.element); 507 if (v !== null) { 508 options.extraData[this.element.name] = Drupal.checkPlain(v); 509 } 510 } 511 512 // Disable the element that received the change to prevent user interface 513 // interaction while the Ajax request is in progress. ajax.ajaxing prevents 514 // the element from triggering a new request, but does not prevent the user 515 // from changing its value. 516 $(this.element).addClass('progress-disabled').attr('disabled', true); 517 518 // Insert progressbar or throbber. 519 if (this.progress.type == 'bar') { 520 var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.i
520d, $.noop, this.progress.method, $.noop); 521 if (this.progress.message) { 522 progressBar.setProgress(-1, this.progress.message); 523 } 524 if (this.progress.url) { 525 progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500); 526 } 527 this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar'); 528 this.progress.object = progressBar; 529 $(this.element).after(this.progress.element); 530 } 531 else if (this.progress.type == 'throbber') { 532 this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>'); 533 if (this.progress.message) { 534 $('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>'); 535 } 536 $(this.element).after(this.progress.element); 537 } 538}; 539 540/** 541 * Handler for the form redirection completion. 542 */ 543Drupal.ajax.prototype.success = function (response, status) { 544 // Remove the progress element. 545 if (this.progress.element) { 546 $(this.progress.element).remove(); 547 } 548 if (this.progress.object) { 549 this.progress.object.stopMonitoring(); 550 } 551 $(this.element).removeClass('progress-disabled').removeAttr('disabled'); 552 553 Drupal.freezeHeight(); 554 555 for (var i in response) { 556 if (response.hasOwnProperty(i) && response[i]['command'] && this.commands[response[i]['command']]) { 557 this.commands[response[i]['command']](this, response[i], status); 558 } 559 } 560 561 // Reattach behaviors, if they were detached in beforeSerialize(). The 562 // attachBehaviors() called on the new content from processing the response 563 // commands is not sufficient, because behaviors from the entire form need 564 // to be reattached. 565 if (this.form) { 566 var settings = this.settings || Drupal.settings; 567 Drupal.attachBehaviors(this.form, settings); 568 } 569 570 Drupal.unfreezeHeight(); 571 572 // Remove any response-specific settings so they don't get used on the next 573 // call by mistake. 574 this.settings = null; 575}; 576 577/** 578 * Build an effect object which tells us how to apply the effect when adding new HTML. 579 */ 580Drupal.ajax.prototype.getEffect = function (response) { 581 var type = response.effect || this.effect; 582 var speed = response.speed || this.speed; 583 584 var effect = {}; 585 if (type == 'none') { 586 effect.showEffect = 'show'; 587 effect.hideEffect = 'hide'; 588 effect.showSpeed = ''; 589 } 590 else if (type == 'fade') { 591 effect.showEffect = 'fadeIn'; 592 effect.hideEffect = 'fadeOut'; 593 effect.showSpeed = speed; 594 } 595 else { 596 effect.showEffect = type + 'Toggle'; 597 effect.hideEffect = type + 'Toggle'; 598 effect.showSpeed = speed; 599 } 600 601 return effect; 602}; 603 604/** 605 * Handler for the form redirection error. 606 */ 607Drupal.ajax.prototype.error = function (xmlhttprequest, uri, customMessage) { 608 Drupal.displayAjaxError(Drupal.ajaxError(xmlhttprequest, uri, customMessage)); 609 // Remove the progress element. 610 if (this.progress.element) { 611 $(this.progress.element).remove(); 612 } 613 if (this.progress.object) { 614 this.progress.object.stopMonitoring(); 615 } 616 // Undo hide. 617 $(this.wrapper).show(); 618 // Re-enable the element. 619 $(this.element).removeClass('progress-disabled').removeAttr('disabled'); 620 // Reattach behaviors, if they were detached in beforeSerialize(). 621 if (this.form) { 622 var settings = this.settings || Drupal.settings; 623 Drupal.attachBehaviors(this.form, settings); 624 } 625}; 626 627/** 628 * Provide a series of commands that the server can request the client perform. 629 */ 630Drupal.ajax.prototype.commands = { 631 /** 632 * Command to insert new content into the DOM. 633 */ 634 insert: function (ajax, response, status) { 635 // Get information from the response. If it is not there, default to 636 // our presets. 637 var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper); 638 var method = response.method || ajax.method; 639 var effect = ajax.getEffect(response); 640 641 // We don't know what response.data contains: it might be a string of text 642 // without HTML, so don't rely on jQuery correctly iterpreting 643 // $(response.data) as new HTML rather than a CSS selector. Also, if 644 // response.data contains top-level text nodes, they get lost with either 645 // $(response.data) or $('<div></div>').replaceWith(response.data). 646 var new_content_wrapped = $('<div></div>').html(response.data); 647 var new_content = new_content_wrapped.contents(); 648 649 // For legacy reasons, the effects processing code assumes that new_content 650 // consists of a single top-level element. Also, it has not been 651 // sufficiently tested whether attachBehaviors() can be successfully called 652 // with a context object that includes top-level text nodes. However, to 653 // give developers full control of the HTML appearing in the page, and to 654 // enable Ajax content to be inserted in places where DIV elements are not 655 // allowed (e.g., within TABLE, TR, and SPAN parents), we check if the new 656 // content satisfies the requirement of a single top-level element, and 657 // only use the container DIV created above when it doesn't. For more 658 // information, please see http://drupal.org/node/736066. 659 if (new_content.length != 1 || new_content.get(0).nodeType != 1) { 660 new_content = new_content_wrapped; 661 } 662 663 // If removing content from the wrapper, detach behaviors first. 664 switch (method) { 665 case 'html': 666 case 'replaceWith': 667 case 'replaceAll': 668 case 'empty': 669 case 'remove': 670 var settings = response.settings || ajax.settings || Drupal.settings; 671 Drupal.detachBehaviors(wrapper, settings); 672 } 673 674 // Add the new content to the page. 675 wrapper[method](new_content); 676 677 // Immediately hide the new content if we're using any effects. 678 if (effect.showEffect != 'show') { 679 new_content.hide(); 680 } 681 682 // Determine which effect to use and what content will receive the 683 // effect, then show the new content. 684 if ($('.ajax-new-content', new_content).length > 0) { 685 $('.ajax-new-content', new_content).hide(); 686 new_content.show(); 687 $('.ajax-new-content', new_content)[effect.showEffect](effect.showSpeed); 688 } 689 else if (effect.showEffect != 'show') { 690 new_content[effect.showEffect](effect.showSpeed); 691 } 692 693 // Attach all JavaScript behaviors to the new content, if it was successfully 694 // added to the page, this if statement allows #ajax['wrapper'] to be 695 // optional. 696 if (new_content.parents('html').length > 0) { 697 // Apply any settings from the returned JSON if available. 698 var settings = response.settings || ajax.settings || Drupal.settings; 699 Drupal.attachBehaviors(new_content, settings); 700 } 701 }, 702 703 /** 704 * Command to remove a chunk from the page. 705 */ 706 remove: function (ajax, response, status) { 707 var settings = response.settings || ajax.settings || Drupal.settings; 708 Drupal.detachBehaviors($(response.selector), settings); 709 $(response.selector).remove(); 710 }, 711 712 /** 713 * Command to mark a chunk changed. 714 */ 715 changed: function (ajax, response, status) { 716 if (!$(response.selector).hasClass('ajax-changed')) { 717 $(response.selector).addClass('ajax-changed'); 718 if (response.asterisk) { 719 $(response.selector).find(response.asterisk).append(' <span class="ajax-changed">*</span> '); 720 } 721 } 722 }, 723 724 /** 725 * Command to provide an alert. 726 */ 727 alert: function (ajax, response, status) { 728 alert(response.text, response.title); 729 }, 730 731 /** 732 * Command to provide the jQuery css() function. 733 */ 734 css: function (ajax, response, status) { 735 $(response.selector).css(response.argument); 736 }, 737 738 /** 739 * Command to set the settings that will be used for other commands in this response. 740 */ 741 settings: function (ajax, response, status) { 742 if (response.merge) { 743 $.extend(true, Drupal.settings, response.settings); 744 } 745 else { 746 ajax.settings = response.settings; 747 } 748 }, 749 750 /** 751 * Command to attach data using jQuery's data API. 752 */ 753 data: function (ajax, response, status) { 754 $(response.selector).data(response.name, response.value); 755 }, 756 757 /** 758 * Command to apply a jQuery method. 759 */ 760 invoke: function (ajax, response, status) { 761 var $element = $(response.selector); 762 $element[response.method].apply($element, response.arguments); 763 }, 764 765 /** 766 * Command to restripe a table. 767 */ 768 restripe: function (ajax, response, status) { 769 // :even and :odd are reversed because jQuery counts from 0 and 770 // we count from 1, so we're out of sync. 771 // Match immediate children of the parent element to allow nesting. 772 $('> tbody > tr:visible, > tr:visible', $(response.selector)) 773 .removeClass('odd even') 774 .filter(':even').addClass('odd').end() 775 .filter(':odd').addClass('even'); 776 }, 777 778 /** 779 * Command to add css. 780 * 781 * Uses the proprietary addImport method if available as browsers which 782 * support that method ignore @import statements in dynamically added 783 * stylesheets. 784 */ 785 add_css: function (ajax, response, status) { 786 // Add the styles in the normal way. 787 $('head').prepend(response.data); 788 // Add imports in the styles using the addImport method if available. 789 var match, importMatch = /^@import url\("(.*)"\);$/igm; 790 if (document.styleSheets[0].addImport && importMatch.test(response.data)) { 791 importMatch.lastIndex = 0; 792 while (match = importMatch.exec(response.data)) { 793 document.styleSheets[0].addImport(match[1]); 794 } 795 } 796 }, 797 798 /** 799 * Command to update a form's build ID. 800 */ 801 updateBuildId: function(ajax, response, status) { 802 $('input[name="form_build_id"][value="' + response['old'] + '"]').val(response['new']); 803 } 804}; 805 806})(jQuery); 807;/*})'"*/;/*})'"*/ 808(function ($) { 809 810 Drupal.honeypot = {}; 811 Drupal.honeypot.timestampJS = new Date(); 812 813 Drupal.behaviors.honeypotJS = { 814 attach: function (context, settings) { 815 $('form.honeypot-timestamp-js').once('honeypot-timestamp').bind('submit', function() { 816 var $honeypotTime = $(this).find('input[name="honeypot_time"]'); 817 $honeypotTime.attr('value', Drupal.behaviors.honeypotJS.getIntervalTimestamp()); 818 }); 819 }, 820 getIntervalTimestamp: function() { 821 var now = new Date(); 822 var interval = Math.floor((now - Drupal.honeypot.timestampJS) / 1000); 823 return Drupal.settings.honeypot.jsToken + '|' + interval; 824 } 825 }; 826 827 if (Drupal.ajax && Drupal.ajax.prototype && Drupal.ajax.prototype.beforeSubmit) { 828 Drupal.ajax.prototype.honeypotOriginalBeforeSubmit = Drupal.ajax.prototype.beforeSubmit; 829 Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) { 830 if (this.form && $(this.form).hasClass('honeypot-timestamp-js')) { 831 for (key in form_values) { 832 // Inject the right interval timestamp. 833 if (form_values[key].name == 'honeypot_time' && form_values[key].value == 'no_js_available') { 834 form_values[key].value = Drupal.behaviors.honeypotJS.getIntervalTimestamp(); 835 } 836 } 837 } 838 839 // Call the original function in case someone else has overridden it. 840 return Drupal.ajax.prototype.honeypotOriginalBeforeSubmit(form_values, element, options); 841 } 842 } 843 844}(jQuery)); 845;/*})'"*/;/*})'"*/ 846(function (D) { 847 var beforeSerialize = D.ajax.prototype.beforeSerialize;
vendor: 9,254 bytes, lines 848-859
848 D.ajax.prototype.beforeSerialize = function (element, options) { 849 beforeSerialize.call(this, element, options); 850 options.data['ajax_page_state[jquery_version]'] = D.settings.ajaxPageState.jquery_version; 851 } 852})(Drupal); 853;/*})'"*/;/*})'"*/ 854/*! 855 Colorbox 1.6.4 856 license: MIT 857 http://www.jacklmoore.com/colorbox 858*/ 859(function(t,e,i){function n(i,n,o){var r=e.createElement(i);return n&&(r.id=Z+n),o&&(r.style.cssText=o),t(r)}function o(){return i.innerHeight?i.innerHeight:t(i).height()}function r(e,i){i!==Object(i)&&(i={}),this.cache={},this.el=e,this.value=function(e){var n;return void 0===this.cache[e]&&(n=t(this.el).attr("data-cbox-"+e),void 0!==n?this.cache[e]=n:void 0!==i[e]?this.cache[e]=i[e]:void 0!==X[e]&&(this.cache[e]=X[e])),this.cache[e]},this.get=function(e){var i=this.value(e);return t.isFunction(i)?i.call(this.el,this):i}}function h(t){var e=W.length,i=(A+t)%e;return 0>i?e+i:i}function a(t,e){return Math.round((/%/.test(t)?("x"===e?E.width():o())/100:1)*parseInt(t,10))}function s(t,e){return t.get("photo")||t.get("photoRegex").test(e)}function l(t,e){return t.get("retinaUrl")&&i.devicePixelRatio>1?e.replace(t.get("photoRegex"),t.get("retinaSuffix")):e}function d(t){"contains"in x[0]&&!x[0].contains(t.target)&&t.target!==v[0]&&(t.stopPropagation(),x.focus())}function c(t){c.str!==t&&(x.add(v).removeClass(c.str).addClass(t),c.str=t)}function g(e){A=0,e&&e!==!1&&"nofollow"!==e?(W=t("."+te).filter(function(){var i=t.data(this,Y),n=new r(this,i);return n.get("rel")===e}),A=W.index(_.el),-1===A&&(W=W.add(_.el),A=W.length-1)):W=t(_.el)}function u(i){t(e).trigger(i),ae.triggerHandler(i)}function f(i){var o;if(!G){if(o=t(i).data(Y),_=new r(i,o),g(_.get("rel")),!U){U=$=!0,c(_.get("className")),x.css({visibility:"hidden",display:"block",opacity:""}),I=n(se,"LoadedContent","width:0; height:0; overflow:hidden; visibility:hidden"),b.css({width:"",height:""}).append(I),j=T.height()+k.height()+b.outerHeight(!0)-b.height(),D=C.width()+H.width()+b.outerWidth(!0)-b.width(),N=I.outerHeight(!0),z=I.outerWidth(!0);var h=a(_.get("initialWidth"),"x"),s=a(_.get("initialHeight"),"y"),l=_.get("maxWidth"),f=_.get("maxHeight");_.w=Math.max((l!==!1?Math.min(h,a(l,"x")):h)-z-D,0),_.h=Math.max((f!==!1?Math.min(s,a(f,"y")):s)-N-j,0),I.css({width:"",height:_.h}),J.position(),u(ee),_.get("onOpen"),O.add(F).hide(),x.focus(),_.get("trapFocus")&&e.addEventListener&&(e.addEventListener("focus",d,!0),ae.one(re,function(){e.removeEventListener("focus",d,!0)})),_.get("returnFocus")&&ae.one(re,function(){t(_.el).focus()})}var p=parseFloat(_.get("opacity"));v.css({opacity:p===p?p:"",cursor:_.get("overlayClose")?"pointer":"",visibility:"visible"}).show(),_.get("closeButton")?B.html(_.get("close")).appendTo(b):B.appendTo("<div/>"),w()}}function p(){x||(V=!1,E=t(i),x=n(se).attr({id:Y,"class":t.support.opacity===!1?Z+"IE":"",role:"dialog",tabindex:"-1"}).hide(),v=n(se,"Overlay").hide(),L=t([n(se,"LoadingOverlay")[0],n(se,"LoadingGraphic")[0]]),y=n(se,"Wrapper"),b=n(se,"Content").append(F=n(se,"Title"),R=n(se,"Current"),P=t('<button type="button"/>').attr({id:Z+"Previous"}),K=t('<button type="button"/>').attr({id:Z+"Next"}),S=t('<button type="button"/>').attr({id:Z+"Slideshow"}),L),B=t('<button type="button"/>').attr({id:Z+"Close"}),y.append(n(se).append(n(se,"TopLeft"),T=n(se,"TopCenter"),n(se,"TopRight")),n(se,!1,"clear:left").append(C=n(se,"MiddleLeft"),b,H=n(se,"MiddleRight")),n(se,!1,"clear:left").append(n(se,"BottomLeft"),k=n(se,"BottomCenter"),n(se,"BottomRight"))).find("div div").css({"float":"left"}),M=n(se,!1,"position:absolute; width:9999px; visibility:hidden; display:none; max-width:none;"),O=K.add(P).add(R).add(S)),e.body&&!x.parent().length&&t(e.body).append(v,x.append(y,M))}function m(){function i(t){t.which>1||t.shiftKey||t.altKey||t.metaKey||t.ctrlKey||(t.preventDefault(),f(this))}return x?(V||(V=!0,K.click(function(){J.next()}),P.click(function(){J.prev()}),B.click(function(){J.close()}),v.click(function(){_.get("overlayClose")&&J.close()}),t(e).bind("keydown."+Z,function(t){var e=t.keyCode;U&&_.get("escKey")&&27===e&&(t.preventDefault(),J.close()),U&&_.get("arrowKey")&&W[1]&&!t.altKey&&(37===e?(t.preventDefault(),P.click()):39===e&&(t.preventDefault(),K.click()))}),t.isFunction(t.fn.on)?t(e).on("click."+Z,"."+te,i):t("."+te).live("click."+Z,i)),!0):!1}function w(){var e,o,r,h=J.prep,d=++le;if($=!0,q=!1,u(he),u(ie),_.get("onLoad"),_.h=_.get("height")?a(_.get("height"),"y")-N-j:_.get("innerHeight")&&a(_.get("innerHeight"),"y"),_.w=_.get("width")?a(_.get("width"),"x")-z-D:_.get("innerWidth")&&a(_.get("innerWidth"),"x"),_.mw=_.w,_.mh=_.h,_.get("maxWidth")&&(_.mw=a(_.get("maxWidth"),"x")-z-D,_.mw=_.w&&_.w<_.mw?_.w:_.mw),_.get("maxHeight")&&(_.mh=a(_.get("maxHeight"),"y")-N-j,_.mh=_.h&&_.h<_.mh?_.h:_.mh),e=_.get("href"),Q=setTimeout(function(){L.show()},100),_.get("inline")){var c=t(e).eq(0);r=t("<div>").hide().insertBefore(c),ae.one(he,function(){r.replaceWith(c)}),h(c)}else _.get("iframe")?h(" "):_.get("html")?h(_.get("html")):s(_,e)?(e=l(_,e),q=_.get("createImg"),t(q).addClass(Z+"Photo").bind("error."+Z,function(){h(n(se,"Error").html(_.get("imgError")))}).one("load",function(){d===le&&setTimeout(function(){var e;_.get("retinaImage")&&i.devicePixelRatio>1&&(q.height=q.height/i.devicePixelRatio,q.width=q.width/i.devicePixelRatio),_.get("scalePhotos")&&(o=function(){q.height-=q.height*e,q.width-=q.width*e},_.mw&&q.width>_.mw&&(e=(q.width-_.mw)/q.width,o()),_.mh&&q.height>_.mh&&(e=(q.height-_.mh)/q.height,o())),_.h&&(q.style.marginTop=Math.max(_.mh-q.height,0)/2+"px"),W[1]&&(_.get("loop")||W[A+1])&&(q.style.cursor="pointer",t(q).bind("click."+Z,function(){J.next()})),q.style.width=q.width+"px",q.style.height=q.height+"px",h(q)},1)}),q.src=e):e&&M.load(e,_.get("data"),function(e,i){d===le&&h("error"===i?n(se,"Error").html(_.get("xhrError")):t(this).contents())})}var v,x,y,b,T,C,H,k,W,E,I,M,L,F,R,S,K,P,B,O,_,j,D,N,z,A,q,U,$,G,Q,J,V,X={html:!1,photo:!1,iframe:!1,inline:!1,transition:"elastic",speed:300,fadeOut:300,width:!1,initialWidth:"600",innerWidth:!1,maxWidth:!1,height:!1,initialHeight:"450",innerHeight:!1,maxHeight:!1,scalePhotos:!0,scrolling:!0,opacity:.9,preloading:!0,className:!1,overlayClose:!0,escKey:!0,arrowKey:!0,top:!1,bottom:!1,left:!1,right:!1,fixed:!1,data:void 0,closeButton:!0,fastIframe:!0,open:!1,reposition:!0,loop:!0,slideshow:!1,slideshowAuto:!0,slideshowSpeed:2500,slideshowStart:"start slideshow",slideshowStop:"stop slideshow",photoRegex:/\.(gif|png|jp(e|g|eg)|bmp|ico|webp|jxr|svg)((#|\?).*)?$/i,retinaImage:!1,retinaUrl:!1,retinaSuffix:"@2x.$1",current:"image {current} of {total}",previous:"previous",next:"next",close:"close",xhrError:"This content failed to load.",imgError:"This image failed to load.",returnFocus:!0,trapFocus:!0,onOpen:!1,onLoad:!1,onComplete:!1,onCleanup:!1,onClosed:!1,rel:function(){return this.rel},href:function(){return t(this).attr("href")},title:function(){return this.title},createImg:function(){var e=new Image,i=t(this).data("cbox-img-attrs");return"object"==typeof i&&t.each(i,function(t,i){e[t]=i}),e},createIframe:function(){var i=e.createElement("iframe"),n=t(this).data("cbox-iframe-attrs");return"object"==typeof n&&t.each(n,function(t,e){i[t]=e}),"frameBorder"in i&&(i.frameBorder=0),"allowTransparency"in i&&(i.allowTransparency="true"),i.name=(new Date).getTime(),i.allowFullscreen=!0,i}},Y="colorbox",Z="cbox",te=Z+"Element",ee=Z+"_open",ie=Z+"_load",ne=Z+"_complete",oe=Z+"_cleanup",re=Z+"_closed",he=Z+"_purge",ae=t("<a/>"),se="div",le=0,de={},ce=function(){function t(){clearTimeout(h)}function e(){(_.get("loop")||W[A+1])&&(t(),h=setTimeout(J.next,_.get("slideshowSpeed")))}function i(){S.html(_.get("slideshowStop")).unbind(s).one(s,n),ae.bind(ne,e).bind(ie,t),x.removeClass(a+"off").addClass(a+"on")}function n(){t(),ae.unbind(ne,e).unbind(ie,t),S.html(_.get("slideshowStart")).unbind(s).one(s,function(){J.next(),i()}),x.removeClass(a+"on").addClass(a+"off")}function o(){r=!1,S.hide(),t(),ae.unbind(ne,e).unbind(ie,t),x.removeClass(a+"off "+a+"on")}var r,h,a=Z+"Slideshow_",s="click."+Z;return function(){r?_.get("slideshow")||(ae.unbind(oe,o),o()):_.get("slideshow")&&W[1]&&(r=!0,ae.one(oe,o),_.get("slideshowAuto")?i():n(),S.show())}}();t[Y]||(t(p),J=t.fn[Y]=t[Y]=function(e,i){var n,o=this;return e=e||{},t.isFunction(o)&&(o=t("<a/>"),e.open=!0),o[0]?(p(),m()&&(i&&(e.onComplete=i),o.each(function(){var i=t.data(this,Y)||{};t.data(this,Y,t.extend(i,e))}).addClass(te),n=new r(o[0],e),n.get("open")&&f(o[0])),o):o},J.position=function(e,i){function n(){T[0].style.width=k[0].style.width=b[0].style.width=parseInt(x[0].style.width,10)-D+"px",b[0].style.height=C[0].style.height=H[0].style.height=parseInt(x[0].style.height,10)-j+"px"}var r,h,s,l=0,d=0,c=x.offset();if(E.unbind("resize."+Z),x.css({top:-9e4,left:-9e4}),h=E.scrollTop(),s=E.scrollLeft(),_.get("fixed")?(c.top-=h,c.left-=s,x.css({position:"fixed"})):(l=h,d=s,x.css({position:"absolute"})),d+=_.get("right")!==!1?Math.max(E.width()-_.w-z-D-a(_.get("right"),"x"),0):_.get("left")!==!1?a(_.get("left"),"x"):Math.round(Math.max(E.width()-_.w-z-D,0)/2),l+=_.get("bottom")!==!1?Math.max(o()-_.h-N-j-a(_.get("bottom"),"y"),0):_.get("top")!==!1?a(_.get("top"),"y"):Math.round(Math.max(o()-_.h-N-j,0)/2),x.css({top:c.top,left
859:c.left,visibility:"visible"}),y[0].style.width=y[0].style.height="9999px",r={width:_.w+z+D,height:_.h+N+j,top:l,left:d},e){var g=0;t.each(r,function(t){return r[t]!==de[t]?(g=e,void 0):void 0}),e=g}de=r,e||x.css(r),x.dequeue().animate(r,{duration:e||0,complete:function(){n(),$=!1,y[0].style.width=_.w+z+D+"px",y[0].style.height=_.h+N+j+"px",_.get("reposition")&&setTimeout(function(){E.bind("resize."+Z,J.position)},1),t.isFunction(i)&&i()},step:n})},J.resize=function(t){var e;U&&(t=t||{},t.width&&(_.w=a(t.width,"x")-z-D),t.innerWidth&&(_.w=a(t.innerWidth,"x")),I.css({width:_.w}),t.height&&(_.h=a(t.height,"y")-N-j),t.innerHeight&&(_.h=a(t.innerHeight,"y")),t.innerHeight||t.height||(e=I.scrollTop(),I.css({height:"auto"}),_.h=I.height()),I.css({height:_.h}),e&&I.scrollTop(e),J.position("none"===_.get("transition")?0:_.get("speed")))},J.prep=function(i){function o(){return _.w=_.w||I.width(),_.w=_.mw&&_.mw<_.w?_.mw:_.w,_.w}function a(){return _.h=_.h||I.height(),_.h=_.mh&&_.mh<_.h?_.mh:_.h,_.h}if(U){var d,g="none"===_.get("transition")?0:_.get("speed");I.remove(),I=n(se,"LoadedContent").append(i),I.hide().appendTo(M.show()).css({width:o(),overflow:_.get("scrolling")?"auto":"hidden"}).css({height:a()}).prependTo(b),M.hide(),t(q).css({"float":"none"}),c(_.get("className")),d=function(){function i(){t.support.opacity===!1&&x[0].style.removeAttribute("filter")}var n,o,a=W.length;U&&(o=function(){clearTimeout(Q),L.hide(),u(ne),_.get("onComplete")},F.html(_.get("title")).show(),I.show(),a>1?("string"==typeof _.get("current")&&R.html(_.get("current").replace("{current}",A+1).replace("{total}",a)).show(),K[_.get("loop")||a-1>A?"show":"hide"]().html(_.get("next")),P[_.get("loop")||A?"show":"hide"]().html(_.get("previous")),ce(),_.get("preloading")&&t.each([h(-1),h(1)],function(){var i,n=W[this],o=new r(n,t.data(n,Y)),h=o.get("href");h&&s(o,h)&&(h=l(o,h),i=e.createElement("img"),i.src=h)})):O.hide(),_.get("iframe")?(n=_.get("createIframe"),_.get("scrolling")||(n.scrolling="no"),t(n).attr({src:_.get("href"),"class":Z+"Iframe"}).one("load",o).appendTo(I),ae.one(he,function(){n.src="//about:blank"}),_.get("fastIframe")&&t(n).trigger("load")):o(),"fade"===_.get("transition")?x.fadeTo(g,1,i):i())},"fade"===_.get("transition")?x.fadeTo(g,0,function(){J.position(0,d)}):J.position(g,d)}},J.next=function(){!$&&W[1]&&(_.get("loop")||W[A+1])&&(A=h(1),f(W[A]))},J.prev=function(){!$&&W[1]&&(_.get("loop")||A)&&(A=h(-1),f(W[A]))},J.close=function(){U&&!G&&(G=!0,U=!1,u(oe),_.get("onCleanup"),E.unbind("."+Z),v.fadeTo(_.get("fadeOut")||0,0),x.stop().fadeTo(_.get("fadeOut")||0,0,function(){x.hide(),v.hide(),u(he),I.remove(),setTimeout(function(){G=!1,u(re),_.get("onClosed")},1)}))},J.remove=function(){x&&(x.stop(),t[Y].close(),x.stop(!1,!0).remove(),v.remove(),G=!1,x=null,t("."+te).removeData(Y).removeClass(te),t(e).unbind("click."+Z).unbind("keydown."+Z))},J.element=function(){return t(_.el)},J.settings=X)})(jQuery,document,window); 860;/*})'"*/;/*})'"*/ 861/** 862 * @file 863 * Colorbox module init js. 864 */ 865 866(function ($) { 867 868Drupal.behaviors.initColorbox = { 869 attach: function (context, settings) { 870 if (!$.isFunction($('a, area, input', context).colorbox) || typeof settings.colorbox === 'undefined') { 871 return; 872 } 873 874 if (settings.colorbox.mobiledetect && window.matchMedia) { 875 // Disable Colorbox for small screens. 876 var mq = window.matchMedia("(max-device-width: " + settings.colorbox.mobiledevicewidth + ")"); 877 if (mq.matches) { 878 return; 879 } 880 } 881 882 // Use "data-colorbox-gallery" if set otherwise use "rel". 883 settings.colorbox.rel = function () { 884 if ($(this).data('colorbox-gallery')) { 885 return $(this).data('colorbox-gallery'); 886 } 887 else { 888 return $(this).attr('rel'); 889 } 890 }; 891 892 $('.colorbox', context) 893 .once('init-colorbox').each(function(){ 894 // Only images are supported for the "colorbox" class. 895 // The "photo" setting forces the href attribute to be treated as an image. 896 var extendParams = { 897 photo: true 898 }; 899 // If a title attribute is supplied, sanitize it. 900 var title = $(this).attr('title'); 901 if (title) { 902 extendParams.title = Drupal.checkPlain(title); 903 } 904 $(this).colorbox($.extend({}, settings.colorbox, extendParams)); 905 }); 906 907 $(context).bind('cbox_complete', function () { 908 Drupal.attachBehaviors('#cboxLoadedContent'); 909 }); 910 } 911}; 912 913})(jQuery); 914;/*})'"*/;/*})'"*/ 915/** 916 * @file 917 * Colorbox module load js. 918 */ 919(function ($) { 920 921Drupal.behaviors.initColorboxLoad = { 922 attach: function (context, settings) { 923 if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') { 924 return; 925 } 926 927 if (settings.colorbox.mobiledetect && window.matchMedia) { 928 // Disable Colorbox for small screens. 929 var mq = window.matchMedia("(max-device-width: " + settings.colorbox.mobiledevicewidth + ")"); 930 if (mq.matches) { 931 return; 932 } 933 } 934
935 $.urlParams = function (url) { 936 var p = {}, 937 e, 938 a = /\+/g, // Regex for replacing addition symbol with a space. 939 r = /([^&=]+)=?([^&]*)/g, 940 d = function (s) { return decodeURIComponent(s.replace(a, ' ')); }, 941 q = url.split('?'); 942 while (e = r.exec(q[1])) { 943 e[1] = d(e[1]); 944 e[2] = d(e[2]); 945 switch (e[2].toLowerCase()) { 946 case 'true': 947 case 'yes': 948 e[2] = true; 949 break; 950 case 'false': 951 case 'no': 952 e[2] = false; 953 break; 954 } 955 if (e[1] == 'width') { e[1] = 'innerWidth'; } 956 if (e[1] == 'height') { e[1] = 'innerHeight'; } 957 if (e[2]) { 958 e[2] = Drupal.checkPlain(e[2]); 959 } 960 p[e[1]] = e[2]; 961 } 962 return p; 963 }; 964 965 $('.colorbox-load', context) 966 .once('init-colorbox-load', function () { 967 var href = $(this).attr('href'); 968 if (!hrefIsSafe(href)) { 969 return; 970 } 971 972 var params = $.urlParams(href); 973 if (!params.hasOwnProperty('title')) { 974 // If a title attribute is supplied, sanitize it. 975 var title = $(this).attr('title'); 976 if (title) { 977 params.title = Drupal.checkPlain(title); 978 } 979 } 980 $(this).colorbox($.extend({}, settings.colorbox, params)); 981 }); 982 } 983}; 984 985/** 986 * Returns true if the passed-in href string is safe for colorbox_load. 987 * 988 * @param href 989 * The href string to be tested. 990 * 991 * @return 992 * Boolean true if the href is safe. 993 */ 994function hrefIsSafe(href) { 995 var normalizedUrl = Drupal.absoluteUrl(href); 996 997 // Only local, non-file-system URLs are allowed. 998 if (!Drupal.urlIsLocal(normalizedUrl)) { 999 return false; 1000 } 1001 1002 // Reject uploaded files from the public or private file system. 1003 if (normalizedUrl.indexOf(Drupal.settings.colorbox.file_public_path) !== -1 || 1004 normalizedUrl.match(/\/system\/files\//) || 1005 normalizedUrl.match(/[?|&]q=system\/files\//)) { 1006 return false; 1007 } 1008 1009 // All checks passed. 1010 return true; 1011} 1012 1013})(jQuery); 1014;/*})'"*/;/*})'"*/ 1015/** 1016 * @file 1017 * Colorbox module inline js. 1018 */ 1019 1020(function ($) { 1021 1022Drupal.behaviors.initColorboxInline = { 1023 attach: function (context, settings) { 1024 if (!$.isFunction($.colorbox) || typeof settings.colorbox === 'undefined') { 1025 return; 1026 } 1027 1028 if (settings.colorbox.mobiledetect && window.matchMedia) { 1029 // Disable Colorbox for small screens. 1030 var mq = window.matchMedia("(max-device-width: " + settings.colorbox.mobiledevicewidth + ")"); 1031 if (mq.matches) { 1032 return; 1033 } 1034 } 1035 1036 $.urlParam = function(name, url){ 1037 if (name == 'fragment') { 1038 var results = new RegExp('(#[^&#]*)').exec(url); 1039 } 1040 else { 1041 var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(url); 1042 } 1043 if (!results) { return ''; } 1044 return results[1] || ''; 1045 }; 1046 $('.colorbox-inline', context).once('init-colorbox-inline').colorbox({ 1047 transition:settings.colorbox.transition, 1048 speed:settings.colorbox.speed, 1049 opacity:settings.colorbox.opacity, 1050 slideshow:settings.colorbox.slideshow, 1051 slideshowAuto:settings.colorbox.slideshowAuto, 1052 slideshowSpeed:settings.colorbox.slideshowSpeed, 1053 slideshowStart:settings.colorbox.slideshowStart, 1054 slideshowStop:settings.colorbox.slideshowStop, 1055 current:settings.colorbox.current, 1056 previous:settings.colorbox.previous, 1057 next:settings.colorbox.next, 1058 close:settings.colorbox.close, 1059 overlayClose:settings.colorbox.overlayClose, 1060 maxWidth:settings.colorbox.maxWidth, 1061 maxHeight:settings.colorbox.maxHeight, 1062 innerWidth:function(){ 1063 return $.urlParam('width', $(this).attr('href')); 1064 }, 1065 innerHeight:function(){ 1066 return $.urlParam('height', $(this).attr('href')); 1067 }, 1068 title:function(){ 1069 return Drupal.checkPlain(decodeURIComponent(Drupal.checkPlain( 1070 $.urlParam('title', $(this).attr('href'))))); 1071 }, 1072 iframe:function(){ 1073 return $.urlParam('iframe', $(this).attr('href')); 1074 }, 1075 inline:function(){ 1076 return $.urlParam('inline', $(this).attr('href')); 1077 }, 1078 href:function(){ 1079 return $.urlParam('fragment', $(this).attr('href')); 1080 } 1081 }); 1082 } 1083}; 1084 1085})(jQuery); 1086;/*})'"*/;/*})'"*/ 1087/** 1088 * Async loader for rotating idea banners. 1089 */ 1090(function ($, Drupal) { 1091 Drupal.behaviors.ltIdeaBanners = { 1092 attach: function (context) { 1093 $('.js-lt-idea-banner', context).once('lt-idea-banner', function () { 1094 var settings = Drupal.settings.ltadvertisingIdeas || {}; 1095 var maxViewport = parseInt(settings.maxViewport, 10) || 0; 1096 if (maxViewport && window.innerWidth > maxViewport) { 1097 $(this).remove(); 1098 return; 1099 } 1100 1101 var $slot = $(this); 1102 var $placeholder = $slot.find('.lt-idea-banner__placeholder'); 1103 if (!$placeholder.length) {
1104 $placeholder = $('<div class="lt-idea-banner__placeholder"></div>').appendTo($slot.empty()); 1105 } 1106 1107 var isDark = $('body').hasClass('dark') || $('body').hasClass('theme-dark'); 1108 $placeholder 1109 .removeClass('lt-idea-banner__placeholder--light lt-idea-banner__placeholder--dark') 1110 .addClass(isDark ? 'lt-idea-banner__placeholder--dark' : 'lt-idea-banner__placeholder--light'); 1111 1112 var basePath = (Drupal.settings && Drupal.settings.basePath) ? Drupal.settings.basePath : '/'; 1113 var endpoint = settings.endpoint || (basePath + 'callback/adv/idea-banner'); 1114 1115 $.ajax({ 1116 url: endpoint, 1117 dataType: 'json', 1118 cache: false, 1119 }).done(function (response) { 1120 if (!response || response.status !== 'ok' || !response.data) { 1121 $slot.remove(); 1122 return; 1123 } 1124 1125 var data = response.data; 1126 var $link = $('<a/>', { 1127 'class': 'lt-idea-banner__link', 1128 href: data.link_url, 1129 rel: 'nofollow noopener', 1130 }); 1131 var $img = $('<img/>', { 1132 'class': 'lt-idea-banner__img', 1133 alt: data.alt || data.title || '' 1134 }).css({ 1135 width: '100%', 1136 height: 'auto', 1137 display: 'block' 1138 }); 1139 1140 $img.attr('src', data.banner_url); 1141 $link.append($img); 1142 1143 var renderBanner = function () { 1144 $img.off('.ltIdeaBanner'); 1145 $slot.empty().append($link); 1146 }; 1147 1148 $img 1149 .on('load.ltIdeaBanner', renderBanner) 1150 .on('error.ltIdeaBanner', function () { 1151 $slot.remove(); 1152 }); 1153 1154 if ($img[0].complete && $img[0].naturalWidth !== 0) { 1155 renderBanner(); 1156 } else { 1157 if (!$placeholder.parent().length) { 1158 $slot.empty().append($placeholder); 1159 } 1160 } 1161 }).fail(function () { 1162 $slot.remove(); 1163 }); 1164 }); 1165 } 1166 }; 1167})(jQuery, Drupal); 1168;/*})'"*/;/*})'"*/ 1169(function($) { 1170$(document).ready(function(){ 1171 1172 $(document).on('click', '.thanks-button.greenbutton', function() { 1173 let $button = $(this); 1174 let $currentContainer = $button.closest('.thanks-button-container'); 1175 let nid = $button.attr('nid'); 1176 1177 languagevar = Drupal.settings.languagevar; 1178 url = languagevar ? '/' + languagevar : ''; 1179 url += '/ajax/ltthanksbutton'; 1180 1181 $button.attr('nid', ''); 1182 $button.removeClass('greenbutton'); 1183 $button.addClass('greenbuttondisable'); 1184 $button.addClass("ajax_loading"); 1185 1186 $button.find('.heart').hide(); 1187 1188 $.ajax({ 1189 type: "POST", 1190 dataType: 'json', 1191 url: url, 1192 data: { 1193 nid: nid 1194 }, 1195 success: function(data) { 1196 $currentContainer.html(data.button); 1197 1198 $currentContainer.closest('.thankstable').find('a#displaythanks').text(data.text); 1199 1200 if (typeof gtag === 'function') { 1201 gtag('event', 'Thanks', {}); 1202 } 1203 } 1204 }); 1205 1206 return false; 1207 }); 1208 1209 // New design thanks button (collection page, etc.) 1210 $(document).on('click', '.collection-thanks-btn, .thanks-details__btn, .who-side__btn[data-nid]', function(e) { 1211 e.preventDefault(); 1212 var $btn = $(this); 1213 var nid = $btn.data('nid'); 1214 if (!nid || $btn.is(':disabled')) return; 1215 1216 var wasThanked = !!$btn.data('thanked'); 1217 var languagevar = Drupal.settings.languagevar; 1218 var url = languagevar ? '/' + languagevar : ''; 1219 url += '/ajax/ltthanksbutton'; 1220 1221 $btn.prop('disabled', true).addClass('ajax_loading_small'); 1222 1223 $.ajax({ 1224 type: 'POST', 1225 dataType: 'json', 1226 url: url, 1227 data: { nid: nid } 1228 }).done(function(res) { 1229 var isThanked = !!res.thanked; 1230 var stateChanged = (isThanked !== wasThanked); 1231 var isLoggedIn = $('body').hasClass('logged-in'); 1232 if (stateChanged && typeof gtag === 'function') { 1233 gtag('event', 'Thanks', {}); 1234 } 1235 // Prefer the server-rendered label from PHP (always translated). Fall 1236 // back to Drupal.t() only if the AJAX response omits it. 1237 var label = res.label_text 1238 || (Drupal.t ? Drupal.t(isThanked ? 'Thanked' : 'Say thanks') 1239 : (isThanked ? 'Thanked' : 'Say thanks')); 1240 // Sync all thanks buttons with same nid on the page 1241 $('.collection-thanks-btn[data-nid="' + nid + '"], .thanks-detail
1241s__btn[data-nid="' + nid + '"], .who-side__btn[data-nid="' + nid + '"]').each(function() { 1242 var $b = $(this); 1243 $b.find('.text').text(label); 1244 $b.data('thanked', isThanked ? 1 : 0); 1245 $b.toggleClass('_thanked', isThanked); 1246 // Guests: keep disabled after thanking (no toggle) 1247 $b.prop('disabled', isThanked && !isLoggedIn); 1248 }); 1249 // Update thanks counters only if state actually changed 1250 if (stateChanged) { 1251 var delta = isThanked ? 1 : -1; 1252 $('.collection-body__thanks-count .num, .row-details__likes .num, .translate__thanks-count .num, .who-side__thanks-count span:not(.icon)').each(function() { 1253 var c = parseInt($.trim($(this).text()), 10); 1254 if (!isNaN(c)) { 1255 var newCount = Math.max(0, c + delta); 1256 $(this).text(newCount); 1257 var $counter = $(this).closest('.translate__thanks-count, .row-details__likes'); 1258 if ($counter.length) $counter.toggle(newCount > 0); 1259 } 1260 }); 1261 } 1262 }).fail(function() { 1263 $btn.prop('disabled', false); 1264 }).always(function() { 1265 $btn.removeClass('ajax_loading_small'); 1266 }); 1267 }); 1268 1269 // Thanks details popup (works on song, translation, collection pages) 1270 var thanksPopupSelector = '[data-thanks-nid]'; 1271 var activeThanksPopup = null; 1272 1273 function positionPopup($popup, $trigger) { 1274 var el = $popup[0]; 1275 var btnRect = $trigger[0].getBoundingClientRect(); 1276 var padding = 8; 1277 1278 // Need to make popup visible to measure it 1279 el.style.visibility = 'hidden'; 1280 el.style.display = 'block'; 1281 var menuRect = el.getBoundingClientRect(); 1282 el.style.visibility = ''; 1283 el.style.display = ''; 1284 1285 var top = btnRect.bottom + padding; 1286 var fitsBelow = (top + menuRect.height) <= (window.innerHeight - padding); 1287 var fitsAbove = (btnRect.top - menuRect.height - padding) >= padding; 1288 1289 if (!fitsBelow && fitsAbove) { 1290 top = btnRect.top - menuRect.height - padding; 1291 } else if (!fitsBelow && !fitsAbove) { 1292 var spaceBelow = window.innerHeight - btnRect.bottom - padding; 1293 var spaceAbove = btnRect.top - padding; 1294 if (spaceBelow >= spaceAbove) { 1295 top = btnRect.bottom + padding; 1296 el.style.maxHeight = spaceBelow + 'px'; 1297 } else { 1298 top = padding; 1299 el.style.maxHeight = spaceAbove + 'px'; 1300 } 1301 el.style.overflowY = 'auto'; 1302 } 1303 1304 // Align to left of trigger, but don't overflow right 1305 var left = btnRect.left; 1306 if (left + menuRect.width > window.innerWidth - padding) { 1307 left = window.innerWidth - menuRect.width - padding; 1308 } 1309 if (left < padding) { left = padding; } 1310 1311 el.style.position = 'absolute'; 1312 el.style.top = (top + window.scrollY) + 'px'; 1313 el.style.left = (left + window.scrollX) + 'px'; 1314 el.style.bottom = 'auto'; 1315 el.style.right = 'auto'; 1316 el.style.marginTop = '0'; 1317 } 1318 1319 function closeThanksPopup() { 1320 if (activeThanksPopup) { 1321 activeThanksPopup.removeClass('-visible'); 1322 var $p = activeThanksPopup; 1323 setTimeout(function() { $p.remove(); }, 200); 1324 activeThanksPopup = null; 1325 } 1326 } 1327 $(document).on('click', function(e) { 1328 if (activeThanksPopup && !$(e.target).closest('.like-users-popup, ' + thanksPopupSelector).length) { 1329 closeThanksPopup(); 1330 } 1331 }); 1332 $(document).on('keydown', function(e) { 1333 if (e.key === 'Escape' && activeThanksPopup) { closeThanksPopup(); } 1334 }); 1335 $(document).on('click', thanksPopupSelector, function(e){ 1336 try { 1337 if ($(e.target).closest('.like-users-popup a').length) { return; } 1338 e.preventDefault(); 1339 e.stopPropagation(); 1340 var nid = $(this).data('thanks-nid'); 1341 if (!nid) { return; } 1342 1343 closeThanksPopup(); 1344 1345 var $trigger = $(this); 1346 var languagevar = (typeof Drupal !== 'undefined' && Drupal.settings && Drupal.settings.languagevar) ? Drupal.settings.languagevar : ''; 1347 var url = (languagevar ? '/' + languagevar : '') + '/callback/get/thankslist'; 1348 var t = (window.Drupal && Drupal.t) ? Drupal.t : function(s){ return s; }; 1349 1350 var $popup = $('<div class="like-users-popup -loading -global">
1350' + 1351 '<div class="like-users-popup__header">' + t('Thanked by') + '</div>' + 1352 '<ul class="like-users-popup__list"></ul>' + 1353 '</div>'); 1354 1355 $(document.body).append($popup); 1356 setTimeout(function() { 1357 positionPopup($popup, $trigger); 1358 $popup.addClass('-visible'); 1359 }, 10); 1360 activeThanksPopup = $popup; 1361 1362 $.ajax({ 1363 type: 'POST', 1364 url: url, 1365 dataType: 'json', 1366 data: { nid: nid } 1367 }).done(function(res){ 1368 $popup.removeClass('-loading'); 1369 var html = (res && res.html) ? res.html : ''; 1370 if (!html) { 1371 $popup.find('.like-users-popup__list').html('<li class="like-users-popup__item">' + t('No data') + '</li>'); 1372 return; 1373 } 1374 var $temp = $('<div>').html(html); 1375 var items = []; 1376 $temp.find('.table__trow').each(function() { 1377 var $username = $(this).find('.ltnodethanks-username'); 1378 var $date = $(this).find('.ltnodethanks-date'); 1379 if ($username.length) { 1380 var $link = $username.find('a'); 1381 var nameHtml = $link.length ? '<a href="' + $link.attr('href') + '">' + $link.text() + '</a>' : $username.text(); 1382 var dateText = $date.length ? '<span class="like-users-popup__date">' + $date.text() + '</span>' : ''; 1383 items.push('<li class="like-users-popup__item">' + nameHtml + dateText + '</li>'); 1384 } 1385 }); 1386 var $guests = $temp.find('.ltnodethanks-guests'); 1387 if ($guests.length) { 1388 items.push('<li class="like-users-popup__more">' + $guests.text() + '</li>'); 1389 } 1390 if (items.length) { 1391 $popup.find('.like-users-popup__list').html(items.join('')); 1392 positionPopup($popup, $trigger); 1393 } else { 1394 $popup.find('.like-users-popup__list').html('<li class="like-users-popup__item">' + t('No data') + '</li>'); 1395 } 1396 }).fail(function(){ 1397 $popup.removeClass('-loading'); 1398 $popup.find('.like-users-popup__list').html('<li class="like-users-popup__item">' + t('Failed to load') + '</li>'); 1399 }); 1400 } catch(err) {} 1401 }); 1402 1403 $(document).on('click', '.displaythanks-link', function(e) { 1404 let nid = $(this).data('nid'); 1405 let $thanksContainer = $('#ltnodethanks-' + nid); 1406 1407 languagevar = Drupal.settings.languagevar; 1408 url = languagevar ? '/' + languagevar : ''; 1409 url += '/callback/get/thankslist'; 1410 1411 $.ajax({ 1412 type: "POST", 1413 dataType: 'json', 1414 url: url, 1415 data: { 1416 nid: nid 1417 }, 1418 success: function(data) { 1419 $thanksContainer.html(data.html); 1420 $thanksContainer.css('background', ''); 1421 } 1422 }); 1423 }); 1424 1425}); 1426})(jQuery); 1427;/*})'"*/;/*})'"*/ 1428$(document).ready(function(){ 1429 1430 var tooltipsterConfig = { 1431 content: 'Loading...', 1432 delay: 700, 1433 side: ['right', 'top', 'bottom', 'left'], 1434 contentAsHTML: true, 1435 theme: ['tooltipster-shadow', 'tooltipster-userinfo'], 1436 maxWidth: 280, 1437 minWidth: 280, 1438 updateAnimation: null, 1439 interactive: true, 1440 functionBefore: function(instance, helper) { 1441 1442 var $origin = $(helper.origin); 1443 1444 languagevar = Drupal.settings.languagevar; 1445 url = languagevar ? '/' + languagevar : ''; 1446 url += '/callback/get/userinfo'; 1447 1448 // we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens 1449 if ($origin.data('loaded') !== true) { 1450 1451 var $uid = instance.elementOrigin().slot || instance.elementOrigin().rel; 1452 1453 if ($uid !== 'nofollow') { 1454 1455 $.get(url, { id: $uid }, function(data) { 1456 1457 // call the 'content' method to update the content of our tooltip with the returned data. 1458 instance.content(data); 1459 1460 // to remember that the data has been loaded 1461 $origin.data('loaded', true); 1462 }); 1463 } 1464 } 1465 } 1466 }; 1467 1468 // Lazy init: only create tooltipster on first hover 1469 $(document).on('mouseenter', '.userpopupinfo', function() { 1470 var $el = $(this); 1471 if ($el.hasClass('tooltipstered')) return; // already initialized 1472 $el.tooltipster(tooltipsterConfig); 1473 $el.tooltipster('open'); 1474 }); 1475 1476}); 1477;/*})'"*/;/*})'"*/ 1478function addSpinner(el) { 1479 el.length || (el = $(el)); 1480 1481 el.addClass('spinner'); 1482} 1483 1484 1485function removeSpinner(el) { 1486 el.length || (el = $(el)); 1487 el.length || (el = $('body')); 1488 1489 el.removeClass('spinner'); 1490} 1491;/*})'"*/;/*})'"*/ 1492var utils = { 1493 escapeRegExChars: function (value) { 1494 return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&"); 1495 } 1496}; 1497 1498$(document).ready(function () { 1499 // Hide stale Devbridge autocomplete dropdowns when returning via back/forward cache 1500 $(window).on('pageshow', function(event) { 1501 var isBackForward = false;
1502 try { 1503 var navEntries = (window.performance && performance.getEntriesByType) ? performance.getEntriesByType('navigation') : []; 1504 if (navEntries && navEntries.length && navEntries[0].type === 'back_forward') { 1505 isBackForward = true; 1506 } 1507 } catch (e) {} 1508 if (!isBackForward && event && event.originalEvent && event.originalEvent.persisted) { 1509 isBackForward = true; 1510 } 1511 1512 if (isBackForward) { 1513 $('.autocomplete-suggestions').hide(); 1514 } 1515 }); 1516 1517 var searchwidth = (deviceType() === 'mobile') ? '280' : 'auto'; 1518 var searchheight = (deviceType() === 'mobile') ? '300' : '500'; 1519 1520 var serviceUrl = '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete'; 1521 // Check if artistnid is set and append it as a query parameter 1522 if (typeof Drupal.settings.artistnid !== 'undefined') { 1523 serviceUrl += '?artistnid=' + encodeURIComponent(Drupal.settings.artistnid); 1524 } 1525 1526 // Initialize autocomplete (exclude header input to avoid double-binding with Algolia) 1527 var searchforminputs = $('#edit-filter3'); 1528 1529 if (searchforminputs.length) { 1530 searchforminputs.autocomplete({ 1531 serviceUrl: serviceUrl, // 1532 minChars: 2, 1533 deferRequestBy: 150, 1534 width: searchwidth, 1535 maxHeight: searchheight, 1536 groupBy: 'category', 1537 triggerSelectOnValidInput: false, 1538 preventBadQueries: false, 1539 preserveInput: true, 1540 onSearchStart: function(params){ 1541 try { 1542 // Show placeholder row after a short delay to avoid flicker on fast responses 1543 if (window.__acLoadingTimer) { clearTimeout(window.__acLoadingTimer); } 1544 var ac = $(this).data('autocomplete'); 1545 window.__acLoadingTimer = setTimeout(function(){ 1546 try { 1547 if (!ac || !ac.suggestionsContainer) { return; } 1548 var $box = $(ac.suggestionsContainer); 1549 var text = (window.Drupal && Drupal.t) ? Drupal.t('Loadingâ¦') : 'Loadingâ¦'; 1550 var html = '<div class="autocomplete-suggestion ac-loading-item" data-index="-1">' 1551 + '<span class="ajax_loading_small" style="display:inline-block;width:16px;height:16px;margin:0 8px 0 4px;vertical-align:middle;"></span>' 1552 + '<span class="text">' + text + '</span>' 1553 + '</div>'; 1554 ac.fixPosition(); 1555 $box.html(html).show(); 1556 ac.visible = true; 1557 ac.selectedIndex = -1; 1558 } catch(e) {} 1559 }, 250); 1560 } catch(e) {} 1561 }, 1562 onSearchComplete: function(query, suggestions){ 1563 try { 1564 if (window.__acLoadingTimer) { clearTimeout(window.__acLoadingTimer); window.__acLoadingTimer = null; } 1565 var ac = this; 1566 if (ac && ac.suggestionsContainer) { ac.suggestionsContainer.find('.ac-loading-item').remove(); } 1567 } catch(e) {} 1568 }, 1569 beforeRender: function(container){ 1570 try { $(container).find('.ac-loading-item').remove(); } catch(e) {} 1571 }, 1572 onHide: function(container){ 1573 try { $(container).find('.ac-loading-item').remove(); } catch(e) {} 1574 }, 1575 onSearchError: function(query, jqXHR, textStatus, errorThrown){ 1576 try { 1577 if (window.__acLoadingTimer) { clearTimeout(window.__acLoadingTimer); window.__acLoadingTimer = null; } 1578 var ac = this; 1579 if (ac && ac.suggestionsContainer) { ac.suggestionsContainer.find('.ac-loading-item').remove(); } 1580 } catch(e) {} 1581 }, 1582 onSelect: function(suggestion){ 1583 var form = $(this).closest("form"); 1584 var submit = form.find(':submit'); 1585 submit.addClass("ajax_loading_small"); 1586 1587 $(this).autocomplete('close'); 1588 $(this).autocomplete('disable'); 1589 1590 if (suggestion.data.url) { 1591 window.location.href = suggestion.data.url; 1592 if (typeof gtag !== 'undefined') { 1593 gtag('event', 'search_suggestion_click', {}); 1594 } 1595 } 1596 else { 1597 form.submit(); 1598 } 1599 }, 1600 noSuggestionNotice: Drupal.t('Hit the search icon and let the magic happen!'), 1601 showNoSuggestionNotice: true 1602 }); 1603 1604 // Ensure arrow keys navigate suggestions list reliably (DEVBRIDGE autocomplete) 1605 searchforminputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){ 1606 var instance = $(this).data('autocomplete'); 1607 if (!instance) { return; } 1608 // Down arrow 1609 if (e.which === 40) { 1610 if (!instance.visible && instance.currentValue) { 1611 instance.suggest(); 1612 } else { 1613 instance.moveDown(); 1614 } 1615 e.preventDefault(); 1616 e.stopImmediatePropagation(); 1617 } 1618 // Up arrow 1619 else if (e.which === 38) { 1620 if (instance.visible) { 1621 instance.moveUp(); 1622 e.preventDefault(); 1623 e.stopImmediatePropagation(); 1624 } 1625 } 1626 }); 1627 } 1628 1629 var usernameinputs = $('#edit-formtree-ltsearchusername, #edit-formtree-ltloglistactionusername, #edit-formtree-ltloglistnodeusername'); 1630 1631 if (usernameinputs.length) { 1632 usernameinputs.autocomplete({ 1633 serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getmember', // 1634 minChars: 2 , 1635 deferRequestBy: 150, 1636 triggerSelectOnValidInput: false, 1637 preventBadQueries: false, 1638 }); 1639 1640 // Attach keyboard navigation fix for these inputs as well 1641 usernameinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){ 1642 var instance = $(this).data('autocomplete'); 1643 if (!instance) { return; } 1644 if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); } 1645 else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } } 1646 }); 1647 } 1648 1649 var usernameinputs = $('#lyricstranslatesearchusername input, #lyricstranslatesearchtextmembers input'); 1650 1651 if (usernameinputs.length) { 1652 usernameinputs.autocomplete({ 1653 serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getmember', // 1654 minChars: 2 , 1655 deferRequestBy: 150, 1656 triggerSelectOnValidInput: false, 1657 preventBadQueries: false, 1658 onSelect: function(suggestion){ 1659 var form = $(this).closest("form"); 1660 var submit = form.find(':submit'); 1661 submit.addClass("ajax_loading_small"); 1662 form.submit(); 1663 } 1664 }); 1665 1666 usernameinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){ 1667 var instance = $(this).data('autocomplete'); 1668 if (!instance) { return; } 1669 if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); } 1670 else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } } 1671 }); 1672 } 1673 1674 var artistinputs = $('#lyricstranslatesearchtextartist input'); 1675 1676 if (artistinputs.length) { 1677 artistinputs.autocomplete({ 1678 serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getartist', // 1679 minChars: 2 , 1680 deferRequestBy: 150, 1681 triggerSelectOnValidInput: false, 1682 preventBadQueries: false, 1683 onSelect: function(suggestion){ 1684 var form = $(this).closest("form"); 1685 var submit = form.find(':submit'); 1686 submit.addClass("ajax_loading_small"); 1687 form.submit(); 1688 } 1689 }); 1690 1691 artistinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){ 1692 var instance = $(this).data('autocomplete'); 1693 if (!instance) { return; } 1694 if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); } 1695 else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } } 1696 }); 1697 } 1698 1699 var songinputs = $('#lyricstranslatesearchtextsong input'); 1700 1701 if (songinputs.length) { 1702 1703 songinputs.autocomplete({ 1704 serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/song', // 1705 minChars: 2 , 1706 deferRequestBy: 150, 1707 maxHeight: 170, 1708 triggerSelectOnValidInput: false, 1709 preventBadQueries: false, 1710 params: { 1711 artist: function () { 1712 if ($('#lyricstranslatesearchtextartist input').length) { 1713 return $('#lyricstranslatesearchtextartist input').val(); 1714 } 1715 } 1716 }, 1717 onSelect: function(suggestion){ 1718 var form = $(this).closest("form"); 1719 var submit = form.find(':submit'); 1720 submit.addClass("ajax_loading_small"); 1721 form.submit(); 1722 } 1723 }); 1724 1725 songinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){ 1726 var instance = $(this).data('autocomplete'); 1727 if (!instance) { return; } 1728 if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); } 1729 else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } } 1730 }); 1731 } 1732 1733 1734 //Ð´Ð»Ñ ÑоÑÐ¼Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑквиваленÑа 1735 if($("div#phrasesanalogtitle input.form-text").length) { 1736 $('div#phrasesanalogtitle input.form-text').autocomplete({ 1737 triggerSelectOnValidInput: false, 1738 preventBadQueries: false, 1739 minChars: 2, 1740 deferRequestBy: 150, 1741 serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getphrases', 1742 onSelect: function(suggestion){ 1743 $('div#phraseanaloghelp p').html( 1744 'Phrase <i>'.concat( $('h1').html() , '</i> will be added as equivalent to the phrase: <i>' , suggestion.value ,'</i>')); 1745 $('div#phraseanaloghelp p').show(); 1746 }, 1747 }); 1748 1749 $('div#phrasesanalogtitle input.form-text').off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){ 1750 var instance = $(this).data('autocomplete'); 1751 if (!instance) { return; } 1752 if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); } 1753 else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } } 1754 }); 1755 } 1756 1757 1758 var phrasesinputs = $("div#phrasestitle input.form-text") 1759 if(phrasesinputs.length) { 1760 phrasesinputs.autocomplete({ 1761 serviceUrl: '/' + Drupal.settings.languagevarq + '/ajax/lyricstranslategoogleautocomplete/autocomplete/getphrases', 1762 minChars: 2, 1763 deferRequestBy: 150, 1764 triggerSelectOnValidInput: false, 1765 preventBadQueries: false, 1766 onSelect: function(suggestion){ 1767 var form = $(this).closest("form"); 1768 var submit = form.find(':submit'); 1769 submit.addClass("ajax_loading_small"); 1770 form.submit(); 1771 } 1772 }); 1773 1774 phrasesinputs.off('keydown.autocomplete-fix').on('keydown.autocomplete-fix', function(e){ 1775 var instance = $(this).data('autocomplete'); 1776 if (!instance) { return; } 1777 if (e.which === 40) { if (!instance.visible && instance.currentValue) { instance.suggest(); } else { instance.moveDown(); } e.preventDefault(); e.stopImmediatePropagation(); } 1778 else if (e.which === 38) { if (instance.visible) { instance.moveUp(); e.preventDefault(); e.stopImmediatePropagation(); } } 1779 }); 1780 } 1781 1782 1783}); 1784;/*})'"*/;/*})'"*/ 1785/** 1786 * Ajax Autocomplete for jQuery, version 1.5.0 1787 * (c) 2017 Tomas Kirda 1788 * 1789 * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT
1789-style license. 1790 * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete 1791 */ 1792 1793// Expose plugin as an AMD module if AMD loader is present: 1794(function (factory) { 1795 "use strict"; 1796 if (typeof define === "function" && define.amd) { 1797 // AMD. Register as an anonymous module. 1798 define(["jquery"], factory); 1799 } else if (typeof exports === "object" && typeof require === "function") { 1800 // Browserify 1801 factory(require("jquery")); 1802 } else { 1803 // Browser globals 1804 factory(jQuery); 1805 } 1806})( 1807 /** 1808 * @param {jQuery} $ 1809 */ 1810 function ($) { 1811 "use strict"; 1812 1813 var utils = { 1814 escapeRegExChars: function (value) { 1815 return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&"); 1816 }, 1817 createNode: function (containerClass) { 1818 var div = document.createElement("div"); 1819 div.className = containerClass; 1820 div.style.position = "absolute"; 1821 div.style.display = "none"; 1822 return div; 1823 }, 1824 }, 1825 keys = { ESC: 27, TAB: 9, RETURN: 13, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 }, 1826 noop = $.noop; 1827 1828 function Autocomplete(el, options) { 1829 var that = this; 1830 1831 // Shared variables: 1832 that.element = el; 1833 that.el = $(el); 1834 that.suggestions = []; 1835 that.badQueries = []; 1836 that.selectedIndex = -1; 1837 that.currentValue = that.element.value; 1838 that.timeoutId = null; 1839 that.cachedResponse = {}; 1840 that.onChangeTimeout = null; 1841 that.onChange = null; 1842 that.isLocal = false;
1843 that.suggestionsContainer = null; 1844 that.noSuggestionsContainer = null; 1845 that.options = $.extend(true, {}, Autocomplete.defaults, options); 1846 that.classes = { 1847 selected: "autocomplete-selected", 1848 suggestion: "autocomplete-suggestion", 1849 }; 1850 that.hint = null; 1851 that.hintValue = ""; 1852 that.selection = null; 1853 1854 // Initialize and set options: 1855 that.initialize(); 1856 that.setOptions(options); 1857 } 1858 1859 Autocomplete.utils = utils; 1860 1861 $.Autocomplete = Autocomplete; 1862 1863 Autocomplete.defaults = { 1864 ajaxSettings: {}, 1865 autoSelectFirst: false, 1866 appendTo: "body", 1867 serviceUrl: null, 1868 lookup: null, 1869 onSelect: null, 1870 onHint: null, 1871 width: "auto", 1872 minChars: 1, 1873 maxHeight: 300, 1874 deferRequestBy: 0, 1875 params: {}, 1876 formatResult: _formatResult, 1877 formatGroup: _formatGroup, 1878 delimiter: null, 1879 zIndex: 9999, 1880 type: "GET", 1881 noCache: false, 1882 onSearchStart: noop, 1883 onSearchComplete: noop, 1884 onSearchError: noop, 1885 preserveInput: false, 1886 containerClass: "autocomplete-suggestions", 1887 tabDisabled: false, 1888 dataType: "text", 1889 currentRequest: null, 1890 triggerSelectOnValidInput: true, 1891 preventBadQueries: true, 1892 lookupFilter: _lookupFilter, 1893 paramName: "query", 1894 transformResult: _transformResult, 1895 showNoSuggestionNotice: false, 1896 noSuggestionNotice: "No results", 1897 orientation: "bottom", 1898 forceFixPosition: false, 1899 }; 1900 1901 function _lookupFilter(suggestion, originalQuery, queryLowerCase) { 1902 return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; 1903 } 1904 1905 function _transformResult(response) { 1906 return typeof response === "string" ? JSON.parse(response) : response; 1907 } 1908 1909 function _formatResult(suggestion, currentValue) { 1910 // Do not replace anything if the current value is empty 1911 if (!currentValue) { 1912 return suggestion.value; 1913 } 1914 1915 var pattern = "(" + utils.escapeRegExChars(currentValue) + ")"; 1916 1917 return suggestion.value 1918 .replace(new RegExp(pattern, "gi"), "<strong>$1</strong>") 1919 .replace(/&/g, "&") 1920 .replace(/</g, "<") 1921 .replace(/>/g, ">") 1922 .replace(/"/g, """) 1923 .replace(/<(\/?strong)>/g, "<$1>"); 1924 } 1925 1926 function _formatGroup(suggestion, category) { 1927 return '<div class="autocomplete-group">' + category + "</div>"; 1928 } 1929 1930 Autocomplete.prototype = { 1931 initialize: function () { 1932 var that = this, 1933 suggestionSelector = "." + that.classes.suggestion, 1934 selected = that.classes.selected, 1935 options = that.options, 1936 container; 1937 1938 that.element.setAttribute("autocomplete", "off"); 1939 1940 // html() deals with many types: htmlString or Element or Array or jQuery
1941 that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>') 1942 .html(this.options.noSuggestionNotice) 1943 .get(0); 1944 1945 that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass); 1946 1947 container = $(that.suggestionsContainer); 1948 1949 container.appendTo(options.appendTo || "body"); 1950 1951 // Only set width if it was provided: 1952 if (options.width !== "auto") { 1953 container.css("width", options.width); 1954 } 1955 1956 // Listen for mouse over event on suggestions list: 1957 container.on("mouseover.autocomplete", suggestionSelector, function () { 1958 that.activate($(this).data("index")); 1959 }); 1960 1961 // Deselect active element when mouse leaves suggestions container: 1962 container.on("mouseout.autocomplete", function () { 1963 that.selectedIndex = -1; 1964 container.children("." + selected).removeClass(selected); 1965 }); 1966 1967 // Listen for click event on suggestions list: 1968 container.on("click.autocomplete", suggestionSelector, function () { 1969 that.select($(this).data("index")); 1970 }); 1971 1972 container.on("click.autocomplete", function () { 1973 clearTimeout(that.blurTimeoutId); 1974 }); 1975 1976 that.fixPositionCapture = function () { 1977 if (that.visible) { 1978 that.fixPosition(); 1979 } 1980 }; 1981 1982 $(window).on("resize.autocomplete", that.fixPositionCapture); 1983 1984 that.el.on("keydown.autocomplete", function (e) { 1985 that.onKeyPress(e); 1986 }); 1987 that.el.on("keyup.autocomplete", function (e) { 1988 that.onKeyUp(e); 1989 }); 1990 that.el.on("blur.autocomplete", function () { 1991 that.onBlur(); 1992 }); 1993 that.el.on("focus.autocomplete", function () { 1994 that.onFocus(); 1995 }); 1996 that.el.on("change.autocomplete", function (e) { 1997 that.onKeyUp(e); 1998 }); 1999 that.el.on("input.autocomplete", function (e) { 2000 that.onKeyUp(e); 2001 }); 2002 }, 2003 2004 onFocus: function () { 2005 var that = this; 2006 2007 if (that.disabled) { 2008 return; 2009 } 2010 2011 that.fixPosition(); 2012 2013 if (that.el.val().length >= that.options.minChars) { 2014 that.onValueChange(); 2015 } 2016 }, 2017 2018 onBlur: function () { 2019 var that = this, 2020 options = that.options, 2021 value = that.el.val(), 2022 query = that.getQuery(value); 2023 2024 // If user clicked on a suggestion, hide() will 2025 // be canceled, otherwise close suggestions 2026 that.blurTimeoutId = setTimeout(function () { 2027 that.hide(); 2028 2029 if (that.selection && that.currentValue !== query) { 2030 (options.onInvalidateSelection || $.noop).call(that.element); 2031 } 2032 }, 200); 2033 }, 2034 2035 abortAjax: function () { 2036 var that = this; 2037 if (that.currentRequest) { 2038 that.currentRequest.abort(); 2039 that.currentRequest = null; 2040 } 2041 }, 2042 2043 setOptions: function (suppliedOptions) { 2044 var that = this, 2045 options = $.extend({}, that.options, suppliedOptions); 2046 2047 that.isLocal = Array.isArray(options.lookup); 2048 2049 if (that.isLocal) { 2050 options.lookup = that.verifySuggestionsFormat(options.lookup); 2051 } 2052 2053 options.orientation = that.validateOrientation(options.orientation, "bottom"); 2054 2055 // Adjust height, width and z-index: 2056 $(that.suggestionsContainer).css({ 2057 "max-height": options.maxHeight + "px", 2058 width: options.width + "px", 2059 "z-index": options.zIndex, 2060 }); 2061 2062 this.options = options; 2063 }, 2064 2065 clearCache: function () { 2066 this.cachedResponse = {}; 2067 this.badQueries = []; 2068 }, 2069 2070 clear: function () { 2071 this.clearCache(); 2072 this.currentValue = ""; 2073 this.suggestions = []; 2074 }, 2075 2076 disable: function () { 2077 var that = this; 2078 that.disabled = true; 2079 clearTimeout(that.onChangeTimeout); 2080 that.abortAjax(); 2081 }, 2082 2083 enable: function () { 2084 this.disabled = false;
2085 }, 2086 2087 fixPosition: function () { 2088 // Use only when container has already its content 2089 2090 var that = this, 2091 $container = $(that.suggestionsContainer), 2092 containerParent = $container.parent().get(0); 2093 // Fix position automatically when appended to body. 2094 // In other cases force parameter must be given. 2095 if (containerParent !== document.body && !that.options.forceFixPosition) { 2096 return; 2097 } 2098 2099 // Choose orientation 2100 var orientation = that.options.orientation, 2101 containerHeight = $container.outerHeight(), 2102 height = that.el.outerHeight(), 2103 offset = that.el.offset(), 2104 styles = { top: offset.top, left: offset.left }; 2105 2106 if (orientation === "auto") { 2107 var viewPortHeight = $(window).height(), 2108 scrollTop = $(window).scrollTop(), 2109 topOverflow = -scrollTop + offset.top - containerHeight, 2110 bottomOverflow = 2111 scrollTop + viewPortHeight - (offset.top + height + containerHeight); 2112 2113 orientation = 2114 Math.max(topOverflow, bottomOverflow) === topOverflow ? "top" : "bottom"; 2115 } 2116 2117 if (orientation === "top") { 2118 styles.top += -containerHeight; 2119 } else { 2120 styles.top += height; 2121 } 2122 2123 // If container is not positioned to body, 2124 // correct its position using offset parent offset 2125 if (containerParent !== document.body) { 2126 var opacity = $container.css("opacity"), 2127 parentOffsetDiff; 2128 2129 if (!that.visible) { 2130 $container.css("opacity", 0).show(); 2131 } 2132 2133 parentOffsetDiff = $container.offsetParent().offset(); 2134 styles.top -= parentOffsetDiff.top; 2135 styles.top += containerParent.scrollTop; 2136 styles.left -= parentOffsetDiff.left; 2137 2138 if (!that.visible) { 2139 $container.css("opacity", opacity).hide(); 2140 } 2141 } 2142 2143 if (that.options.width === "auto") { 2144 styles.width = that.el.outerWidth() + "px"; 2145 } 2146 2147 $container.css(styles); 2148 }, 2149 2150 isCursorAtEnd: function () { 2151 var that = this, 2152 valLength = that.el.val().length, 2153 selectionStart = that.element.selectionStart, 2154 range; 2155 2156 if (typeof selectionStart === "number") { 2157 return selectionStart === valLength; 2158 } 2159 if (document.selection) { 2160 range = document.selection.createRange(); 2161 range.moveStart("character", -valLength); 2162 return valLength === range.text.length; 2163 } 2164 return true; 2165 }, 2166 2167 onKeyPress: function (e) { 2168 var that = this; 2169 2170 // If suggestions are hidden and user presses arrow down, display suggestions: 2171 if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) { 2172 that.suggest(); 2173 return; 2174 } 2175 2176 if (that.disabled || !that.visible) { 2177 return; 2178 } 2179 2180 switch (e.which) { 2181 case keys.ESC: 2182 that.el.val(that.currentValue); 2183 that.hide(); 2184 break; 2185 case keys.RIGHT: 2186 if (that.hint && that.options.onHint && that.isCursorAtEnd()) { 2187 that.selectHint(); 2188 break; 2189 } 2190 return; 2191 case keys.TAB: 2192 if (that.hint && that.options.onHint) { 2193 that.selectHint(); 2194 return; 2195 } 2196 if (that.selectedIndex === -1) { 2197 that.hide(); 2198 return; 2199 } 2200 that.select(that.selectedIndex); 2201 if (that.options.tabDisabled === false) { 2202 return; 2203 } 2204 break; 2205 case keys.RETURN: 2206 if (that.selectedIndex === -1) { 2207 that.hide(); 2208 return; 2209 } 2210 that.select(that.selectedIndex); 2211 break; 2212 case keys.UP: 2213 that.moveUp(); 2214 break; 2215 case keys.DOWN: 2216 that.moveDown(); 2217 break; 2218 default: 2219 return; 2220 } 2221 2222 // Cancel event if function did not return: 2223 e.stopImmediatePropagation(); 2224 e.preventDefault(); 2225 }, 2226 2227 onKeyUp: function (e) { 2228 var that = this; 2229 2230 if (that.disabled) { 2231 return; 2232 } 2233 2234 switch (e.which) { 2235 case keys.UP: 2236 case keys.DOWN: 2237 return; 2238 } 2239 2240 clearTimeout(that.onChangeTimeout); 2241 2242 if (that.currentValue !== that.el.val()) { 2243 that.findBestHint(); 2244 if (that.options.deferRequestBy > 0) { 2245 // Defer lookup in case when value changes very quickly: 2246 that.onChangeTimeout = setTimeout(function () { 2247 that.onValueChange(); 2248 }, that.options.deferRequestBy); 2249 } else { 2250 that.onValueChange(); 2251 } 2252 } 2253 }, 2254 2255 onValueChange: function () { 2256 if (this.ignoreValueChange) { 2257 this.ignoreValueChange = false;
2258 return; 2259 } 2260 2261 var that = this, 2262 options = that.options, 2263 value = that.el.val(), 2264 query = that.getQuery(value); 2265 2266 if (that.selection && that.currentValue !== query) { 2267 that.selection = null; 2268 (options.onInvalidateSelection || $.noop).call(that.element); 2269 } 2270 2271 clearTimeout(that.onChangeTimeout); 2272 that.currentValue = value; 2273 that.selectedIndex = -1; 2274 2275 // Check existing suggestion for the match before proceeding: 2276 if (options.triggerSelectOnValidInput && that.isExactMatch(query)) { 2277 that.select(0); 2278 return; 2279 } 2280 2281 if (query.length < options.minChars) { 2282 that.hide(); 2283 } else { 2284 that.getSuggestions(query); 2285 } 2286 }, 2287 2288 isExactMatch: function (query) { 2289 var suggestions = this.suggestions; 2290 2291 return ( 2292 suggestions.length === 1 && 2293 suggestions[0].value.toLowerCase() === query.toLowerCase() 2294 ); 2295 }, 2296 2297 getQuery: function (value) { 2298 var delimiter = this.options.delimiter, 2299 parts; 2300 2301 if (!delimiter) { 2302 return value; 2303 } 2304 parts = value.split(delimiter); 2305 return parts[parts.length - 1].trim(); 2306 }, 2307 2308 getSuggestionsLocal: function (query) { 2309 var that = this, 2310 options = that.options, 2311 queryLowerCase = query.toLowerCase(), 2312 filter = options.lookupFilter, 2313 limit = parseInt(options.lookupLimit, 10), 2314 data; 2315 2316 data = { 2317 suggestions: $.grep(options.lookup, function (suggestion) { 2318 return filter(suggestion, query, queryLowerCase); 2319 }), 2320 }; 2321 2322 if (limit && data.suggestions.length > limit) { 2323 data.suggestions = data.suggestions.slice(0, limit); 2324 } 2325 2326 return data; 2327 }, 2328 2329 getSuggestions: function (q) { 2330 var response, 2331 that = this, 2332 options = that.options, 2333 serviceUrl = options.serviceUrl, 2334 params, 2335 cacheKey, 2336 ajaxSettings; 2337 2338 options.params[options.paramName] = q; 2339 2340 if (options.onSearchStart.call(that.element, options.params) === false) { 2341 return; 2342 } 2343 2344 params = options.ignoreParams ? null : options.params; 2345 2346 if (typeof options.lookup === "function") { 2347 options.lookup(q, function (data) { 2348 that.suggestions = data.suggestions; 2349 that.suggest(); 2350 options.onSearchComplete.call(that.element, q, data.suggestions); 2351 }); 2352 return; 2353 } 2354 2355 if (that.isLocal) { 2356 response = that.getSuggestionsLocal(q); 2357 } else { 2358 if (typeof serviceUrl === "function") { 2359 serviceUrl = serviceUrl.call(that.element, q); 2360 } 2361 cacheKey = serviceUrl + "?" + $.param(params || {}); 2362 response = that.cachedResponse[cacheKey]; 2363 } 2364 2365 if (response && Array.isArray(response.suggestions)) { 2366 that.suggestions = response.suggestions; 2367 that.suggest(); 2368 options.onSearchComplete.call(that.element, q, response.suggestions); 2369 } else if (!that.isBadQuery(q)) { 2370 that.abortAjax(); 2371 2372 ajaxSettings = { 2373 url: serviceUrl, 2374 data: params, 2375 type: options.type, 2376 dataType: options.dataType, 2377 }; 2378 2379 $.extend(ajaxSettings, options.ajaxSettings); 2380 2381 that.currentRequest = $.ajax(ajaxSettings) 2382 .done(function (data) { 2383 var result; 2384 that.currentRequest = null; 2385 result = options.transformResult(data, q); 2386 that.processResponse(result, q, cacheKey); 2387 options.onSearchComplete.call(that.element, q, result.suggestions); 2388 }) 2389 .fail(function (jqXHR, textStatus, errorThrown) { 2390 options.onSearchError.call( 2391 that.element, 2392 q, 2393 jqXHR, 2394 textStatus, 2395 errorThrown 2396 ); 2397 }); 2398 } else { 2399 options.onSearchComplete.call(that.element, q, []); 2400 } 2401 }, 2402 2403 isBadQuery: function (q) { 2404 if (!this.options.preventBadQueries) { 2405 return false;
2406 } 2407 2408 var badQueries = this.badQueries, 2409 i = badQueries.length; 2410 2411 while (i--) { 2412 if (q.indexOf(badQueries[i]) === 0) { 2413 return true; 2414 } 2415 } 2416 2417 return false; 2418 }, 2419 2420 hide: function () { 2421 var that = this, 2422 container = $(that.suggestionsContainer); 2423 2424 if (typeof that.options.onHide === "function" && that.visible) { 2425 that.options.onHide.call(that.element, container); 2426 } 2427 2428 that.visible = false; 2429 that.selectedIndex = -1; 2430 clearTimeout(that.onChangeTimeout); 2431 $(that.suggestionsContainer).hide(); 2432 that.onHint(null); 2433 }, 2434 2435 suggest: function () { 2436 if (!this.suggestions.length) { 2437 if (this.options.showNoSuggestionNotice) { 2438 this.noSuggestions(); 2439 } else { 2440 this.hide(); 2441 } 2442 return; 2443 } 2444 2445 var that = this, 2446 options = that.options, 2447 groupBy = options.groupBy, 2448 formatResult = options.formatResult, 2449 value = that.getQuery(that.currentValue), 2450 className = that.classes.suggestion, 2451 classSelected = that.classes.selected, 2452 container = $(that.suggestionsContainer), 2453 noSuggestionsContainer = $(that.noSuggestionsContainer), 2454 beforeRender = options.beforeRender, 2455 html = "", 2456 category, 2457 formatGroup = function (suggestion) { 2458 var currentCategory = suggestion.data[groupBy]; 2459 2460 if (category === currentCategory) { 2461 return ""; 2462 } 2463 2464 category = currentCategory; 2465 2466 return options.formatGroup(suggestion, category); 2467 }; 2468 2469 if (options.triggerSelectOnValidInput && that.isExactMatch(value)) { 2470 that.select(0); 2471 return; 2472 } 2473 2474 // Build suggestions inner HTML: 2475 $.each(that.suggestions, function (i, suggestion) { 2476 if (groupBy) { 2477 html += formatGroup(suggestion, value, i); 2478 } 2479 2480 html += 2481 '<div class="' + 2482 className + 2483 '" data-index="' + 2484 i + 2485 '">' + 2486 formatResult(suggestion, value, i) + 2487 "</div>"; 2488 }); 2489 2490 this.adjustContainerWidth(); 2491 2492 noSuggestionsContainer.detach(); 2493 container.html(html); 2494 2495 if (typeof beforeRender === "function") { 2496 beforeRender.call(that.element, container, that.suggestions); 2497 } 2498 2499 that.fixPosition(); 2500 container.show(); 2501 2502 // Select first value by default: 2503 if (options.autoSelectFirst) { 2504 that.selectedIndex = 0; 2505 container.scrollTop(0); 2506 container 2507 .children("." + className) 2508 .first() 2509 .addClass(classSelected); 2510 } 2511 2512 that.visible = true; 2513 that.findBestHint(); 2514 }, 2515 2516 noSuggestions: function () { 2517 var that = this, 2518 beforeRender = that.options.beforeRender, 2519 container = $(that.suggestionsContainer), 2520 noSuggestionsContainer = $(that.noSuggestionsContainer); 2521 2522 this.adjustContainerWidth(); 2523 2524 // Some explicit steps. Be careful here as it easy to get 2525 // noSuggestionsContainer removed from DOM if not detached properly. 2526 noSuggestionsContainer.detach(); 2527 2528 // clean suggestions if any 2529 container.empty(); 2530 container.append(noSuggestionsContainer); 2531 2532 if (typeof beforeRender === "function") { 2533 beforeRender.call(that.element, container, that.suggestions); 2534 } 2535 2536 that.fixPosition(); 2537 2538 container.show(); 2539 that.visible = true; 2540 }, 2541 2542 adjustContainerWidth: function () { 2543 var that = this, 2544 options = that.options, 2545 width, 2546 container = $(that.suggestionsContainer); 2547
2548 // If width is auto, adjust width before displaying suggestions, 2549 // because if instance was created before input had width, it will be zero. 2550 // Also it adjusts if input width has changed. 2551 if (options.width === "auto") { 2552 width = that.el.outerWidth(); 2553 container.css("width", width > 0 ? width : 300); 2554 } else if (options.width === "flex") { 2555 // Trust the source! Unset the width property so it will be the max length 2556 // the containing elements. 2557 container.css("width", ""); 2558 } 2559 }, 2560 2561 findBestHint: function () { 2562 var that = this, 2563 value = that.el.val().toLowerCase(), 2564 bestMatch = null; 2565 2566 if (!value) { 2567 return; 2568 } 2569 2570 $.each(that.suggestions, function (i, suggestion) { 2571 var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0; 2572 if (foundMatch) { 2573 bestMatch = suggestion; 2574 } 2575 return !foundMatch; 2576 }); 2577 2578 that.onHint(bestMatch); 2579 }, 2580 2581 onHint: function (suggestion) { 2582 var that = this, 2583 onHintCallback = that.options.onHint, 2584 hintValue = ""; 2585 2586 if (suggestion) { 2587 hintValue = 2588 that.currentValue + suggestion.value.substr(that.currentValue.length); 2589 } 2590 if (that.hintValue !== hintValue) { 2591 that.hintValue = hintValue; 2592 that.hint = suggestion; 2593 if (typeof onHintCallback === "function") { 2594 onHintCallback.call(that.element, hintValue); 2595 } 2596 } 2597 }, 2598 2599 verifySuggestionsFormat: function (suggestions) { 2600 // If suggestions is string array, convert them to supported format: 2601 if (suggestions.length && typeof suggestions[0] === "string") { 2602 return $.map(suggestions, function (value) { 2603 return { value: value, data: null }; 2604 }); 2605 } 2606 2607 return suggestions; 2608 }, 2609 2610 validateOrientation: function (orientation, fallback) { 2611 orientation = (orientation || "").trim().toLowerCase(); 2612 2613 if ($.inArray(orientation, ["auto", "bottom", "top"]) === -1) { 2614 orientation = fallback; 2615 } 2616 2617 return orientation; 2618 }, 2619 2620 processResponse: function (result, originalQuery, cacheKey) { 2621 var that = this, 2622 options = that.options; 2623 2624 result.suggestions = that.verifySuggestionsFormat(result.suggestions); 2625 2626 // Cache results if cache is not disabled: 2627 if (!options.noCache) { 2628 that.cachedResponse[cacheKey] = result; 2629 if (options.preventBadQueries && !result.suggestions.length) { 2630 that.badQueries.push(originalQuery); 2631 } 2632 } 2633 2634 // Return if originalQuery is not matching current query: 2635 if (originalQuery !== that.getQuery(that.currentValue)) { 2636 return; 2637 } 2638 2639 that.suggestions = result.suggestions; 2640 that.suggest(); 2641 }, 2642 2643 activate: function (index) { 2644 var that = this, 2645 activeItem, 2646 selected = that.classes.selected, 2647 container = $(that.suggestionsContainer), 2648 children = container.find("." + that.classes.suggestion); 2649 2650 container.find("." + selected).removeClass(selected); 2651 2652 that.selectedIndex = index; 2653 2654 if (that.selectedIndex !== -1 && children.length > that.selectedIndex) { 2655 activeItem = children.get(that.selectedIndex); 2656 $(activeItem).addClass(selected); 2657 return activeItem; 2658 } 2659 2660 return null; 2661 }, 2662 2663 selectHint: function () { 2664 var that = this, 2665 i = $.inArray(that.hint, that.suggestions); 2666 2667 that.select(i); 2668 }, 2669 2670 select: function (i) { 2671 var that = this; 2672 that.hide(); 2673 that.onSelect(i); 2674 }, 2675 2676 moveUp: function () { 2677 var that = this; 2678 2679 if (that.selectedIndex === -1) { 2680 return; 2681 } 2682 2683 if (that.selectedIndex === 0) { 2684 $(that.suggestionsContainer) 2685 .children("." + that.classes.suggestion) 2686 .first() 2687 .removeClass(that.classes.selected); 2688 that.selectedIndex = -1; 2689 that.ignoreValueChange = false;
2690 that.el.val(that.currentValue); 2691 that.findBestHint(); 2692 return; 2693 } 2694 2695 that.adjustScroll(that.selectedIndex - 1); 2696 }, 2697 2698 moveDown: function () { 2699 var that = this; 2700 2701 if (that.selectedIndex === that.suggestions.length - 1) { 2702 return; 2703 } 2704 2705 that.adjustScroll(that.selectedIndex + 1); 2706 }, 2707 2708 adjustScroll: function (index) { 2709 var that = this, 2710 activeItem = that.activate(index); 2711 2712 if (!activeItem) { 2713 return; 2714 } 2715 2716 var offsetTop, 2717 upperBound, 2718 lowerBound, 2719 heightDelta = $(activeItem).outerHeight(); 2720 2721 offsetTop = activeItem.offsetTop; 2722 upperBound = $(that.suggestionsContainer).scrollTop(); 2723 lowerBound = upperBound + that.options.maxHeight - heightDelta; 2724 2725 if (offsetTop < upperBound) { 2726 $(that.suggestionsContainer).scrollTop(offsetTop); 2727 } else if (offsetTop > lowerBound) { 2728 $(that.suggestionsContainer).scrollTop( 2729 offsetTop - that.options.maxHeight + heightDelta 2730 ); 2731 } 2732 2733 if (!that.options.preserveInput) { 2734 // During onBlur event, browser will trigger "change" event, 2735 // because value has changed, to avoid side effect ignore, 2736 // that event, so that correct suggestion can be selected 2737 // when clicking on suggestion with a mouse 2738 that.ignoreValueChange = true; 2739 that.el.val(that.getValue(that.suggestions[index].value)); 2740 } 2741 2742 that.onHint(null); 2743 }, 2744 2745 onSelect: function (index) { 2746 var that = this, 2747 onSelectCallback = that.options.onSelect, 2748 suggestion = that.suggestions[index]; 2749 2750 that.currentValue = that.getValue(suggestion.value); 2751 2752 if (that.currentValue !== that.el.val() && !that.options.preserveInput) { 2753 that.el.val(that.currentValue); 2754 } 2755 2756 that.onHint(null); 2757 that.suggestions = []; 2758 that.selection = suggestion; 2759 2760 if (typeof onSelectCallback === "function") { 2761 onSelectCallback.call(that.element, suggestion); 2762 } 2763 }, 2764 2765 getValue: function (value) { 2766 var that = this, 2767 delimiter = that.options.delimiter, 2768 currentValue, 2769 parts; 2770 2771 if (!delimiter) { 2772 return value; 2773 } 2774 2775 currentValue = that.currentValue; 2776 parts = currentValue.split(delimiter); 2777 2778 if (parts.length === 1) { 2779 return value; 2780 } 2781 2782 return ( 2783 currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + 2784 value 2785 ); 2786 }, 2787 2788 dispose: function () { 2789 var that = this; 2790 that.el.off(".autocomplete").removeData("autocomplete"); 2791 $(window).off("resize.autocomplete", that.fixPositionCapture); 2792 $(that.suggestionsContainer).remove(); 2793 }, 2794 }; 2795 2796 // Create chainable jQuery plugin: 2797 $.fn.devbridgeAutocomplete = function (options, args) { 2798 var dataKey = "autocomplete"; 2799 // If function invoked without argument return 2800 // instance of the first matched element: 2801 if (!arguments.length) { 2802 return this.first().data(dataKey); 2803 } 2804 2805 return this.each(function () { 2806 var inputElement = $(this), 2807 instance = inputElement.data(dataKey); 2808 2809 if (typeof options === "string") { 2810 if (instance && typeof instance[options] === "function") { 2811 instance[options](args); 2812 } 2813 } else { 2814 // If instance already exists, destroy it: 2815 if (instance && instance.dispose) { 2816 instance.dispose(); 2817 } 2818 instance = new Autocomplete(this, options); 2819 inputElement.data(dataKey, instance); 2820 } 2821 }); 2822 }; 2823 2824 // Don't overwrite if it already exists 2825 if (!$.fn.autocomplete) { 2826 $.fn.autocomplete = $.fn.devbridgeAutocomplete; 2827 } 2828 } 2829); 2830;/*})'"*/;/*})'"*/ 2831(function ($) { 2832 2833Drupal.behaviors.quote = { 2834 attach: function(context, settings) { 2835 var level = Drupal.settings.quote_nest - 1; 2836 if (level >= 0) { 2837 var top = $('blockquote.quote-nest-1', context); 2838 $('blockquote.quote-msg:eq(' + level + ')', top) 2839 .hide() 2840 .after('<div class="quote-snip">' + Drupal.t('<a href="#">[snip]</a>') + '</div>') 2841 .next('.quote-snip') 2842 .children('a') 2843 .click(function(e) { 2844 $(this).parent().siblings('.quote-msg').toggle(); 2845 e.preventDefault(); 2846 }); 2847 } 2848 } 2849}; 2850 2851}(jQuery)); 2852;/*})'"*/;/*})'"*/ 2853(function ($) { 2854 2855 Drupal.behaviors.captcha = { 2856 attach: function (context) { 2857 2858 // Turn off autocompletion for the CAPTCHA response field. 2859 // We do it here with JavaScript (instead of directly in the markup) 2860 // because this autocomplete attribute is not standard and 2861 // it would break (X)HTML compliance. 2862 $("#edit-captcha-response").attr("autocomplete", "off"); 2863 2864 } 2865 }; 2866 2867 Drupal.behaviors.captchaAdmin = { 2868 attach: function (context) {
2869 // Add onclick handler to checkbox for adding a CAPTCHA description 2870 // so that the textfields for the CAPTCHA description are hidden 2871 // when no description should be added. 2872 // @todo: div.form-item-captcha-description depends on theming, maybe 2873 // it's better to add our own wrapper with id (instead of a class). 2874 $("#edit-captcha-add-captcha-description").click(function() { 2875 if ($("#edit-captcha-add-captcha-description").is(":checked")) { 2876 // Show the CAPTCHA description textfield(s). 2877 $("div.form-item-captcha-description").show('slow'); 2878 } 2879 else { 2880 // Hide the CAPTCHA description textfield(s). 2881 $("div.form-item-captcha-description").hide('slow'); 2882 } 2883 }); 2884 // Hide the CAPTCHA description textfields if option is disabled on page load. 2885 if (!$("#edit-captcha-add-captcha-description").is(":checked")) { 2886 $("div.form-item-captcha-description").hide(); 2887 } 2888 } 2889 2890 }; 2891 2892})(jQuery); 2893;/*})'"*/;/*})'"*/ 2894(function ($) { 2895 2896Drupal.behaviors.textarea = { 2897 attach: function (context, settings) { 2898 $('.form-textarea-wrapper.resizable', context).once('textarea', function () { 2899 var staticOffset = null; 2900 var textarea = $(this).addClass('resizable-textarea').find('textarea'); 2901 var grippie = $('<div class="grippie"></div>').mousedown(startDrag); 2902 2903 grippie.insertAfter(textarea); 2904 2905 function startDrag(e) { 2906 staticOffset = textarea.height() - e.pageY; 2907 textarea.css('opacity', 0.25); 2908 $(document).mousemove(performDrag).mouseup(endDrag); 2909 return false; 2910 } 2911 2912 function performDrag(e) { 2913 textarea.height(Math.max(32, staticOffset + e.pageY) + 'px'); 2914 return false; 2915 } 2916 2917 function endDrag(e) { 2918 $(document).unbind('mousemove', performDrag).unbind('mouseup', endDrag); 2919 textarea.css('opacity', 1); 2920 } 2921 }); 2922 } 2923}; 2924 2925})(jQuery); 2926;/*})'"*/;/*})'"*/ 2927(function ($) { 2928 2929/** 2930 * A progressbar object. Initialized with the given id. Must be inserted into 2931 * the DOM afterwards through progressBar.element. 2932 * 2933 * method is the function which will perform the HTTP request to get the 2934 * progress bar state. Either "GET" or "POST". 2935 * 2936 * e.g. pb = new progressBar('myProgressBar'); 2937 * some_element.appendChild(pb.element); 2938 */ 2939Drupal.progressBar = function (id, updateCallback, method, errorCallback) { 2940 var pb = this; 2941 this.id = id; 2942 this.method = method || 'GET'; 2943 this.updateCallback = updateCallback; 2944 this.errorCallback = errorCallback; 2945 2946 // The WAI-ARIA setting aria-live="polite" will announce changes after users 2947 // have completed their current activity and not interrupt the screen reader. 2948 this.element = $('<div class="progress" aria-live="polite"></div>').attr('id', id); 2949 this.element.html('<div class="bar"><div class="filled"></div></div>' + 2950 '<div class="percentage"></div>' + 2951 '<div class="message"> </div>'); 2952}; 2953 2954/** 2955 * Set the percentage and status message for the progressbar. 2956 */ 2957Drupal.progressBar.prototype.setProgress = function (percentage, message) { 2958 if (percentage >= 0 && percentage <= 100) { 2959 $('div.filled', this.element).css('width', percentage + '%'); 2960 $('div.percentage', this.element).html(percentage + '%'); 2961 } 2962 $('div.message', this.element).html(message); 2963 if (this.updateCallback) { 2964 this.updateCallback(percentage, message, this); 2965 } 2966}; 2967 2968/** 2969 * Start monitoring progress via Ajax. 2970 */ 2971Drupal.progressBar.prototype.startMonitoring = function (uri, delay) { 2972 this.delay = delay; 2973 this.uri = uri; 2974 this.sendPing(); 2975}; 2976 2977/** 2978 * Stop monitoring progress via Ajax. 2979 */ 2980Drupal.progressBar.prototype.stopMonitoring = function () { 2981 clearTimeout(this.timer); 2982 // This allows monitoring to be stopped from within the callback. 2983 this.uri = null; 2984}; 2985 2986/** 2987 * Request progress data from server. 2988 */ 2989Drupal.progressBar.prototype.sendPing = function () { 2990 if (this.timer) { 2991 clearTimeout(this.timer); 2992 } 2993 if (this.uri) { 2994 var pb = this; 2995 // When doing a post request, you need non-null data. Otherwise a 2996 // HTTP 411 or HTTP 406 (with Apache mod_security) error may result. 2997 $.ajax({ 2998 type: this.method, 2999 url: this.uri, 3000 data: '', 3001 dataType: 'json', 3002 success: function (progress) { 3003 // Display errors. 3004 if (progress.status == 0) { 3005 pb.displayError(progress.data); 3006 return; 3007 } 3008 // Update display. 3009 pb.setProgress(progress.percentage, progress.message); 3010 // Schedule next timer.
3011 pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay); 3012 }, 3013 error: function (xmlhttp) { 3014 pb.displayError(Drupal.ajaxError(xmlhttp, pb.uri)); 3015 } 3016 }); 3017 } 3018}; 3019 3020/** 3021 * Display errors on the page. 3022 */ 3023Drupal.progressBar.prototype.displayError = function (string) { 3024 var error = $('<div class="messages error"></div>').html(string); 3025 $(this.element).before(error).hide(); 3026 3027 if (this.errorCallback) { 3028 this.errorCallback(this); 3029 } 3030}; 3031 3032})(jQuery); 3033;/*})'"*/;/*})'"*/ 3034/*! 3035 Autosize 3.0.17 3036 license: MIT 3037 http://www.jacklmoore.com/autosize 3038*/ 3039(function (global, factory) { 3040 if (typeof define === 'function' && define.amd) { 3041 define(['exports', 'module'], factory); 3042 } else if (typeof exports !== 'undefined' && typeof module !== 'undefined') { 3043 factory(exports, module); 3044 } else { 3045 var mod = { 3046 exports: {} 3047 }; 3048 factory(mod.exports, mod); 3049 global.autosize = mod.exports; 3050 } 3051})(this, function (exports, module) { 3052 'use strict'; 3053 3054 var set = typeof Set === 'function' ? new Set() : (function () { 3055 var list = []; 3056 3057 return { 3058 has: function has(key) { 3059 return Boolean(list.indexOf(key) > -1); 3060 }, 3061 add: function add(key) { 3062 list.push(key); 3063 }, 3064 'delete': function _delete(key) { 3065 list.splice(list.indexOf(key), 1); 3066 } }; 3067 })(); 3068 3069 var createEvent = function createEvent(name) { 3070 return new Event(name); 3071 }; 3072 try { 3073 new Event('test'); 3074 } catch (e) { 3075 // IE does not support `new Event()` 3076 createEvent = function (name) { 3077 var evt = document.createEvent('Event'); 3078 evt.initEvent(name, true, false); 3079 return evt; 3080 }; 3081 } 3082 3083 function assign(ta) { 3084 if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || set.has(ta)) return; 3085 3086 var heightOffset = null; 3087 var clientWidth = ta.clientWidth; 3088 var cachedHeight = null; 3089 3090 function init() { 3091 var style = window.getComputedStyle(ta, null); 3092 3093 if (style.resize === 'vertical') { 3094 ta.style.resize = 'none'; 3095 } else if (style.resize === 'both') { 3096 ta.style.resize = 'horizontal'; 3097 } 3098 3099 if (style.boxSizing === 'content-box') { 3100 heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)); 3101 } else { 3102 heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth); 3103 } 3104 // Fix when a textarea is not on document body and heightOffset is Not a Number 3105 if (isNaN(heightOffset)) { 3106 heightOffset = 0; 3107 } 3108 3109 update(); 3110 } 3111 3112 function changeOverflow(value) { 3113 { 3114 // Chrome/Safari-specific fix: 3115 // When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space 3116 // made available by removing the scrollbar. The following forces the necessary text reflow. 3117 var width = ta.style.width; 3118 ta.style.width = '0px'; 3119 // Force reflow: 3120 /* jshint ignore:start */ 3121 ta.offsetWidth; 3122 /* jshint ignore:end */ 3123 ta.style.width = width; 3124 } 3125 3126 ta.style.overflowY = value; 3127 3128 resize(); 3129 } 3130 3131 function getParentOverflows(el) { 3132 var arr = []; 3133 3134 while (el && el.parentNode && el.parentNode instanceof Element) { 3135 if (el.parentNode.scrollTop) { 3136 arr.push({ 3137 node: el.parentNode, 3138 scrollTop: el.parentNode.scrollTop }); 3139 } 3140 el = el.parentNode; 3141 } 3142 3143 return arr; 3144 } 3145 3146 function resize() { 3147 var originalHeight = ta.style.height; 3148 var overflows = getParentOverflows(ta); 3149 var docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240) 3150 3151 ta.style.height = 'auto'; 3152 3153 var endHeight = ta.scrollHeight + heightOffset + 25; 3154 3155 if (ta.scrollHeight === 0) { 3156 // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM. 3157 ta.style.height = originalHeight; 3158 return; 3159 } 3160 3161 ta.style.height = endHeight + 'px'; 3162 3163 // used to check if an update is actually necessary on window.resize 3164 clientWidth = ta.clientWidth; 3165 3166 // prevents scroll-position jumping 3167 overflows.forEach(function (el) { 3168 el.node.scrollTop = el.scrollTop; 3169 }); 3170 3171 if (docTop) { 3172 document.documentElement.scrollTop = docTop; 3173 } 3174 } 3175
3176 function update() { 3177 resize(); 3178 3179 var computed = window.getComputedStyle(ta, null); 3180 var computedHeight = Math.round(parseFloat(computed.height)); 3181 var styleHeight = Math.round(parseFloat(ta.style.height)); 3182 3183 // The computed height not matching the height set via resize indicates that 3184 // the max-height has been exceeded, in which case the overflow should be set to visible. 3185 if (computedHeight !== styleHeight) { 3186 if (computed.overflowY !== 'visible') { 3187 changeOverflow('visible'); 3188 } 3189 } else { 3190 // Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands. 3191 if (computed.overflowY !== 'hidden') { 3192 changeOverflow('hidden'); 3193 } 3194 } 3195 3196 if (cachedHeight !== computedHeight) { 3197 cachedHeight = computedHeight; 3198 var evt = createEvent('autosize:resized'); 3199 ta.dispatchEvent(evt); 3200 } 3201 } 3202 3203 var pageResize = function pageResize() { 3204 if (ta.clientWidth !== clientWidth) { 3205 update(); 3206 } 3207 }; 3208 3209 var destroy = (function (style) { 3210 window.removeEventListener('resize', pageResize, false); 3211 ta.removeEventListener('input', update, false); 3212 ta.removeEventListener('keyup', update, false); 3213 ta.removeEventListener('autosize:destroy', destroy, false); 3214 ta.removeEventListener('autosize:update', update, false); 3215 set['delete'](ta); 3216 3217 Object.keys(style).forEach(function (key) { 3218 ta.style[key] = style[key]; 3219 }); 3220 }).bind(ta, { 3221 height: ta.style.height, 3222 resize: ta.style.resize, 3223 overflowY: ta.style.overflowY, 3224// overflowX: ta.style.overflowX, 3225// wordWrap: ta.style.wordWrap 3226 }); 3227 3228 ta.addEventListener('autosize:destroy', destroy, false); 3229 3230 // IE9 does not fire onpropertychange or oninput for deletions, 3231 // so binding to onkeyup to catch most of those events. 3232 // There is no way that I know of to detect something like 'cut' in IE9. 3233 if ('onpropertychange' in ta && 'oninput' in ta) { 3234 ta.addEventListener('keyup', update, false); 3235 } 3236 3237 window.addEventListener('resize', pageResize, false); 3238 ta.addEventListener('input', update, false); 3239 ta.addEventListener('autosize:update', update, false); 3240 set.add(ta); 3241// ta.style.overflowX = 'hidden'; 3242// ta.style.wordWrap = 'break-word'; 3243 3244 init(); 3245 } 3246 3247 function destroy(ta) { 3248 if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return; 3249 var evt = createEvent('autosize:destroy'); 3250 ta.dispatchEvent(evt); 3251 } 3252 3253 function update(ta) { 3254 if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return; 3255 var evt = createEvent('autosize:update'); 3256 ta.dispatchEvent(evt); 3257 } 3258 3259 var autosize = null; 3260 3261 // Do nothing in Node.js environment and IE8 (or lower) 3262 if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') { 3263 autosize = function (el) { 3264 return el; 3265 }; 3266 autosize.destroy = function (el) { 3267 return el; 3268 }; 3269 autosize.update = function (el) { 3270 return el; 3271 }; 3272 } else { 3273 autosize = function (el, options) { 3274 if (el) { 3275 Array.prototype.forEach.call(el.length ? el : [el], function (x) { 3276 return assign(x, options); 3277 }); 3278 } 3279 return el; 3280 }; 3281 autosize.destroy = function (el) { 3282 if (el) { 3283 Array.prototype.forEach.call(el.length ? el : [el], destroy); 3284 } 3285 return el; 3286 }; 3287 autosize.update = function (el) { 3288 if (el) { 3289 Array.prototype.forEach.call(el.length ? el : [el], update); 3290 } 3291 return el; 3292 }; 3293 } 3294 3295 module.exports = autosize; 3296}); 3297;/*})'"*/;/*})'"*/ 3298$(document).ready(function(){ 3299 3300 $('div#blockrighttabs ul').each(function() { 3301 $(this).find('li').each(function(i) { 3302 $(this).click(function(){ 3303 $(this).addClass('active topics-side__tab--active').siblings().removeClass('active topics-side__tab--active') 3304 .parents('div.block').find('div.blockrighttab-div').hide().end().find('div.blockrighttab-div:eq('+i+')').fadeIn(150); 3305 return false; 3306 }); 3307 }); 3308 }); 3309 3310}); 3311;/*})'"*/;/*})'"*/ 3312const LYRICS_TIME_OFFSET = 0.5; 3313if (typeof gtag === 'undefined') { window.gtag = function() {}; } 3314 3315jQuery.event.special.touchstart = { 3316 setup: function( _, ns, handle ) { 3317 this.addEventListener('touchstart', handle, { passive: !ns.includes('noPreventDefault') }); 3318 } 3319}; 3320jQuery.event.special.touchmove = { 3321 setup: function( _, ns, handle ) { 3322 this.addEventListener('touchmove', handle, { passive: !ns.includes('noPreventDefault') }); 3323 } 3324}; 3325 3326 3327/** 3328 * Gets the HTML content for a footnote tooltip by finding the corresponding <li> 3329 * based on the link's href attribute. 3330 * 3331 * @param {HTMLElement} linkElement The <a> element linking to the footnote. 3332 * @returns {string} The HTML content of the footnote, or an empty string. 3333 */ 3334function getFootnoteTooltipContent(linkElement) { 3335 if (!linkElement) { 3336 return ''; 3337 } 3338 var href = linkElement.getAttribute('href'); 3339 if (href && href.startsWith('#')) { 3340 var targetId = href.substring(1); 3341 // Use plain JS for finding element by ID for potentially better performance/reliability 3342 var targetLi = document.getElementById(targetId); 3343 if (targetLi) { 3344 // Clone the li using jQuery to easily manipulate it 3345 var $cloneLi = $(targetLi).clone(); 3346 // Remove the back-link label(s) 3347 $cloneLi.find('a.footnote-label, a.footnote-multi').remove(); 3348 // Get the remaining HTML, trim whitespace 3349 var contentHtml = $cloneLi.html().trim(); 3350 if (contentHtml) { 3351 // Wrap the HTML string in a jQuery object/detached element 3352 // This is safer for Tooltipster when contentAsHTML is true. 3353 return $('<div></div>').html(contentHtml); 3354 } 3355 return ''; // Return empty string (Tooltipster can handle this) 3356 // or an empty jQuery object: $() 3357 } 3358 } 3359 return ''; // Return empty if href is missing or target not found 3360} 3361 3362$(document).ready(function() { 3363 3364 // Navigation menu 3365 var $navWidth = (deviceType() == 'mobile') ? '290' : '310'; 3366 var $userNavWidth = (deviceType() == 'mobile') ? '290' : '400';
3366 // Wider for desktop 3367 var $navPosition = 'right'; // Always open from right for consistency 3368 var $isRTL = $('body.rtl').length > 0; 3369 3370 var $navTitleLT = $('<img src="/sites/all/themes/lt7/img/logo.svg" alt="Home" id="logo-mobile-img" width="48" height="36">'); 3371 if ($('body.not-logged-in').length) { 3372 // Guests: defer hcOffcanvasNav init to first burger click (saves ~553+ cloned DOM elements) 3373 var _guestNavInited = false; 3374 var _guestIsNavEmpty = true; 3375 3376 $(document).on('click', '[data-menu-burger]', function(e) { 3377 e.preventDefault(); 3378 if (!_guestNavInited) { 3379 _guestNavInited = true; 3380 var $nav = $('#main-nav').hcOffcanvasNav({ 3381 customToggle: $('.main-nav-toggle'), 3382 navTitle: $navTitleLT, 3383 width: $navWidth, 3384 levelTitles: true, 3385 levelTitleAsBack: true, 3386 levelOpen: 'expand', 3387 labelBack: Drupal.t('Back'), 3388 position: $navPosition, 3389 rtl: $isRTL, 3390 swipeGestures: false 3391 }); 3392 var Nav = $nav.data('hcOffcanvasNav'); 3393 3394 Nav.on('open', function(e, settings) { 3395 if (_guestIsNavEmpty) { 3396 $(e.currentTarget).find('.nav-content').addClass('ajax_loading'); 3397 var languagevarq = Drupal.settings.languagevarq; 3398 $.ajax({ 3399 type: "POST", 3400 dataType: 'json', 3401 url: '/' + languagevarq + '/callback/nav1', 3402 success: function(data) { 3403 $('#main-nav').html(data.html); 3404 Nav.update(true); 3405 _guestIsNavEmpty = false; 3406 }, 3407 }); 3408 } 3409 }); 3410 3411 Nav.open(); 3412 } else { 3413 var navInstance = $('#main-nav').data('hcOffcanvasNav'); 3414 if (navInstance) navInstance.open(); 3415 } 3416 }); 3417 // Guests have no .user-avatar or #user-nav content â skip user-nav init entirely 3418 } 3419 else { 3420 // Logged-in: immediate init (user-specific content in both navs) 3421 var $nav = $('#main-nav').hcOffcanvasNav({ 3422 customToggle: $('.main-nav-toggle'), 3423 navTitle: $navTitleLT, 3424 width: $navWidth, 3425 levelTitles: true, 3426 levelTitleAsBack: true, 3427 levelOpen: 'expand', 3428 labelBack: Drupal.t('Back'), 3429 position: $navPosition, 3430 rtl: $isRTL, 3431 swipeGestures: false 3432 }); 3433 3434 var $navTitleUser = $('.user-avatar').clone(); 3435 if (!$navTitleUser.find('.text').length) { 3436 $navTitleUser.append( $('#user-name').text() ); 3437 } 3438 3439 $navPosition = $isRTL ? 'left' : 'right'; 3440 3441 $('#user-nav').hcOffcanvasNav({ 3442 customToggle: $('.user-nav-toggle'), 3443 navTitle: $navTitleUser, 3444 width: $userNavWidth, 3445 levelTitles: true, 3446 levelTitleAsBack: true, 3447 levelOpen: 'expand', 3448 position: $navPosition, 3449 rtl: $isRTL, 3450 swipeGestures: false 3451 }); 3452 3453 $(document).on('click', '[data-menu-burger]', function(e) { 3454 e.preventDefault(); 3455 var navInstance = $('#main-nav').data('hcOffcanvasNav'); 3456 if (navInstance) navInstance.open(); 3457 }); 3458 3459 $(document).on('click', '.user-avatar', function(e) { 3460 e.preventDefault(); 3461 var userNavInstance = $('#user-nav').data('hcOffcanvasNav'); 3462 if (userNavInstance) userNavInstance.open(); 3463 }); 3464 } 3465 3466 // tooltips 3467 // https://www.heteroclito.fr/modules/tooltipster/ 3468 3469 3470 3471 3472 // Initialize tooltipster for footnotes (.see-footnote) individually using custom content logic 3473 $('.see-footnote').each(function() { 3474 var currentLink = this; // Capture the current link element 3475 3476 $(this).tooltipster({ 3477 theme: ['tooltipster-shadow', 'tooltipster-large-padding'], 3478 maxWidth: 400, 3479 delay: 100, 3480 side: ['right', 'top', 'bottom', 'left'], 3481 interactive: true, 3482 multiple: true, 3483 contentAsHTML: true, // Needed for footnotes with HTML 3484 // Use the refactored function to get content 3485 content: function(instance, helper) { 3486 // Pass the originally captured link element to the function 3487 return getFootnoteTooltipContent(currentLink); 3488 } 3489 }); 3490 }); 3491 3492 // Initialize tooltipster for generic tooltips (.tooltip) using default behavior 3493 $('.tooltip').tooltipster({ 3494 theme: 'tooltipster-shadow', 3495 maxWidth: 400, 3496 delay: 100, 3497 side: ['right', 'top', 'bottom', 'left'], 3498 interactive: true, 3499 multiple: true, 3500 // Using default content discovery (likely title attribute) 3501 // Add functionFormat to ensure content from title attribute is treated as plain text 3502 functionFormat: function(instance, helper, content) { 3503 // The 'content' parameter is the string from the title attribute. 3504 // Returning it directly should make Tooltipster use it as plain text,
3505 // respecting the default contentAsHTML: false. 3506 return content; 3507 } 3508 }); 3509 3510 $('.tooltiptop, img.badge').tooltipster({ 3511 theme: 'tooltipster-shadow', 3512 maxWidth: 400, 3513 side: ['top', 'bottom', 'tight', 'left'], 3514 interactive: true, 3515 trigger: 'hover', 3516 delay: 100, 3517 multiple: true, 3518 }); 3519 3520 $('.tooltipclick').tooltipster({ 3521 theme: ['tooltipster-shadow', 'tooltipster-large-padding'], 3522 maxWidth: 400, 3523 side: ['top', 'right', 'bottom', 'left'], 3524 interactive: true, 3525 contentAsHTML: true, 3526 trigger: 'click' 3527 }); 3528 3529 // Initialize tooltipster for comment action buttons (like, reply, more) 3530 $('.comment-like-btn, .comment-reply-btn, .comment-more-btn').tooltipster({ 3531 theme: 'tooltipster-shadow', 3532 maxWidth: 200, 3533 side: ['top', 'bottom', 'left', 'right'], 3534 delay: 200, 3535 }); 3536 3537 // Video preview tooltip â delegated, init on first hover 3538 $(document).on('mouseenter', '.video-icon-player.active-player', function() { 3539 var $el = $(this); 3540 if ($el.data('tooltipsterNs')) return; // already initialized 3541 $el.tooltipster({ 3542 content: '<div class="ajax_loading"></div>', 3543 contentAsHTML: true, 3544 interactive: true, 3545 delay: 100, 3546 animation: 'fade', 3547 updateAnimation: false, 3548 theme: ['tooltipster-shadow', 'tooltipster-shadow-customized'], 3549 functionBefore: function(instance, helper) { 3550 var youtubeId = $(helper.origin).attr('youtube'); 3551 if (!youtubeId) return; 3552 instance.content('<div class="ajax_loading"></div>'); 3553 var img = new Image(); 3554 img.onload = function() { 3555 instance.content('<div class="tooltip-background" style="background-image: url(' + img.src + ');"><span class="lty-playbtn"></span></div>'); 3556 }; 3557 img.src = 'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'; 3558 }, 3559 functionReady: function(instance, helper) { 3560 $(helper.tooltip).on('click', function() { 3561 $(helper.origin).trigger('click'); 3562 }); 3563 } 3564 }); 3565 $el.tooltipster('open'); 3566 }); 3567 3568 // auto direction (rtl or ltr) 3569 $("textarea, input, #comments p, .field-field-author-comments .field-items p, .authorcommenttext p, .forum-post-content p, .privatemsg-message-body p").each( function() { 3570 $(this).attr("dir","auto"); 3571 }); 3572 3573 // green_buttons: track action select changes (new design) 3574 $(document).on('change', '.songs__action-btn select', function() { 3575 var val = $(this).val(); 3576 if (!val) return; 3577 var act = 'unknown'; 3578 if (val.indexOf('add/song') !== -1 || val.indexOf('add/transrequest') !== -1) act = 'add_song'; 3579 else if (val.indexOf('add/translate') !== -1) act = 'add_translation'; 3580 else if (val.indexOf('add/request') !== -1) act = 'add_request'; 3581 else if (val.indexOf('add/subtitles') !== -1) act = 'add_subtitles'; 3582 else if (val.indexOf('proofreading') !== -1) act = 'proofreading'; 3583 if (typeof gtag === 'function') { 3584 gtag('event', 'green_buttons', { 'user_action': act }); 3585 } 3586 }); 3587 3588 // green_buttons: track add/request links in overlay menus (mobile) 3589 $(document).on('click', '.menu-b__link[href*="add/translate"], .menu-b__link[href*="add/request"], .menu-b__link[href*="add/song"], .menu-b__link[href*="add/transrequest"], .menu-b__link[href*="add/subtitles"]', function() { 3590 var href = $(this).attr('href') || ''; 3591 var act = 'unknown'; 3592 if (href.indexOf('add/song') !== -1 || href.indexOf('add/transrequest') !== -1) act = 'add_song'; 3593 else if (href.indexOf('add/translate') !== -1) act = 'add_translation'; 3594 else if (href.indexOf('add/request') !== -1) act = 'add_request'; 3595 else if (href.indexOf('add/subtitles') !== -1) act = 'add_subtitles'; 3596 if (typeof gtag === 'function') { 3597 gtag('event', 'green_buttons', { 'user_action': act }); 3598 } 3599 }); 3600 3601 3602 /** 3603 * Clearable text inputs 3604 */ 3605 function tog(v){return v ? "addClass" : "removeClass";} 3606 $(document).on("input", ".clearable", function(){ 3607 $(this)[tog(this.value)]("x"); 3608 }).on("mousemove", ".x", function( e ){ 3609 $(this)[tog(this.offsetWidth-18 < e.clientX-this.getBoundingClientRect().left)]("onX"); 3610 }).on("touchstart click", ".onX", function( ev ){ 3611 ev.preventDefault(); 3612 $(this).removeClass("x onX").val("").change(); 3613 }); 3614 $("input.clearable").trigger("input"); 3615 3616 3617 3618 // analytics for form submit 3619 $(document).on('submit','form',function(){ 3620 var form = $(this); 3621 var fid = form.attr('id'); 3622 if (! fid) { 3623 fid = 'noname'; 3624 } 3625 gtag('event', 'form_submit', { 3626 'form_id': fid, 3627 }); 3628 }); 3629 3630 // monitor click on video 3631 $(document).on("click", "lite-youtube", function(e){ 3632 gtag('event', 'video_load', { 3633 'video_category' : 'lite-youtube' 3634 }); 3635 }); 3636 3637 // close alert 3638 $(document).on("click", "a.alert-close", function(e){ 3639 e.preventDefault(); 3640 $el = $(this); 3641 $p = $el.parent(); 3642 $p.hide(); 3643 }); 3644 3645 3646 var $el, $ps, $up, $totalHeight; 3647 $(document).on("click", ".expand-text-wrapper .expand-button", function(e){ 3648 e.preventDefault(); 3649 $totalHeight = 0; 3650 $el = $(this); 3651 $p = $el.parent(); 3652 $up = $p.parent(); 3653 $up 3654 .css({ 3655 "height": 'auto', 3656 "max-height": 9999 3657 }); 3658 $p.css('display', 'none'); 3659 }); 3660 3661 3662 $('.wrapper-visible-part').on('click', function (e) { 3663 $(this).find('.wrapper-hidden-part').show(); 3664 $(this).find('.wrapper-hide-on-click').hide(); 3665 }); 3666 3667 3668 $(document).on('click', '.wrapper-visible-part-inline', function (e) { 3669 $(this).find('.wrapper-hidden-part').css( "display", "inline-flex" ); 3670 $(this).find('.wrapper-hide-on-click').hide(); 3671 }); 3672 3673 3674 var loc = getPageType(); 3675 var loggedIn = $("body.logged-in").length ? 'user' : 'guest'; 3676 var isGuest = $("body").hasClass("not-logged-in") ? true : false;
3677 3678 if (loggedIn) { 3679 $("#lyrics-formatting-confirm").click( function(e) { 3680 $('#lyrics-formatting-confirm').addClass("ajax_loading_small"); 3681 linkurl = '/' + Drupal.settings.languagevarq + '/callback/set/lyricsformattingconfirm'; 3682 3683 $.ajax({ 3684 type: "POST", 3685 url: linkurl, 3686 data: {}, 3687 dataType: 'json', 3688 success: function(result) { 3689 if(result && (result.ok == 'ok')) { 3690 $('.announcement-container').fadeOut(); 3691 } 3692 else { 3693 console.log('can not confirm'); 3694 } 3695 } 3696 }); 3697 }); 3698 } 3699 3700 3701 if (loggedIn || (isGuest && (loc != 'song') && (loc != 'translation') && (loc != 'artist'))) { 3702 if ($(".messages").length) { 3703 // Prepend a close button to each message. 3704 $(".messages:not(.messageclose-processed)").each( function() { 3705 $(this).addClass('messageclose-processed'); 3706 $(this).prepend('<a href="#" class="messageclose" title="' + Drupal.t('close') + '">×</a>'); 3707 }); 3708 3709 // When a close button is clicked hide this message. 3710 $(".messages a.messageclose").click( function(event) { 3711 event.preventDefault(); 3712 $(this).parent().fadeOut("slow", function() { 3713 var messages_left = $("#messages .section").children().size(); 3714 if (messages_left == 1) { 3715 $("#messages").remove(); 3716 } 3717 else { 3718 $(this).remove(); 3719 } 3720 }); 3721 }); 3722 } 3723 } 3724 3725 // colorbox for some links 3726 if ($("body").hasClass("front") && $("body").hasClass("not-logged-in") && $(".front-search-block-right a").length) { 3727 $('.front-search-block-right a').colorboxNode({'launch': false, 'width':'320px', 'height':'550px'}); 3728 } 3729 3730 // show/hide part of long texts 3731 textMinMore(200); 3732 3733 // share_content: track share menu open 3734 $(document).on('click', '[data-more-toggle*="share"], #artist-share-toggle-guest', function() { 3735 if (typeof gtag === 'function') { 3736 gtag('event', 'share_content', { 'share_content_action': 'open' }); 3737 } 3738 }); 3739 3740 // share_content: track final clicks in share overlay (Facebook, X links) 3741 $(document).on('click', '[data-more-content*="share"] .menu-b__link:not(.social-copy-url):not(#system-share-btn)', function() { 3742 if (typeof gtag === 'function') { 3743 var provider = $.trim($(this).find('.text').text()).toLowerCase() || 'unknown'; 3744 gtag('event', 'share_content', { 'share_content_provider': provider }); 3745 } 3746 }); 3747 3748 // share_content: track direct FB/X links below lyrics (translate__btns) 3749 $(document).on('click', '.translate__btn[target="_blank"]', function() { 3750 if (typeof gtag === 'function') { 3751 var title = $(this).attr('title') || ''; 3752 var provider = 'unknown'; 3753 if (title.indexOf('Facebook') !== -1) provider = 'facebook'; 3754 else if (title.indexOf('X') !== -1) provider = 'x'; 3755 gtag('event', 'share_content', { 'share_content_provider': provider }); 3756 } 3757 }); 3758 3759 // block click statistics 3760 $(document).on("click", "div.block", function(e){ 3761 3762 var block_id = $(this).attr('id'); 3763 3764 if (block_id) { 3765 block_id = block_id.replace("lyricstranslateblocks-", ""); 3766 block_id = block_id.replace("lyricstranslate", ""); 3767 gtag('event', 'block_use', { 3768 'block_id' : block_id 3769 }); 3770 } 3771 3772 }); 3773 3774 // sticky affiliate link 3775 var stickyBanner = $("#tonefuse_container1"); 3776 3777 // tonefuse statistics (GA4) â gtag survives page unload via its own 3778 // pagehide flush, so the bubble phase is fine for monitorTonefuse. 3779 $(document).on("click", "#tonefuse_container1", function(e){ 3780 monitorTonefuse(tonefuseLabel(this), 'top', tonefuseExtras(this)); 3781 }); 3782 3783 $(document).on("click", "#tonefuse_container2", function(e){ 3784 monitorTonefuse(tonefuseLabel(this), 'bottom', tonefuseExtras(this)); 3785 }); 3786 3787}); 3788 3789 3790$(document).ready(function() { 3791 3792 3793 // sticky ads for guests on song/translation pages 3794 if ($("body").hasClass("not-logged-in") && $("#sd").length && $(".content__side").length) { 3795 var $sidebar = $(".content__side"); 3796 var $footer = $("#footer, .footer"); 3797 3798 if ($footer.length && $sidebar.length) { 3799 var dis = $footer.offset().top - $sidebar.offset().top; 3800 3801 if ((dis > 700) && ($(window).width() > 750)) { 3802 setTimeout(function(){ 3803 if ($.fn.hcSticky) { 3804 $sidebar.hcSticky({ 3805 stickTo: ".content__container", 3806 innerSticker: "#sd", 3807 followScroll: false, 3808 responsive: { 3809 750: { 3810 disable: true 3811 } 3812 } 3813 }); 3814 } 3815 }, 3000); 3816 } 3817 } 3818 } 3819 3820 3821 // sticky video for lyrics request 3822 if ($.fn.hcSticky && $("body.node-type-lttransrequest").length) { 3823 $('body.node-type-lttransrequest #vd').hcSticky({ 3824 responsive: { 3825 750: { 3826 disable: true 3827 } 3828 } 3829 }); 3830 } 3831 3832 // sticky video for songs 3833 if ($.fn.hcSticky) { 3834 $('body.node-type-song.logged-in #vd, body.node-type-song.nadlt.not-logged-in #vd').hcSticky({ 3835 top: 10, 3836 followScroll: false, 3837 responsive: { 3838 750: { 3839 disable: true 3840 } 3841 } 3842 }); 3843 } 3844 3845 3846 3847 3848 $(document).on("click", ".social-copy-url", function(e){ 3849 e.preventDefault(); 3850 if (typeof gtag === 'function') { 3851 gtag('event', 'share_content', { 'share_content_provider': 'copy_link' }); 3852 } 3853 var currentButton = $(this); // ÐбеÑÑгаÑмо поÑоÑний ÐµÐ»ÐµÐ¼ÐµÐ½Ñ 3854 var urlToCopy = currentButton.attr('data-copy-url') || window.location.href; 3855 navigator.clipboard.writeText(urlToCopy).then(function() { 3856 // ÐбеÑÑгаÑмо ÑÐºÐ¾Ð½ÐºÑ Ð¿Ð¾ÑоÑÐ½Ð¾Ñ ÐºÐ½Ð¾Ð¿ÐºÐ¸: ÑпеÑÑÑ ÑÑкаÑмо <i>, ÑнакÑе .icon 3857 var iconEl = currentButton.find('i').first(); 3858 if (!iconEl.length) { iconEl = currentButton.find('.icon').first(); } 3859 var iconHtml = iconEl.length ? iconEl.prop('outerHTML') : ''; 3860 // ÐмÑнÑÑмо ÑекÑÑ ÐºÐ½Ð¾Ð¿ÐºÐ¸ поÑоÑÐ½Ð¾Ñ ÐºÐ½Ð¾Ð¿ÐºÐ¸ 3861 currentButton.html((iconHtml ? iconHtml + ' ' : '') + Drupal.t('Link copied')); 3862 3863 // ÐовеÑÑаÑмо поÑаÑковий ÑекÑÑ ÑеÑез 2 ÑекÑнди 3864 setTimeout(function() { 3865 currentButton.html((iconHtml ? iconHtml + ' ' : '') + Drupal.t('Copy link')); 3866 }, 2000); 3867 }).catch(function(err) { 3868 console.error('Failed to copy link: ', err); 3869 }); 3870 }); 3871 3872 // Safety: prevent default navigation on lyrics copy anchors globally. 3873 // Main copy-lyrics logic lives in js_new/song.js; this ensures no page jump even if that script loads later. 3874 $(document).on("click", ".social-copy-lyrics", function(e){ 3875 e.preventDefault(); 3876 }); 3877 3878 3879 var saveSrc = ''; 3880 3881 $(window).resize(function () { 3882 3883 var player1 = $(".node-type-song .song-node-video div.youtube-player iframe"); 3884 var player2 = $(".node-type-song .mobile-video-player div.youtube-player iframe"); 3885 3886 if ((player1.length || player2.length) && (saveSrc == '')) { 3887 saveSrc = player1.length ? player1.attr("src") : (player2.length ? player2.attr("src") : ''); 3888 saveSrc = saveSrc.replace("autoplay=1", "autoplay=0"); 3889 } 3890 3891 if ($(this).width() < 1000) { 3892 if (player1.length) { 3893 player1.attr("src",""); 3894 } 3895 3896 if (player2.length && (! player2.attr("src"))) { 3897 player2.attr("src", saveSrc); 3898 } 3899 } 3900 else { 3901 if (player2.length) { 3902 player2.attr("src",""); 3903 } 3904 3905 if (player1.length && (! player1.attr("src"))) { 3906 player1.attr("src", saveSrc); 3907 } 3908 } 3909 3910 }); 3911 3912}); 3913 3914function textMinMore(num) { 3915 var minimized_elements = $('.minimize-' + num); 3916 3917 minimized_elements.each(function(){ 3918 var t = $(this).text(); 3919 if(t.length < num) return; 3920 3921 $(this).html( 3922 t.slice(0,num)+'<span>... </span><a href="#" class="minmore">⋙</a>'+ 3923 '<span style="display:none;">'+ t.slice(num,t.length)+' <a href="#" class="minless">⋘</a></span>' 3924 ); 3925 }); 3926 3927 $('a.minmore', minimized_elements).click(function(event){ 3928 event.preventDefault(); 3929 $(this).hide().prev().hide(); 3930 $(this).next().show(); 3931 }); 3932 3933 $('a.minless', minimized_elements).click(function(event){ 3934 event.preventDefault(); 3935 $(this).parent().hide().prev().show().prev().show(); 3936 }); 3937} 3938 3939function deviceType() { 3940
3941 var dev = 'desktop'; 3942 var mobileBreakpoint = 36.31125 * 16; // 36.31125rem in pixels 3943 3944 if ($(window).width() < mobileBreakpoint) { 3945 dev = 'mobile'; 3946 } 3947 else if ($(window).width() < 1000) { 3948 dev = 'tablet'; 3949 } 3950 3951 return dev; 3952} 3953 3954function hasUrl(s) { 3955 var regexp1 = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; 3956 var regexp2 = /<a\s/i; 3957 if (regexp1.test(s) || regexp2.test(s)) { 3958 return true; 3959 } 3960 else { 3961 return false; 3962 } 3963} 3964 3965function getPageType() { 3966 3967 var loc = 'unknown'; 3968 3969 if ($("body").hasClass( "node-type-song" )) { 3970 loc = 'song'; 3971 } 3972 else if ($("body").hasClass( "node-type-translation" )) { 3973 loc = 'translation'; 3974 } 3975 else if ($("body").hasClass( "node-type-request" )) { 3976 loc = 'request'; 3977 } 3978 else if ($("body").hasClass( "node-type-artist" )) { 3979 loc = 'artist'; 3980 } 3981 else if ($("body").hasClass( "node-type-collection" )) { 3982 loc = 'collection'; 3983 } 3984 else if ($("body").hasClass( "page-user" )) { 3985 loc = 'user'; 3986 } 3987 else if ($("body").hasClass( "node-type-lttransrequest" )) { 3988 loc = 'transrequest'; 3989 } 3990 else if ($("body").hasClass( "front" )) { 3991 loc = 'front'; 3992 } 3993 3994 return loc; 3995} 3996 3997function getUserType() { 3998 3999 var loc = 'guest'; 4000 4001 if ($("body.logged-in").length) { 4002 loc = 'user'; 4003 } 4004 4005 return loc; 4006 4007} 4008 4009function ispinner(obj) { 4010 var html = '<div class="ispinner gray animating">'; 4011 for (var i = 0; i < 12; i++) { 4012 html += '<div class="ispinner-blade"></div>'; 4013 } 4014 html += '</div>'; 4015 obj.html(html); 4016} 4017 4018// ToneFuse injects <style>/<script> into the container in a way that 4019// innerText doesn't always exclude (rules from injected <style> can show up 4020// in the label, ~4k events/day). Clone and strip them before reading. 4021function tonefuseLabel(el) { 4022 if (!el) return ''; 4023 var clone = el.cloneNode(true); 4024 var junk = clone.querySelectorAll('style, script'); 4025 for (var i = 0; i < junk.length; i++) junk[i].parentNode.removeChild(junk[i]); 4026 return ((clone.innerText || clone.textContent) || '').replace(/\s+/g, ' ').trim(); 4027} 4028 4029// Extracts ToneFuse's own identifiers from <tonefuse-ad data-tf_context="..."> 4030// Returns null when the ad has not rendered yet or markup changed. 4031function tonefuseExtras(el) { 4032 if (!el || !el.querySelector) return null; 4033 var ctxEl = el.querySelector('[data-tf_context]'); 4034 if (!ctxEl) return null; 4035 try { 4036 var ctx = JSON.parse(ctxEl.getAttribute('data-tf_context')); 4037 return { creative_id: ctx.creative_id, geo: ctx.geo_country, ad_unit: ctx.ad_unit_id }; 4038 } catch (e) { 4039 return null; 4040 } 4041} 4042 4043// Brand from the ad label â ToneFuse keeps the brand name in the label text 4044// ("... on Apple Music", "Amazon Music Unlimited (ad)", "... on Bandsintown"). 4045// "ÑинÑаÑн" catches Bandsintown transliterated into Cyrillic (sr/bg locales). 4046function tonefuseBrand(label) { 4047 if (!label) return ''; 4048 if (label.indexOf('Apple Music') !== -1) return 'Apple'; 4049 if (label.indexOf('Amazon Music') !== -1) return 'Amazon'; 4050 if (label.indexOf('Bandsintown') !== -1 || label.indexOf('ÑинÑаÑн') !== -1) return 'Bandsintown'; 4051 return 'Other'; 4052} 4053 4054// GA4 SDK coerces all-digit strings into int_value; custom dimensions read 4055// only string_value. Prefix to keep them stringly-typed. 4056function monitorTonefuse(text, location, extras) { 4057 var payload = { 4058 tonefuse_location: location, 4059 tonefuse_label: text 4060 }; 4061 var brand = tonefuseBrand(text); 4062 if (brand) payload.tonefuse_brand = brand; 4063 if (extras) { 4064 if (extras.creative_id) payload.tonefuse_creative_id = 'cr-' + extras.creative_id; 4065 if (extras.geo) payload.tonefuse_geo = extras.geo; 4066 if (extras.ad_unit) payload.tonefuse_ad_unit = 'au-' + extras.ad_unit; 4067 } 4068 gtag('event', 'tonefuse', payload); 4069} 4070 4071function createCookie(name, value, days) { 4072 var expires; 4073 4074 if (days) { 4075 var date = new Date(); 4076 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 4077 expires = "; expires=" + date.toGMTString(); 4078 } else { 4079 expires = ""; 4080 } 4081 document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/"; 4082} 4083 4084function readCookie(name) { 4085 var nameEQ = encodeURIComponent(name) + "="; 4086 var ca = document.cookie.split(';'); 4087 for (var i = 0; i < ca.length; i++) { 4088 var c = ca[i]; 4089 while (c.charAt(0) === ' ') 4090 c = c.substring(1, c.length); 4091 if (c.indexOf(nameEQ) === 0) 4092 return decodeURIComponent(c.substring(nameEQ.length, c.length)); 4093 } 4094 return null; 4095} 4096
4097function eraseCookie(name) { 4098 createCookie(name, "", -1); 4099} 4100 4101 4102function isNumeric(str) { 4103 if (typeof str != "string") return false // we only process strings! 4104 return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)... 4105 !isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail 4106} 4107 4108function isMacOS() { 4109 const userAgent = window.navigator.userAgent; 4110 return /Macintosh/.test(userAgent) && !window.MSStream; 4111} 4112 4113function isiOS() { 4114 const userAgent = window.navigator.userAgent; 4115 return /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream; 4116} 4117 4118function getOperatingSystem() { 4119 var userAgent = navigator.userAgent; 4120 var windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']; 4121 var os = null; 4122 4123 if (windowsPlatforms.indexOf(window.navigator.platform) !== -1) { 4124 os = 'Windows'; 4125 } else if (!os && /Mac/.test(userAgent)) { 4126 os = 'MacOS'; 4127 } else if (!os && /Linux/.test(userAgent)) { 4128 os = 'Linux'; 4129 } else if (!os && /Android/.test(userAgent)) { 4130 os = 'Android'; 4131 } else if (!os && /iPhone|iPad|iPod/.test(userAgent)) { 4132 os = 'iOS'; 4133 } 4134 4135 return os; 4136} 4137 4138// ФÑнкÑÑÑ Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½ÑÐ°Ð¶ÐµÐ½Ð½Ñ JS-ÑайлÑв 4139function loadJSFile(path) { 4140 return new Promise((resolve, reject) => { 4141 $.getScript(path, function (data, textStatus, jqxhr) { 4142 if (textStatus === "success") { 4143 resolve(data); 4144 } else { 4145 reject(new Error("Failed to load script: " + path)); 4146 } 4147 }); 4148 }); 4149} 4150 4151// ФÑнкÑÑÑ Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½ÑÐ°Ð¶ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ Ñ Ð·Ð±ÐµÑÑÐ³Ð°Ð½Ð½Ñ ÑÑ Ð² window.ltSubtitlesData 4152function loadSubtitles(path) { 4153 return new Promise((resolve) => { 4154 $.getJSON(path) 4155 .done(function(data) { 4156 window.ltSubtitlesData = data; 4157 if (data && data['webtvv']) { 4158 gtag('event', 'subtitles_load'); 4159 } 4160 resolve(data); 4161 }) 4162 .fail(function() { 4163 // ЯкÑо ÑоÑÑ Ð¿ÑÑло не Ñак - повеÑÑаÑмо пÑÑÑий об'ÑÐºÑ 4164 window.ltSubtitlesData = {}; 4165 resolve({}); 4166 }); 4167 }); 4168} 4169 4170function loadPlayerFunctionsScript() { 4171 return new Promise((resolve, reject) => { 4172 const script = document.createElement('script'); 4173 script.src = "/sites/all/themes/lt7/js/jmenu/youtubePlayerFunctions.js?23"; 4174 script.onload = resolve; 4175 script.onerror = reject; 4176 document.body.appendChild(script); 4177 }); 4178} 4179;/*})'"*/;/*})'"*/ 4180var vjsPlayer, vjsSongs; 4181var videoIconsCache = null; 4182 4183// Cache DOM elements for better performance 4184function initVideoIconsCache() { 4185 if (!videoIconsCache) { 4186 videoIconsCache = $('#lyrics-settings-video, .video-icon-player.active-player'); 4187 } 4188 return videoIconsCache; 4189} 4190 4191// Reset cache when new icons are added 4192function resetVideoIconsCache() { 4193 videoIconsCache = null; 4194} 4195 4196function onReady() { 4197 const activeIcon = $(".isactive"); 4198 setVideoIconState(activeIcon, true); 4199} 4200 4201function onStop(player) { 4202 var current = player.currentSrc(); 4203 $('#lyrics-settings-video, .video-icon-player.active-player').each(function () { 4204 if (current.indexOf($( this ).attr("youtube")) > -1) { 4205 setVideoIconState($(this), false); 4206 } 4207 }); 4208} 4209 4210function onPause(player) { 4211 var current = player.currentSrc(); 4212 var icons = initVideoIconsCache(); 4213 4214 icons.each(function () { 4215 var $this = $(this); 4216 if (current.indexOf($this.attr("youtube")) > -1) { 4217 setVideoIconState($this, false); 4218 } 4219 }); 4220} 4221 4222function onPlayComplete() { 4223 if (typeof vjsPlayer === 'undefined' || !vjsPlayer) { 4224 return; 4225 } 4226 var current = vjsPlayer.currentSrc(); 4227 $('#lyrics-settings-video, .video-icon-player.active-player').each(function () { 4228 if (current.indexOf($( this ).attr("youtube")) > -1) { 4229 setVideoIconState($(this), false); 4230 } 4231 }); 4232 // Also reset artist-settings-video button state 4233 $("#artist-settings-video").removeClass("isactive"); 4234 setVideoIconState($("#artist-settings-video"), false); 4235} 4236 4237function onPlay(player) { 4238 $(".vjs-selected")[0].scrollIntoView({behavior: "smooth", block: "center"}); 4239 var current = player.currentSrc(); 4240 var icons = initVideoIconsCache(); 4241 4242 icons.each(function () { 4243 var $this = $(this); 4244 if (current.indexOf($this.attr("youtube")) > -1) { 4245 setVideoIconState($this, true); 4246 } else { 4247 setVideoIconState($this, false); 4248 } 4249 }); 4250 4251 // var loc = getPageType(); 4252 // ga('send', 'event', 'Video', 'Icon', loc); 4253} 4254 4255 4256function onFirstPlay(player) { 4257 var current = player.currentSrc(); 4258 var icons = initVideoIconsCache(); 4259 4260 if (current.indexOf($(".isactive").attr("youtube")) == -1) { 4261 $(".isactive").removeClass("isactive"); 4262 } 4263 4264 let lyricsId = 0; 4265 let youtubeId = ''; 4266 4267 // find current icon and make it active + find attributes for subtitles 4268 icons.each(function () { 4269 var $this = $(this); 4270 if (current.indexOf($this.attr("youtube")) != -1) { 4271 $this.addClass("isactive"); 4272 setVideoIconState($this, true); // Set icon to playing state 4273 youtubeId = $this.attr("youtube"); 4274 lyricsId = $this.attr("nid"); 4275 lyricsSubtitles = $this.attr("cc"); 4276 } 4277 }); 4278 4279 // update "View lyrics" link 4280 if (lyricsId && $("#vjs-view-lyrics").length) { 4281 $("#vjs-view-lyrics").attr('href', '/' + Drupal.settings.languagevarq + '/node/' + lyricsId); 4282 } 4283 4284 // remove previous subtitles 4285 var tracks = player.remoteTextTracks(); 4286 var trackToRemove; 4287 4288 for (var i = 0; i < tracks.length; i++) { 4289 var track = tracks[i]; 4290 player.removeRemoteTextTrack(track); 4291 } 4292 4293 // load subtitles 4294 if (lyricsSubtitles) { 4295 const subtitlesPath = '/' + Drupal.settings.languagevarq + '/callback/subtitles/' + lyricsId + '/' + youtubeId; 4296 loadSubtitles(subtitlesPath) 4297 .then(() => { 4298 const data = window.ltSubtitlesData; 4299 4300 if (data.webtvv) { 4301 4302 let decodedString = data.webtvv.replace(/\\u([\d\w]{4})/gi, function (match, grp) { 4303 return String.fromCharCode(parseInt(grp, 16)); 4304 }); 4305 4306 const cleanedData = decodedString.replace(/^\s+/,''); // Remove leading whitespace 4307 const blob = new Blob([cleanedData], { type: 'text/vtt' }); 4308 const blobUrl = URL.createObjectURL(blob); 4309 4310 const track = { 4311 src: blobUrl, 4312 kind: 'subtitles', 4313 srclang: data.langsrc, 4314 label: data.langname + ' LT',
4315 mode: 'showing' 4316 }; 4317 4318 player.addRemoteTextTrack(track, true); 4319 } 4320 }) 4321 .catch(error => { 4322 console.error("Error loading subtitles in onFirstPlay:", error); 4323 }); 4324 } 4325 4326 4327 4328 if (typeof gtag !== 'undefined') gtag('event', 'video_load', { 4329 'video_category': 'sticky-youtube' 4330 }); 4331 4332} 4333 4334function onClose() { 4335 // var player = VJS.getPlayer('vjs-player'); 4336 // vjsPlayer.dispose(); 4337 vjsPlayer.pause(); 4338 $('#vjs-player-wrapper').hide(); 4339 onPlayComplete(); 4340 4341 // Disable video seek mode when player closes 4342 if (typeof disableVideoSeekMode === 'function') { 4343 disableVideoSeekMode(); 4344 } 4345} 4346 4347 4348function stickyVideoPlayer(obj) { 4349 var youtube = obj.attr('youtube'); 4350 var lyricsNid = obj.attr('nid'); 4351 4352 if (!youtube) { 4353 return; 4354 } 4355 4356 var path = '/sites/all/themes/lt7/js/videojs'; 4357 4358 $('<link>') 4359 .appendTo('head') 4360 .attr({ 4361 type: 'text/css', 4362 rel: 'stylesheet', 4363 href: path + '/video-js.min.css' 4364 }); 4365 4366 $('<link>') 4367 .appendTo('head') 4368 .attr({ 4369 type: 'text/css', 4370 rel: 'stylesheet', 4371 href: path + '/videojs-playlist-ui.css' 4372 }); 4373 4374 $('<link>') 4375 .appendTo('head') 4376 .attr({ 4377 type: 'text/css', 4378 rel: 'stylesheet', 4379 href: path + '/video-js-custom.css?new14' 4380 }); 4381 4382 var dev = deviceType(); 4383 4384 var offsetX = (dev === 'mobile') ? 0 : 5; 4385 var baseOffsetY = (dev === 'mobile') ? 0 : 5; 4386 function getAdhesiveHeight() { 4387 var ad = document.querySelector('.snigel-adhesive.bottom:not(.adhesive-hidden)'); 4388 if (!ad) return 0; 4389 return ad.offsetHeight; 4390 } 4391 var offsetY = baseOffsetY + getAdhesiveHeight(); 4392 var maxwidth = ($(window).width() > 1100) ? 500 : ((dev === 'mobile') ? window.innerWidth : 400); 4393 var maxheight = ($(window).width() > 1100) ? 280 : 225; 4394 maxwidth = ($("body.logged-in.node-type-lttransrequest").length || $("body.logged-in.page-node-edit.node-type-song").length || $("body.logged-in.page-node-edit.node-type-translation").length) ? 400 : maxwidth; 4395 maxheight = ($("body.logged-in.node-type-lttransrequest").length || $("body.logged-in.page-node-edit.node-type-song").length || $("body.logged-in.page-node-edit.node-type-translation").length) ? 225 : maxheight; 4396 var verticalPosition = $(".iflychat-popup").length ? "top" : "bottom"; 4397 var horizontalPosition = $("body.rtl").length ? "left" : "right"; 4398 var addKeyboardSupport = $("body").hasClass("not-logged-in") ? 'yes' : ($("body").hasClass("front") || $("body").hasClass("page-user")) ? 'yes' : 'no'; 4399 4400 if (!$('#vjs-player-wrapper').length) { 4401 var playerWrapper = document.createElement('div'); 4402 playerWrapper.id = 'vjs-player-wrapper'; 4403 playerWrapper.style.cssText = 'position: fixed; z-index: 9999; ' + verticalPosition + ': ' + offsetY + 'px; ' + horizontalPosition + ': ' + offsetX + 'px;'; 4404 4405 // playlist wrapper 4406 var playerPlaylist = document.createElement('div'); 4407 playerPlaylist.id = 'vjs-playlist'; 4408 playerPlaylist.classList.add('vjs-playlist'); 4409 if (deviceType() == 'mobile' ) { 4410 playerPlaylist.style.cssText = 'max-width: ' + maxwidth + 'px; display: none;'; 4411 } 4412 else { 4413 playerPlaylist.style.cssText = 'max-width: ' + maxwidth + 'px;'; 4414 } 4415 4416 playerWrapper.appendChild(playerPlaylist); 4417 4418 // close button 4419 var closeButton = document.createElement('div'); 4420 closeButton.classList.add('vjs-close'); 4421 closeButton.style.cssText = horizontalPosition + ': 0;'; 4422 playerWrapper.appendChild(closeButton); 4423 4424 // view lyrics link (hide when already on the lyrics node or its translation page) 4425 var currentSongId = (typeof Drupal !== 'undefined' && Drupal.settings && Drupal.settings.songnid) ? ('' + Drupal.settings.songnid) : ''; 4426 var isSameNode = currentSongId && (('' + lyricsNid) === currentSongId); 4427 // Also hide if the sticky is spawned by the main header button for the same youtube 4428 var shouldShowHeader = (lyricsNid && lyricsNid !== '0') && 4429 ($("#lyrics-settings-video").attr('youtube') != youtube) && 4430 (!isSameNode); 4431 if (shouldShowHeader) { 4432 var playerHeader = document.createElement('div'); 4433 playerHeader.classList.add('vjs-header'); 4434 var lyricsLink = document.createElement('a'); 4435 //lyricsLink.classList.add('vjs-view-lyrics'); 4436 lyricsLink.id = 'vjs-view-lyrics'; 4437 lyricsLink.href = '/' + Drupal.settings.languagevarq + '/node/' + lyricsNid; 4438 lyricsLink.innerHTML = Drupal.t('View lyrics'); 4439 playerHeader.style.cssText = 'width: ' + maxwidth + 'px;'; 4440 playerHeader.appendChild(lyricsLink); 4441 playerWrapper.appendChild(playerHeader);
4442 closeButton.style.top = '-21px'; 4443 } 4444 4445 4446 // player 4447 var player = document.createElement('video'); 4448 player.id = 'vjs-player'; 4449 player.classList.add('video-js', 'vjs-player', 'vjs-default-skin', 'vjs-youtube'); 4450 // player.classList.add('vjs-player', 'vjs-default-skin', 'video-js'); 4451 // player.classList.add('vjs-player'); 4452 player.style.cssText = 'max-width: ' + maxwidth + 'px; max-height: ' + maxheight + 'px;'; 4453 playerWrapper.appendChild(player); 4454 document.body.appendChild(playerWrapper); 4455 4456 // Keep video above the sticky ad (Snigel adhesive) 4457 function adjustForAdhesive() { 4458 playerWrapper.style.bottom = (baseOffsetY + getAdhesiveHeight()) + 'px'; 4459 } 4460 4461 // Watch for adhesive appearing/hiding (class changes, DOM additions) 4462 var adhesiveObserver = new MutationObserver(function() { 4463 adjustForAdhesive(); 4464 // Once adhesive found, also watch its size 4465 var ad = document.querySelector('.snigel-adhesive.bottom'); 4466 if (ad && !ad._resizeObserved) { 4467 ad._resizeObserved = true; 4468 new ResizeObserver(adjustForAdhesive).observe(ad); 4469 } 4470 }); 4471 adhesiveObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); 4472 } 4473 4474 Promise.all([ 4475 loadJSFile(path + "/video.min.js"), 4476 loadJSFile('/sites/all/modules/custom/ltsubtitles/js/ltsubtitles.view.js') 4477 ]).then(() => { 4478 VJS = videojs; 4479 vjsSongs = []; 4480 4481 var autoplay = true; 4482 4483 // ÐимикаÑмо autoplay длÑ: 4484 // - мобÑлÑÐ½Ð¸Ñ Ð¿ÑиÑÑÑоÑв 4485 // - пеÑÑого запÑÑÐºÑ Ð² ÑедакÑоÑÑ ÑÑбÑайÑлÑв 4486 if (deviceType() == 'mobile' || window.temporaryDisableAutoplay) { 4487 autoplay = false; 4488 } 4489 4490 var options = { 4491 "techOrder": ["youtube"], 4492 "sources": [ 4493 { 4494 "type": "video/youtube", 4495 "src": 'https://www.youtube.com/watch?v=' + youtube 4496 }], 4497 "youtube": { 4498 autohide: 1, 4499 disablekb: 1 4500 }, 4501 autoplay: autoplay, 4502 controls: true, 4503 'height': maxheight, 4504 'width': maxwidth, 4505 'html5': { 4506 nativeTextTracks: false 4507 }, 4508 'controlBar': { 4509 'children': [ 4510 'playToggle', 4511 'volumePanel', 4512 'currentTimeDisplay', 4513 'timeDivider', 4514 'durationDisplay', 4515 'progressControl', 4516 'playbackRateMenuButton', 4517 'fullscreenToggle' 4518 ] 4519 }, 4520 'playbackRates': [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2], 4521 'userActions': { 4522 'hotkeys': function(event) { 4523 // Left arrow - seek back 5s 4524 if (event.which === 37) { 4525 event.preventDefault(); 4526 this.currentTime(Math.max(0, this.currentTime() - 5)); 4527 // Right arrow - seek forward 5s 4528 } else if (event.which === 39) { 4529 event.preventDefault(); 4530 this.currentTime(Math.min(this.duration(), this.currentTime() + 5)); 4531 // J - seek back 10s 4532 } else if (event.which === 74) { 4533 event.preventDefault(); 4534 this.currentTime(Math.max(0, this.currentTime() - 10)); 4535 // L - seek forward 10s 4536 } else if (event.which === 76) { 4537 event.preventDefault(); 4538 this.currentTime(Math.min(this.duration(), this.currentTime() + 10)); 4539 // < (Shift+,) - decrease playback speed 4540 } else if (event.which === 188 && event.shiftKey) { 4541 event.preventDefault(); 4542 var rates = this.playbackRates(); 4543 var current = this.playbackRate(); 4544 for (var i = rates.length - 1; i >= 0; i--) { 4545 if (rates[i] < current) { this.playbackRate(rates[i]); break; } 4546 } 4547 // > (Shift+.) - increase playback speed 4548 } else if (event.which === 190 && event.shiftKey) { 4549 event.preventDefault(); 4550 var rates = this.playbackRates(); 4551 var current = this.playbackRate(); 4552 for (var i = 0; i < rates.length; i++) { 4553 if (rates[i] > current) { this.playbackRate(rates[i]); break; } 4554 } 4555 // Space or K - play/pause 4556 } else if (event.which === 32 || event.which === 75) { 4557 event.preventDefault(); 4558 this.paused() ? this.play() : this.pause(); 4559 } 4560 } 4561 } 4562 }; 4563 4564 // Add custom CSS for playback rate button 4565 var style = document.createElement('style'); 4566 style.textContent = ` 4567 .vjs-playback-rate .vjs-playback-rate-value { 4568 font-size: 1em; 4569 line-height: 3; 4570 } 4571 .vjs-menu-button-popup .vjs-menu .vjs-menu-content { 4572 max-height: 25em; 4573 } 4574 .video-js .vjs-menu-button-popup .vjs-menu .vjs-menu-item { 4575 font-size: 0.8em; 4576 } 4577 .vjs-control-bar .vjs-fullscreen-control { 4578 position: absolute !important; 4579 right: 0 !important; 4580 } 4581 4582 .video-js .vjs-fullscreen-control { 4583 width: 3.5em; 4584 float: right !important; 4585 } 4586 .vjs-control-bar { 4587 display: flex; 4588 justify-content: flex-start; 4589 padding-right: 3.5em; 4590 } 4591 `; 4592 document.head.appendChild(style); 4593 4594 vjsPlayer = videojs('vjs-player', options, function onPlayerReady() { 4595 onReady(); 4596 4597 $(document).trigger('vjsPlayerReady', [this]); 4598 4599 delete window.temporaryDisableAutoplay; 4600 4601 // Replace spinner with pause icon when player is ready 4602 $('.video-icon-player.isactive').each(function() { 4603 var $this = $(this); 4604 if ($this.find('.ispinner').length > 0) { 4605 $this.html('<div class="video-icon playing" style="width: 24px; height: 24px;"></div>'); 4606 } 4607 }); 4608 4609 this.on('firstplay', function () { 4610 onFirstPlay(this); 4611 }); 4612 this.on('play', function () { 4613 onPlay(this); 4614 }); 4615 this.on('pause', function () { 4616 onPause(this); 4617 }); 4618 this.on('stop', function () { 4619 onStop(this); 4620 }); 4621 this.on('ended', function () { 4622 // onClose(); 4623 }); 4624 4625 // Handle video errors - auto-flag unavailable videos 4626 // VideoJS YouTube tech doesn't trigger standard 'error' event, 4627 // so we check for errors after a short delay. 4628 // videojs-youtube encodes YT error codes as 1000 + original (see
4629 // Youtube.js error() method), so we subtract to recover it. 4630 var player = this; 4631 var errorCheckInterval = setInterval(function() { 4632 var error = player.error(); 4633 if (error) { 4634 clearInterval(errorCheckInterval); 4635 var ytCode = error.code - 1000; 4636 if (lyricsNid && [100, 101, 150].indexOf(ytCode) !== -1) { 4637 $.ajax({ 4638 url: '/flag_video.php', 4639 type: 'GET', 4640 data: { nid: lyricsNid, code: ytCode }, 4641 dataType: 'json' 4642 }); 4643 } 4644 } 4645 }, 1000); 4646 // Clear interval after 10 seconds to prevent memory leak 4647 setTimeout(function() { 4648 clearInterval(errorCheckInterval); 4649 }, 10000); 4650 4651 // highlight subtitles with lyrics 4652 // Check both old #lyrics-settings-video and new .video-icon-player.active-player 4653 var $videoEl = $("#lyrics-settings-video").length ? $("#lyrics-settings-video") : $(".video-icon-player.active-player[nid='" + lyricsNid + "']"); 4654 if ($videoEl.length && $videoEl.attr('youtube') == youtube) { 4655 loadSubtitles('/' + Drupal.settings.languagevarq + '/callback/subtitles/' + lyricsNid + '/' + youtube) 4656 .then(() => { 4657 const data = window.ltSubtitlesData; 4658 4659 if (data.webtvv) { 4660 const originalTVV = data.webtvv; 4661 4662 const ltvtt = transformVTT(originalTVV); 4663 const cues = parseVTT(ltvtt); 4664 4665 addIdsToLines(cues); 4666 4667 // Add video seek icons to lines (for seek-on-click functionality) 4668 if (typeof addVideoSeekIcons === 'function') { 4669 addVideoSeekIcons(cues); 4670 } 4671 4672 // Enable video seek mode (show icons on hover) - player is loaded 4673 if (typeof enableVideoSeekMode === 'function') { 4674 enableVideoSeekMode(); 4675 } 4676 4677 const throttledTimeUpdate = throttle(function() { 4678 timeUpdateHandler.call(this, cues); 4679 }, 100); 4680 4681 this.on('timeupdate', throttledTimeUpdate); 4682 } 4683 }) 4684 .catch(error => { 4685 console.error("Error loading subtitles in onPlayerReady:", error); 4686 // Clean up placeholder icons if subtitles failed to load 4687 document.querySelectorAll('.video-seek-icon.-placeholder').forEach(function(icon) { icon.remove(); }); 4688 delete window.ltDeferredSeekLine; 4689 }); 4690 } 4691 4692 }); 4693 4694 4695 if (deviceType() != 'mobile') { 4696 4697 // 'prev' button 4698 var button = videojs.getComponent('Button'); 4699 var previousButton = videojs.extend(button, { 4700 constructor: function() { 4701 button.apply(this, arguments); 4702 this.controlText(Drupal.t('Previous')); 4703 this.addClass('vjs-icon-previous-item'); 4704 }, 4705 handleClick: function() { 4706 vjsPlayer.playlist.previous(); 4707 } 4708 }); 4709 videojs.registerComponent('previousButton', previousButton); 4710 vjsPlayer.getChild('controlBar').addChild('previousButton', {}, 0); 4711 4712 // 'next' button 4713 var button = videojs.getComponent('Button'); 4714 var nextButton = videojs.extend(button, { 4715 constructor: function() { 4716 button.apply(this, arguments); 4717 this.controlText(Drupal.t('Next')); 4718 this.addClass('vjs-icon-next-item'); 4719 }, 4720 handleClick: function() { 4721 vjsPlayer.playlist.next(); 4722 } 4723 }); 4724 videojs.registerComponent('nextButton', nextButton); 4725 vjsPlayer.getChild('controlBar').addChild('nextButton', {}, 2); 4726 4727 // 'playlist' button 4728 if ($(".video-icon-player.active-player").length > 1) { 4729 var button = videojs.getComponent('Button'); 4730 var playlistButton = videojs.extend(button, { 4731 constructor: function() { 4732 button.apply(this, arguments); 4733 this.controlText(Drupal.t('Playlist')); 4734 this.addClass('vjs-icon-chapters'); 4735 }, 4736 handleClick: function() { 4737 // this.player().dispose(); 4738 var playlist = $('#vjs-playlist'); 4739 if (playlist.is(':visible')) { 4740 playlist.slideUp(); 4741 } else { 4742 playlist.slideDown(); } 4743 } 4744 }); 4745 videojs.registerComponent('playlistButton', playlistButton); 4746 vjsPlayer.getChild('controlBar').addChild('playlistButton', {}); 4747 } 4748 4749 var playlistButton = vjsPlayer.controlBar.getChild('playlistButton'); 4750 4751 // змÑнÑÑмо поÑÑдок кнопок ÑÑлÑки ÑкÑо playlistButton ÑÑнÑÑ 4752 if (playlistButton) { 4753 var fullscreenToggle = vjsPlayer.controlBar.getChild('FullscreenToggle'); 4754 var fullscreenToggleEl = fullscreenToggle.el(); 4755 4756 fullscreenToggleEl.parentNode.insertBefore(playlistButton.el(), fullscreenToggleEl); 4757 } 4758 4759 } 4760 4761 4762 //vjsPlayer.controlBar.removeChild(vjsPlayer.controlBar.getChild('PictureInPictureToggle')); 4763 4764 4765 var currentSong = {}; 4766 var youtubes = []; 4767 var index = 0; 4768 $('.video-icon-player, #lyrics-settings-video').each(function () { 4769 var src = $(this).attr('youtube'); 4770 4771 if (youtubes.indexOf(src) == -1) { 4772 youtubes.push(src); 4773 4774 var lyricsNid = $(this).attr('nid'); 4775 if (src === youtube) { 4776 currentSong.name = $(this).attr("songtitle") || $(this).parent().find('a').text(); 4777 //currentSong.poster = '/files/styles/square_thumbnail/public/Odyn-v-kanoe-foto-1_0.jpg'; 4778 currentSong.sources = [{src: 'https://www.youtube.com/watch?v=' + src, type: 'video/youtube'}]; 4779 currentSong.lyricsNid = lyricsNid; 4780 currentSong.index = index; 4781 //return; 4782 } 4783 var song = {}; 4784 song.name = $(this).attr("songtitle") || $(this).parent().find('a').text(); 4785 //song.poster = $(this).attr('img'); 4786 song.sources = [{src: 'https://www.youtube.com/watch?v=' + src, type: 'video/youtube'}]; 4787 song.lyricsNid = lyricsNid; 4788 song.index = index; 4789 vjsSongs.push(song); 4790 4791 index++; 4792 } 4793 4794 }); 4795 4796 4797 //vjsSongs.unshift(currentSong); 4798 4799 if ((vjsSongs.length > 1) && (deviceType() != 'mobile')) { 4800 vjsPlayer.playlist(vjsSongs); 4801 vjsPlayer.playlistUi({horizontal: true}); 4802 vjsPlayer.playlist.autoadvance(0); 4803 4804 vjsPlayer.playlist.currentItem(currentSong.index); 4805 } 4806 4807 }).catch(error => { 4808 console.error("Error:", error); 4809 }); 4810} 4811 4812$(document).ready(function () { 4813 4814 $(document).on("click", "#artist-settings-video", function (e) { 4815 4816 // If currently playing (button has isactive class), pause the video 4817 if ($(this).hasClass("isactive")) { 4818 // Find and click the currently active video button to pause it 4819 if ($(".video-icon-player.isactive").length) { 4820 $(".video-icon-player.isactive:first").trigger( "click" ); 4821 } 4822 // Update our button state immediately 4823 $(this).removeClass("isactive"); 4824 setVideoIconState($(this), false); 4825 } 4826 else { 4827 // If not playing, start playing the first video 4828 if ($(".video-icon-player.isactive").length) { 4829 $(".video-icon-player.isactive:first").trigger( "click" ); 4830 } 4831 else { 4832 $(".video-icon-player:first").trigger( "click" ); 4833 } 4834 // Update our button state immediately 4835 $(this).addClass("isactive"); 4836 setVideoIconState($(this), true); 4837 } 4838 }); 4839 4840 // click statistics 4841 4842 $(document).on("click", ".video-icon-player.active-player", function (e) { 4843 if (typeof gtag !== 'undefined') gtag('event', 'video_icon_play', { 4844 'video_category': 'sticky-youtube-icon' 4845 }); 4846 }); 4847 4848 4849 // play sticky video 4850 $(document).on("click", "#lyrics-settings-video, .video-icon-player.active-player", function (e) { 4851 4852 if (typeof vjsPlayer !== 'undefined') { 4853 var $newsource = 'https://www.youtube.com/watch?v=' + $(this).attr('youtube'); 4854 if (vjsPlayer.currentSrc() === $newsource) { 4855 if (!vjsPlayer.paused()) { 4856 vjsPlayer.pause(); 4857 setVideoIconState($(this), false); 4858 $("#artist-settings-video").removeClass("isactive"); 4859 setVideoIconState($("#artist-settings-video"), false); 4860 } 4861 else { 4862 vjsPlayer.play(); 4863 setVideoIconState($(this), true); 4864 $("#artist-settings-video").addClass("isactive"); 4865 setVideoIconState($("#artist-settings-video"), true); 4866 } 4867 } 4868 else { 4869 $("#lyrics-settings-video, .video-icon-player.active-player").removeClass("isactive"); 4870 $("#artist-settings-video").removeClass("isactive"); 4871 setVideoIconState($("#artist-settings-video"), false); 4872 $(this).addClass("isactive"); 4873 vjsPlayer.src({ 4874 "type": "video/youtube", 4875 "src": $newsource 4876 }); 4877 vjsPlayer.play(); 4878 setVideoIconState($(this), true); 4879 $("#artist-settings-video").addClass("isactive"); 4880 setVideoIconState($("#artist-settings-video"), true); 4881 } 4882 $('#vjs-player-wrapper').show(); 4883 } 4884 else { 4885 ispinner($(this)); 4886 $(this).addClass("isactive"); 4887 stickyVideoPlayer($(this)); 4888 } 4889 }); 4890 4891 $(document).on("click", ".vjs-close", function (e) { 4892 e.preventDefault(); 4893 4894 onClose(); 4895 4896 return false;
4897 }); 4898}); 4899 4900 4901function throttle(func, limit) { 4902 let lastFunc; 4903 let lastRan; 4904 return function() { 4905 const context = this; 4906 const args = arguments; 4907 if (!lastRan) { 4908 func.apply(context, args); 4909 lastRan = Date.now(); 4910 } else { 4911 clearTimeout(lastFunc); 4912 lastFunc = setTimeout(function() { 4913 if ((Date.now() - lastRan) >= limit) { 4914 func.apply(context, args); 4915 lastRan = Date.now(); 4916 } 4917 }, limit - (Date.now() - lastRan)); 4918 } 4919 } 4920} 4921 4922let currentHighlighted = null; 4923let currentHighlighted2 = null; 4924 4925 4926function timeUpdateHandler(cues) { 4927 4928 requestAnimationFrame(() => { 4929 const currentTime = this.currentTime() + LYRICS_TIME_OFFSET; 4930 let currentCue = cues.find(cue => currentTime >= cue.start && currentTime <= cue.end); 4931 4932 if (currentCue) { 4933 const newHighlightedElement = document.getElementById('l-' + currentCue.id); 4934 const newHighlightedElement2 = document.getElementById('t-' + currentCue.id); 4935 4936 if (newHighlightedElement !== currentHighlighted) { 4937 if (currentHighlighted) { 4938 currentHighlighted.classList.remove('bgh'); 4939 } 4940 if (currentHighlighted2) { 4941 currentHighlighted2.classList.remove('bgh'); 4942 } 4943 4944 if (newHighlightedElement) { 4945 newHighlightedElement.classList.add('bgh'); 4946 currentHighlighted = newHighlightedElement; 4947 } 4948 if (newHighlightedElement2) { 4949 newHighlightedElement2.classList.add('bgh'); 4950 currentHighlighted2 = newHighlightedElement2; 4951 } 4952 4953 } 4954 } else { 4955 if (currentHighlighted) { 4956 currentHighlighted.classList.remove('bgh'); 4957 currentHighlighted = null; 4958 } 4959 if (currentHighlighted2) { 4960 currentHighlighted2.classList.remove('bgh'); 4961 currentHighlighted2 = null; 4962 } 4963 } 4964 }); 4965} 4966 4967// Optimized: Use CSS classes instead of HTML replacement 4968function setVideoIconState(element, isPlaying) { 4969 var videoIcon = element.find('.video-icon'); 4970 if (isPlaying) { 4971 videoIcon.addClass('playing'); 4972 } else { 4973 videoIcon.removeClass('playing'); 4974 } 4975} 4976 4977// Legacy functions for backward compatibility - now return CSS-based icons 4978function svgPlayIcon() { 4979 return '<div class="video-icon" style="width: 24px; height: 24px;"></div>'; 4980} 4981 4982function svgPauseIcon() { 4983 return '<div class="video-icon playing" style="width: 24px; height: 24px;"></div>'; 4984} 4985 4986// Legacy function - now returns CSS-based icon 4987function svgDefaultIcon(cc) { 4988 return svgPlayIcon(); 4989} 4990 4991function svgSubtitlesIcon() { 4992 return '<div class="video-icon subtitles-icon" style="width: 24px; height: 24px;"></div>'; 4993} 4994;/*})'"*/;/*})'"*/ 4995function formatState (state) { 4996 if (!state.id) { 4997 return state.text; 4998 } 4999 var baseUrl = "/user/pages/images/flags"; 5000 var $state = $( 5001 '<span><img src="' + baseUrl + '/' + state.element.value.toLowerCase() + '.png" class="img-flag" /> ' + state.text + '</span>' 5002 ); 5003 return $state; 5004}; 5005 5006 5007function fitFooterSelectToContent() { 5008 var $wrap = $('.footer__select'); 5009 if (!$wrap.length) return; 5010 var $sel = $wrap.find('select'); 5011 if (!$sel.length) return; 5012 5013 var text = $sel.find('option:selected').text(); 5014 if (!text) return; 5015 5016 var cs = window.getComputedStyle($sel[0]); 5017 var $temp = $('<span>').text(text).css({ 5018 visibility: 'hidden', 5019 position: 'absolute', 5020 whiteSpace: 'nowrap', 5021 fontFamily: cs.fontFamily, 5022 fontSize: cs.fontSize, 5023 fontWeight: cs.fontWeight, 5024 letterSpacing: cs.letterSpacing 5025 }).appendTo('body'); 5026 var textWidth = $temp[0].getBoundingClientRect().width; 5027 $temp.remove(); 5028 5029 var padL = parseFloat(cs.paddingLeft) || 0; 5030 var padR = parseFloat(cs.paddingRight) || 0; 5031 var border = (parseFloat(cs.borderLeftWidth) || 0) + (parseFloat(cs.borderRightWidth) || 0); 5032 var needed = Math.ceil(textWidth + padL + padR + border); 5033 var current = parseFloat(window.getComputedStyle($wrap[0]).width); 5034 5035 if (needed > current) { 5036 $wrap[0].style.width = needed + 'px'; 5037 } 5038} 5039 5040$(document).ready(function () { 5041 fitFooterSelectToContent(); 5042 var resizeT; 5043 $(window).on('resize', function () { 5044 clearTimeout(resizeT); 5045 resizeT = setTimeout(fitFooterSelectToContent, 150); 5046 }); 5047 5048 // Interface language select / footer 5049 $('#lang-chooser-select').bind('change', function () { 5050 var url = $(this).val(); 5051 var lang = $('option:selected', this).attr('ltlang'); 5052 var languagevarq = Drupal.settings.languagevarq; 5053 5054 // Show loading spinner on the lang icon 5055 $(this).closest('.footer__select').find('.icon:not(.-arrow) img') 5056 .attr('src', '/sites/all/themes/lt7/img/icons/spinner-small.gif'); 5057 5058 // ga('send', 'event', 'Interface language', 'footer', languagevarq + ' -> ' + lang); 5059 gtag('event', 'interface_language_switch', { 5060 'interface_language_switch_location': 'footer' 5061 }); 5062 5063 language_save_preferences(lang); 5064 5065 if (url) { 5066 window.location = url; 5067 } 5068 return false;
5069 }); 5070 5071 // Interface language select / header 5072 $('#lang-chooser-select-header').bind('change', function () { 5073 var url = $(this).val(); 5074 var lang = $('option:selected', this).attr('ltlang'); 5075 var languagevarq = Drupal.settings.languagevarq; 5076 5077 // ga('send', 'event', 'Interface language', 'footer', languagevarq + ' -> ' + lang); 5078 gtag('event', 'interface_language_switch', { 5079 'interface_language_switch_location': 'header' 5080 }); 5081 5082 language_save_preferences(lang); 5083 5084 if (url) { 5085 window.location = url; 5086 } 5087 return false; 5088 }); 5089 5090 // Interface language select / panel 5091 5092 $(document).on('change', '#lang-chooser-select-panel, #lang-chooser-select-usermenu', function () { 5093 var url = $(this).val(); 5094 var lang = $('option:selected', this).attr('ltlang'); 5095 var languagevarq = Drupal.settings.languagevarq; 5096 5097 // ga('send', 'event', 'Interface language', 'footer', languagevarq + ' -> ' + lang); 5098 gtag('event', 'interface_language_switch', { 5099 'interface_language_switch_location': 'left-panel' 5100 }); 5101 5102 language_save_preferences(lang); 5103 5104 if (url) { 5105 window.location = url; 5106 } 5107 return false; 5108 }); 5109 5110 5111 $('.block-switchtheme .form-item-theme select').change(function() { 5112 this.form.submit(); 5113 }); 5114 5115 $('#selectedlanguage').on('click', function (e) { 5116 e.preventDefault(); 5117 $('div#selectlanguages ul').addClass('selectedlanguageopen'); 5118 $('div#selectlanguages ul li').show(); 5119 }); 5120 5121}); 5122 5123function language_save_preferences(lang) { 5124 5125 languagevarq = Drupal.settings.languagevarq; 5126 5127 $.ajax({ 5128 type: "POST", 5129 dataType: 'json', 5130 async: false, 5131 cache: false, 5132 url: '/' + languagevarq + '/callback/change_language', 5133 data: { 5134 lang : lang, 5135 }, 5136 }); 5137 5138} 5139;/*})'"*/;/*})'"*/ 5140/** 5141* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+ 5142* <http://cherne.net/brian/resources/jquery.hoverIntent.html> 5143* 5144* @param f onMouseOver function || An object with configuration options 5145* @param g onMouseOut function || Nothing (use configuration options object) 5146* @author Brian Cherne brian(at)cherne(dot)net 5147*/ 5148(function($){$.fn.hoverIntent=function(f,g){var cfg={sensitivity:7,interval:100,timeout:0};cfg=$.extend(cfg,g?{over:f,out:g}:f);var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if((Math.abs(pX-cX)+Math.abs(pY-cY))<cfg.sensitivity){$(ob).unbind("mousemove",track);ob.hoverIntent_s=1;return cfg.over.apply(ob,[ev])}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=0;return cfg.out.apply(ob,[ev])};var handleHover=function(e){var ev=jQuery.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t)}if(e.type=="mouseenter"){pX=ev.pageX;pY=ev.pageY;$(ob).bind("mousemove",track);if(ob.hoverIntent_s!=1){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}}else{$(ob).unbind("mousemove",track);if(ob.hoverIntent_s==1){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob)},cfg.timeout)}}};return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover)}})(jQuery); 5149;/*})'"*/;/*})'"*/
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.