1!function(){"use strict";var e,t={noop:function(){},texturize:function(e){return(e=(e=(e=(e+="").replace(/'/g,"’").replace(/'/g,"’")).replace(/"/g,"”").replace(/"/g,"”").replace(/"/g,"”").replace(/[\u201D]/g,"”")).replace(/([\w]+)=&#[\d]+;(.+?)&#[\d]+;/g,'$1="$2"')).trim()},applyReplacements:function(e,t){if(e)return t?e.replace(/{(\d+)}/g,function(e,r){return void 0!==t[r]?t[r]:e}):e},getBackgroundImage:function(e){var t=document.createElement("canvas"),r=t.getContext&&t.getContext("2d");if(e){r.filter="blur(20px) ",r.drawImage(e,0,0);var o=t.toDataURL("image/png");return t=null,o}}},r=function(){function e(e,t){return Element.prototype.matches?e.matches(t):Element.prototype.msMatchesSelector?e.msMatchesSelector(t):void 0}function r(e,t,r,o){if(!e)return o();e.jpCarouselCancelFade&&e.jpCarouselCancelFade(),e.classList.remove("jp-carousel-fade"),e.style.removeProperty("display"),e.style.opacity=t,e.style.pointerEvents="none",e.offsetWidth,e.classList.add("jp-carousel-fade"),e.style.opacity=r;var a=1e3*parseFloat(getComputedStyle(e).transitionDuration)||0,n=setTimeout(function(){e.jpCarouselCancelFade=null,e.style.removeProperty("pointer-events"),o()},a+50);e.jpCarouselCancelFade=function(){clearTimeout(n),e.jpCarouselCancelFade=null}}return{closest:function(t,r){if(t.closest)return t.closest(r);var o=t;do{if(e(o,r))return o;o=o.parentElement||o.parentNode}while(null!==o&&1===o.nodeType);return null},matches:e,hide:function(e){e&&(e.style.display="none")},show:function(e){e&&(e.style.display="block")},fadeIn:function(e,o){r(e,0,1,o=o||t.noop)},fadeOut:function(e,o){o=o||t.noop,r(e,1,0,function(){e&&(e.style.display="none"),o()})},scrollToElement:function(e,t,r){if(!e||!t)return r?r():void 0;var o=t.querySelector(".jp-carousel-info-extra");o&&(o.style.minHeight=window.innerHeight-64+"px");var a=!0,n=Date.now(),i=t.scrollTop,l=Math.max(0,e.offsetTop-Math.max(0,window.innerHeight-function(e){var t=e.querySelector(".jp-carousel-info-footer"),r=e.querySelector(".jp-carousel-info-extra"),o=e.querySelector(".jp-carousel-info-content-wrapper");if(t&&r&&o){var a=window.getComputedStyle(r),n=parseInt(a.paddingTop,10)+parseInt(a.paddingBottom,10);return n=isNaN(n)?0:n,o.offsetHeight+t.offsetHeight+n}return 0}(t)))-t.scrollTop;function s(){a=!1}l=Math.min(l,t.scrollHeight-window.innerHeight),t.addEventListener("wheel",s),function e(){var c,u=Date.now(),d=(c=(u-n)/300)<.5?2*c*c:1-Math.pow(-2*c+2,2)/2,m=(d=d>1?1:d)*l;if(t.scrollTop=i+m,u<=n+300&&a)return requestAnimationFrame(e);r&&r(),o&&(o.style.minHeight=""),a=!1,t.removeEventListener("wheel",s)}()},getJSONAttribute:function(e,t){if(e&&e.hasAttribute(t))try{return JSON.parse(e.getAttribute(t))}catch{return}},convertToPlainText:function(e){var t=document.createElement("div");return t.textContent=e,t.innerHTML},stripHTML:function(e){var t=document.createElement("div");return t.innerHTML=e.replace(/<[^>]*>?/gm,""),t.textContent},emitEvent:function(e,t,r){var o;try{o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:r||null})}catch{(o=document.createEvent("CustomEvent")).initCustomEvent(t,!0,!0,r||null)}e.dispatchEvent(o)},isTouch:function(){return"ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch},prefersReducedMotion:function(){return!!window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches}}}();function o(){var o,a,n,i,l={},s={},c="",u=!1,d="div.gallery, div.tiled-gallery, ul.wp-block-gallery, ul.blocks-gallery-grid, figure.wp-block-gallery.has-nested-images, div.wp-block-jetpack-tiled-gallery, a.single-image-gallery",m=".gallery-item, .tiled-gallery-item, .blocks-gallery-item, .tiled-gallery__item",p=m+", .wp-block-image",g={},h="undefined"!=typeof wpcom&&wpcom.carousel&&wpcom.carousel.stat?wpcom.carousel.stat:t.noop,f="undefined"!=typeof wpcom&&wpcom.carousel&&wpcom.carousel.pageview?wpcom.carousel.pageview:t.noop;function v(t){if(!u)switch(t.which){case 38:t.preventDefault(),g.overlay.scrollTop-=100;break;case 40:t.preventDefault(),g.overlay.scrollTop+=100;break;case 39:t.preventDefault(),e.slideNext();break;case 37:case 8:t.preventDefault(),e.slidePrev();break;case 27:t.preventDefault(),k()}}function y(){u=!0}function w(){u=!1}function S(e){e.role="button",e.tabIndex=0,e.ariaLabel=jetpackCarouselStrings.image_label}function j(){g.overlay||(g.overlay=document.querySelector(".jp-carousel-overlay"),g.container=g.overlay.querySelector(".jp-carousel-wrap"),g.gallery=g.container.querySelector(".jp-carousel"),g.info=g.overlay.querySelector(".jp-carousel-info"),g.caption=g.
1info.querySelector(".jp-carousel-caption"),g.commentField=g.overlay.querySelector("#jp-carousel-comment-form-comment-field"),g.emailField=g.overlay.querySelector("#jp-carousel-comment-form-email-field"),g.authorField=g.overlay.querySelector("#jp-carousel-comment-form-author-field"),g.urlField=g.overlay.querySelector("#jp-carousel-comment-form-url-field"),window.innerWidth<=760&&Math.round(window.innerWidth/760*110)<40&&r.isTouch(),[g.commentField,g.emailField,g.authorField,g.urlField].forEach(function(e){e&&(e.addEventListener("focus",y),e.addEventListener("blur",w))}),g.overlay.addEventListener("click",function(e){var t,o,a=e.target,n=!!r.closest(a,".jp-carousel-close-hint"),i=!!window.matchMedia("(max-device-width: 760px)").matches;a===g.overlay?i||k():n?k():a.classList.contains("jp-carousel-image-download")?h("download_original_click"):a.classList.contains("jp-carousel-comment-login")?(t=g.currentSlide,o=t?t.attrs.attachmentId:"0",window.location.href=jetpackCarouselStrings.login_url+"%23jp-carousel-"+o):r.closest(a,"#jp-carousel-comment-form-container")?function(e){var t=e.target,o=r.getJSONAttribute(g.container,"data-carousel-extra")||{},a=g.currentSlide.attrs.attachmentId,n=document.querySelector("#jp-carousel-comment-form-submit-and-info-wrapper"),i=document.querySelector("#jp-carousel-comment-form-spinner"),s=document.querySelector("#jp-carousel-comment-form-button-submit"),c=document.querySelector("#jp-carousel-comment-form");if(g.commentField&&g.commentField.getAttribute("id")===t.getAttribute("id"))y(),r.show(n);else if(r.matches(t,'input[type="submit"]')){e.preventDefault(),e.stopPropagation(),r.show(i),c.classList.add("jp-carousel-is-disabled");var u={action:"post_attachment_comment",nonce:jetpackCarouselStrings.nonce,blog_id:o.blog_id,id:a,comment:g.commentField.value};if(!u.comment.length)return void b(jetpackCarouselStrings.no_comment_text,!1);if(1!==Number(jetpackCarouselStrings.is_logged_in)&&(u.email=g.emailField.value,u.author=g.authorField.value,u.url=g.urlField.value,1===Number(jetpackCarouselStrings.require_name_email))){if(!u.email.length||!u.email.match("@"))return void b(jetpackCarouselStrings.no_comment_email,!1);if(!u.author.length)return void b(jetpackCarouselStrings.no_comment_author,!1)}var d=new XMLHttpRequest;d.open("POST",jetpackCarouselStrings.ajaxurl,!0),d.setRequestHeader("X-Requested-With","XMLHttpRequest"),d.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"),d.onreadystatechange=function(){if(this.readyState===XMLHttpRequest.DONE&&this.status>=200&&this.status<300){var e;try{e=JSON.parse(this.response)}catch{return void b(jetpackCarouselStrings.comment_post_error,!1)}if("approved"===e.comment_status){b(jetpackCarouselStrings.comment_approved,!0);var t=function(e){for(var t=0;t<g.slides.length;t++)if(g.slides[t].attrs.attachmentId===e)return g.slides[t];return null}(a);t&&(t.attrs.commentsCount=(parseInt(t.attrs.commentsCount,10)||0)+1)}else"unapproved"===e.comment_status?b(jetpackCarouselStrings.comment_unapproved,!0):b(jetpackCarouselStrings.comment_post_error,!1);W(),delete l[a],_(a,void 0,!0),s.value=jetpackCarouselStrings.post_comment,r.hide(i),c.classList.remove("jp-carousel-is-disabled")}else b(jetpackCarouselStrings.comment_post_error,!1)};var m=[];for(var p in u)if(p){var h=encodeURIComponent(p)+"="+encodeURIComponent(u[p]);m.push(h.replace(/%20/g,"+"))}var f=m.join("&");d.send(f)}}(e):(r.closest(a,".jp-carousel-photo-icons-container")||a.classList.contains("jp-carousel-photo-title"))&&function(e){e.preventDefault();var t=e.target,o=g.info.querySelector(".jp-carousel-info-extra"),a=g.info.querySelector(".jp-carousel-image-meta"),n=g.info.querySelector(".jp-carousel-comments-wrapper"),i=g.info.querySelector(".jp-carousel-icon-info"),s=g.info.querySelector(".jp-carousel-icon-comments");function c(){s&&s.classList.remove("jp-carousel-selected"),i.classList.toggle("jp-carousel-selected"),n&&n.classList.remove("jp-carousel-show"),a&&(a.classList.toggle("jp-carousel-show"),a.classList.contains("jp-carousel-show")?o.classList.add("jp-carousel-show"):o.classList.remove("jp-carousel-show"))}function u(){if(i&&i.classList.remove("jp-carousel-selected"),s.classList.toggle("jp-carousel-selected"),a&&a.classList.remove("jp-carousel-show"),n)if(n.classList.toggle("jp-carousel-show"),n.classList.contains("jp-carousel-show")){o.classList.add("jp-carousel-show");var e=g.currentSlide;if(e){var t=l[e.attrs.attachmentId];t?t.hasMore&&T(e.attrs.attachmentId,t.nextOffset):_(e.attrs.attachmentId)}}else o.classList.remove("jp-carousel-show")}(r.closest(t,".jp-carousel-icon-info")||t.classList.contains("jp-carousel-photo-title"))&&(a&&a.classList.contains("jp-carousel-show")?r.scrollToElement(g.overlay,g.overlay,c):(c(),r.scrollToElement(g.info,g.overlay))),r.closest(t,".jp-carousel-icon-comments")&&(n&&n.classList.contains("jp-carousel-show")?r.scrollToElement(g.overlay,g.overlay,u):(u(),r.scrollToElement(g.info,g.overlay)))}(e)}),window.addEventListener("keydown",v),g.overlay.addEventListener("jp_carousel.afterOpen",function(){w(),g.slides.length<=1||(g.slides.length<=5?r.show(g.info.querySelector(".jp-swiper-pagination")):r.show(g.info.querySelector(".jp-carousel-pagination")))}),g.overlay.addEventListener("jp_carousel.beforeClose",function(){y(),document.documentElement.style.removeProperty("height"),e&&e.enable(),r.hide(g.info.querySelector(".jp-swiper-pagination")),r.hide(g.info.querySelector(".jp-carousel-pagination"))}),g.overlay.addEventListener("jp_carousel.afterClose",function(){window.history.pushState?history.pushState("",document.title,window.location.pathname+window.location.search):window.location.href="",c="",g.isOpen=!1}),g.overlay.addEventListener("touchstart",function(e){e.touches.length>1&&e.preventDefault()}))}function b(e,t){var o=g.overlay.querySelector("#jp-carousel-comment-post-results"),a="jp-carousel-comment-post-"+(t?"success":"error");o.innerHTML='<span class="'+a+'">'+e+"</span>",r.hide(g.overlay.querySelector("#jp-carousel-comment-form-spinner")),g.overlay.querySelector("#jp-carousel-comment-form").classList.remove("jp-carousel-is-disabled"),r.show(o)}function L(){var e=document.querySelectorAll("a img[data-attachment-id]");Array.prototype.forEach.call(e,function(e){var t=e.parentElement,o=t.parentElement;if(!o.classList.contains("gallery-icon")&&!r.closest(o,m)&&t.hasAttribute("href")){var a=!1;t.getAttribute("href").split("?")[0]===e.getAttribute("data-orig-file").split("?")[0]&&1===Number(jetpackCarouselStrings.single_image_gallery_media_file)&&(a=!0),t.getAttribute("href")===e.getAttribute("data-permalink")&&(a=!0),a&&(S(e),t.classList.add("single-image-gallery"),t.setAttribute("data-carousel-extra",JSON.stringify({blog_id:Number(jetpackCarouselStrings.blog_id)})))}})}function x(t,r){g.isOpen?(C(r),e.slideTo(r+1)):z(t,{startIndex:r})}function C(e){(!e||e<0||e>g.slides.length)&&(e=0),g.currentSlide=g.slides[e];var a,n,i=g.currentSlide,s=i.attrs.attachmentId;M(g.slides[e]),function(e){var t=[],r=g.slides.length;if(r>1){var o=e>0?e-1:r-1;t.push(o);var a=e<r-1?e+1:0;t.push(a)}t.forEach(function(e){var t=g.slides[e];t&&(M(t),1!==Number(jetpackCarouselStrings.display_background_image)||t.backgroundImage||F(t))})}(e),1!==Number(jetpackCarouselStrings.display_background_image)||g.slides[e].backgroundImage||F(g.slides[e]),r.hide(g.caption),function(e){var t,o,a,n,i,l,s;if(n=g.overlay.querySelector(".jp-carousel-photo-caption"),i=g.overlay.querySelector(".jp-carousel-caption"),l=g.overlay.querySelector(".jp-carousel-photo-title"),s=g.overlay.querySelector(".jp-carousel-photo-description"),r.hide(n),r.hide(i),r.hide(l),r.hide(s),t=A(e.caption)||"",o=A(e.title)||"",a=A(e.desc)||"",(t||o||a)&&(t&&(n.innerHTML=t,i.innerHTML=t,r.show(n),r.show(i)),r.stripHTML(t)===r.stripHTML(o)&&(o=""),r.stripHTML(t)===r.stripHTML(a)&&(a=""),r.stripHTML(o)===r.stripHTML(a)&&(a=""),a&&(s.innerHTML=a,r.show(s),o||t||(n.textContent=r.stripHTML(a),r.show(n))),o)){var c=r.stripHTML(o);l.textContent=c,t||(n.textContent=c,i.textContent=c,r.show(n)),r.show(l)}}
1({caption:i.attrs.caption,title:i.attrs.title,desc:i.attrs.desc}),function(e){if(!e||1!==Number(jetpackCarouselStrings.display_exif))return!1;var t=g.info.querySelector(".jp-carousel-image-meta");if(!t)return!1;var r="";for(var o in e){var a=e[o],n=jetpackCarouselStrings.meta_data||[];if(0!==parseFloat(a)&&a.length&&-1!==n.indexOf(o)){switch(o){case"focal_length":a+="mm";break;case"shutter_speed":a=E(a);break;case"aperture":a="f/"+a}r+="<li><h5>"+jetpackCarouselStrings[o]+"</h5>"+a+"</li>"}}var i=t.querySelector("ul.jp-carousel-image-exif");if(""!==r){if(!i){(i=document.createElement("ul")).className="jp-carousel-image-exif";var l=t.querySelector(".jp-carousel-title-and-caption");l&&l.nextSibling?t.insertBefore(i,l.nextSibling):t.insertBefore(i,t.firstChild)}i.innerHTML=r,i.style.removeProperty("display")}else i&&i.parentNode.removeChild(i)}(g.slides[e].attrs.imageMeta),function(e){if(!e)return!1;var r,o=[e.attrs.origWidth,e.attrs.origHeight],a=document.createElement("a");a.href=e.attrs.src.replace(/\?.+$/,""),r=null!==a.hostname.match(/^i[\d]{1}\.wp\.com$/i)?a.href:e.attrs.origFile.replace(/\?.+$/,"");var n=g.info.querySelector(".jp-carousel-download-text"),i=g.info.querySelector(".jp-carousel-image-download");n.innerHTML=t.applyReplacements(jetpackCarouselStrings.download_original,o),i.setAttribute("href",r),i.style.removeProperty("display")}(i),1===Number(jetpackCarouselStrings.display_comments)&&(a=g.slides[e].attrs.commentsOpened,n=g.info.querySelector("#jp-carousel-comment-form-container"),1===parseInt(a,10)?r.fadeIn(n):r.fadeOut(n),function(e){var t=e.attrs.attachmentId,a=g.info.querySelector(".jp-carousel-comments"),n=l[t];if(clearInterval(o),r.hide(g.info.querySelector("#jp-carousel-comments-loading")),n)return a.innerHTML=n.html,H(n.count),r.show(a),void(n.hasMore&&I()&&T(t,n.nextOffset));a.innerHTML="",r.hide(a),H(parseInt(e.attrs.commentsCount,10)||0),I()&&_(t)}(i),r.hide(g.info.querySelector("#jp-carousel-comment-post-results")));var u=g.info.querySelector(".jp-carousel-pagination");if(u&&g.slides.length>5){var d=e+1;u.innerHTML="<span>"+d+" / "+g.slides.length+"</span>"}jetpackCarouselStrings.stats&&g.isOpen&&((new Image).src=document.location.protocol+"//pixel.wp.com/g.gif?"+jetpackCarouselStrings.stats+"&post="+encodeURIComponent(s)+"&rand="+Math.random()),g.isOpen&&f(s),c="#jp-carousel-"+s,window.location.hash=c}function k(){document.body.style.overflow=a,document.documentElement.style.overflow=n,W(),y(),r.emitEvent(g.overlay,"jp_carousel.beforeClose"),window.scrollTo(window.scrollX||window.pageXOffset||0,i||0),g.isOpen=!1,e.destroy(),g.slides=[],l={},s={},g.currentSlide=void 0,g.gallery.innerHTML="",r.fadeOut(g.overlay,function(){r.emitEvent(g.overlay,"jp_carousel.afterClose")})}function q(e){if("object"!=typeof e&&(e={}),void 0===e.origFile)return"";if(void 0===e.origWidth||void 0===e.maxWidth)return e.origFile;if(!e.largeFile)return e.origFile;var t=document.createElement("a");t.href=e.largeFile;var r=/^i[0-2]\.wp\.com$/i.test(t.hostname),o=function(e,t,r){var o,a=r?e.replace(/.*=([\d]+%2C[\d]+).*$/,"$1"):e.replace(/.*-([\d]+x[\d]+)\..+$/,"$1");return"9999"===(o=a!==e?r?a.split("%2C"):a.split("x"):[t,0])[0]&&(o[0]="0"),"9999"===o[1]&&(o[1]="0"),o}(e.largeFile,e.origWidth,r),a=parseInt(o[0],10),n=parseInt(o[1],10);e.origMaxWidth=e.maxWidth,e.origMaxHeight=e.maxHeight,void 0!==window.devicePixelRatio&&window.devicePixelRatio>1&&(e.maxWidth=e.maxWidth*window.devicePixelRatio,e.maxHeight=e.maxHeight*window.devicePixelRatio);var i=e.zoomHeadroom||1;if(i>1&&(e.maxWidth=e.maxWidth*i,e.maxHeight=e.maxHeight*i),a>=e.maxWidth||n>=e.maxHeight)return e.largeFile;if(r){if(-1===e.largeFile.lastIndexOf("?"))return e.largeFile;var l=function(e){var t;try{t=new URL(e)}catch(t){return e}var r=["quality","ssl","filter","brightness","contrast","colorize","smooth"],o=Array.from(t.searchParams.entries());return t.search="",o.forEach(([e,o])=>{r.includes(e)&&t.searchParams.append(e,o)}),t}(e.largeFile);return(e.origWidth>e.maxWidth||e.origHeight>e.maxHeight)&&(e.origMaxWidth=e.maxWidth,e.origMaxHeight=e.maxHeight,l.searchParams.set("fit",e.origMaxWidth+","+e.origMaxHeight)),l.toString()}return e.origFile}function E(e){return e>=1?Math.round(10*e)/10+"s":"1/"+Math.round(1/e)+"s"}
1function A(e){return!e.match(" ")&&e.match("_")?"":e}function I(){var e=g.info.querySelector(".jp-carousel-comments-wrapper");return!!e&&e.classList.contains("jp-carousel-show")}function H(e){var t=g.info.querySelector(".jp-carousel-icon-comments .jp-carousel-has-comments-indicator");t&&(e>0?(t.innerText=e,t.classList.add("jp-carousel-show")):t.classList.remove("jp-carousel-show"))}function T(e,t){clearInterval(o),o=setInterval(function(){g.container.scrollTop+150>window.innerHeight&&(clearInterval(o),_(e,t))},300)}function _(e,t,a){var n=void 0===t;if(clearInterval(o),e&&g.currentSlide&&g.currentSlide.attrs.attachmentId===e&&(!s[e]||a)){(!t||t<1)&&(t=0);var i=g.info.querySelector(".jp-carousel-comments"),c=g.info.querySelector("#jp-carousel-comments-loading");r.show(c),n&&(r.hide(i),i.innerHTML="");var u=new XMLHttpRequest;s[e]=u;var d=function(){return s[e]===u},m=jetpackCarouselStrings.ajaxurl+"?action=get_attachment_comments&nonce="+jetpackCarouselStrings.nonce+"&id="+e+"&offset="+t;u.open("GET",m),u.setRequestHeader("X-Requested-With","XMLHttpRequest");var p=function(){r.fadeIn(i),r.fadeOut(c)};u.onload=function(){if(d()&&(delete s[e],g.currentSlide&&g.currentSlide.attrs.attachmentId===e)){var o,a=u.status>=200&&u.status<300;try{o=JSON.parse(u.responseText)}catch{}if(a&&o&&Array.isArray(o)){n&&(i.innerHTML="");for(var m=0;m<o.length;m++){var h=o[m];if(!i.querySelector("#jp-carousel-comment-"+h.id)){var f=document.createElement("div");f.classList.add("jp-carousel-comment"),f.setAttribute("id","jp-carousel-comment-"+h.id),f.innerHTML='<div class="comment-gravatar">'+h.gravatar_markup+'</div><div class="comment-content"><div class="comment-author">'+h.author_markup+'</div><div class="comment-date">'+h.date_gmt+"</div>"+h.content+"</div>",i.appendChild(f)}}var v=o.length>=10,y=t+10;v&&T(e,y);var w=i.querySelectorAll(".jp-carousel-comment").length,S=Math.max(parseInt(g.currentSlide.attrs.commentsCount,10)||0,w);o.length>0&&(r.show(i),H(S)),l[e]={html:i.innerHTML,count:S,hasMore:v,nextOffset:y},r.hide(c)}else p()}},u.onerror=function(){d()&&(delete s[e],g.currentSlide&&g.currentSlide.attrs.attachmentId===e&&p())},u.send()}}function M(e){var t=e.attrs,r=e.el.querySelector("img");if(!r.hasAttribute("data-loaded"))if(r.setAttribute("itemprop","image"),r.setAttribute("data-loaded",1),t.previewImage&&t.previewImage!==t.src){r.src=t.previewImage,r.style.filter="blur(8px)";var o=new window.Image;o.addEventListener("load",function(){r.hasAttribute("data-zoom-loaded")||(r.src=t.src),r.style.filter=""},{once:!0}),o.addEventListener("error",function(){r.style.filter=""},{once:!0}),o.src=t.src}else r.src=t.src}function F(e){var t=e.attrs.originalElement;if(t)if(t.complete&&0!==t.naturalHeight)O(e,t);else{var r=function(){t.removeEventListener("error",o),O(e,t)},o=function(){t.removeEventListener("load",r)};t.addEventListener("load",r,{once:!0}),t.addEventListener("error",o,{once:!0})}}function O(e,r){var o=t.getBackgroundImage(r);o&&(e.backgroundImage=o,e.el.style.backgroundImage="url("+o+")",e.el.style.backgroundSize="cover",e.el.style.backgroundPosition="center")}function W(){g.commentField&&(g.commentField.value="")}function N(e,o){g.slides=[];var a={width:window.innerWidth,height:window.innerHeight-64};0!==o&&null!==e[o].getAttribute("data-gallery-src")&&((new Image).src=e[o].getAttribute("data-gallery-src")),Array.prototype.forEach.call(e,function(e,o){var n=r.closest(e,"a"),i=e.getAttribute("data-orig-file")||e.getAttribute("src-orig"),l=e.getAttribute("data-attachment-id")||e.getAttribute("data-id")||"0",s=document.querySelector('img[data-attachment-id="'+l+'"] + figcaption');s=s?s.innerHTML:e.getAttribute("data-image-caption");var c={originalElement:e,attachmentId:l,commentsOpened:e.getAttribute("data-comments-opened")||"0",commentsCount:e.getAttribute("data-comments-count")||"0",imageMeta:r.getJSONAttribute(e,"data-image-meta")||{},title:e.getAttribute("data-image-title")||"",desc:e.getAttribute("data-image-description")||"",largeFile:e.getAttribute("data-large-file")||"",origFile:i||"",thumbSize:{width:e.naturalWidth,height:e.naturalHeight},caption:s||"",permalink:n&&n.getAttribute("href"),src:i||e.getAttribute("src")||""},u=r.closest(e,".tiled-gallery-item"),d=u&&u.querySelector(".tiled-gallery-caption"),m=d&&d.innerHTML;m&&(c.caption=m);var p=function(e){var t=e.getAttribute("data-orig-size")||"";if(t){var r=t.split(",");return{width:parseInt(r[0],10),height:parseInt(r[1],10)}}return{width:e.getAttribute("data-original-width")||e.getAttribute("width")||void 0,height:e.getAttribute("data-original-height")||e.getAttribute("height")||void 0}}(e);if(c.origWidth=p.width||c.thumbSize.width,c.origHeight=p.height||c.thumbSize.height,"undefined"!=typeof wpcom&&wpcom.carousel&&wpcom.carousel.generateImgSrc)c.src=wpcom.carousel.generateImgSrc(e,a);else{var h=function(e){return{origFile:c.src,origWidth:c.origWidth,origHeight:c.origHeight,maxWidth:a.width,maxHeight:a.height,largeFile:c.largeFile,zoomHeadroom:e}};c.zoomSrc=q(h(2)),c.src=q(h(1))}if(e.setAttribute("data-gallery-src",c.src),"0"!==c.attachmentId){c.title=t.texturize(c.title),c.desc=t.texturize(c.desc),c.caption=t.texturize(c.caption);var f=new Image,v=document.createElement("div");v.classList.add("swiper-slide"),v.setAttribute("itemprop","associatedMedia"),v.setAttribute("itemscope",""),v.setAttribute("itemtype","https://schema.org/ImageObject");var y=document.createElement("div");y.classList.add("swiper-zoom-container"),g.gallery.appendChild(v),v.appendChild(y),y.appendChild(f),v.setAttribute("data-attachment-id",c.attachmentId),v.setAttribute("data-permalink",c.permalink),v.setAttribute("data-orig-file",c.origFile),c.previewImage=e.currentSrc||e.getAttribute("src")||"";var w={el:v,attrs:c,index:o};g.slides.push(w)}})}function z(e,t){if(!window.JetpackSwiper){var o=document.querySelector("#jp-carousel-loading-overlay");r.show(o);var a=document.createElement("script");return a.id="jetpack-carousel-swiper-js",a.src=window.jetpackSwiperLibraryPath.url,a.async=!0,a.onload=function(){r.hide(o),R(e,t)},a.onerror=function(){r.hide(o)},void document.head.appendChild(a)}R(e,t)}function R(t,o){var c,u={imgSelector:".gallery-item [data-attachment-id], .tiled-gallery-item [data-attachment-id], img[data-attachment-id], img[data-id]",startIndex:0},d=r.getJSONAttribute(t,"data-carousel-extra");if(!d)return;const m=t.querySelectorAll(u.imgSelector);if(m.length&&(j(),!g.isOpen)){for(var p in g.isOpen=!0,a=getComputedStyle(document.body).overflow,document.body.style.overflow="hidden",n=getComputedStyle(document.documentElement).overflow,document.documentElement.style.overflow="hidden",i=window.scrollY||window.pageYOffset||0,g.container.setAttribute("data-carousel-extra",JSON.stringify(d)),h(["open","view_image"]),o||{})u[p]=o[p];-1===u.startIndex&&(u.startIndex=0),r.emitEvent(g.overlay,"jp_carousel.beforeOpen"),g.gallery.innerHTML="",l={},s={},g.overlay.style.opacity=1,g.overlay.style.display="block",N(m,u.startIndex),(e=new window.JetpackSwiper(".jp-carousel-swiper-container",{centeredSlides:!0,speed:r.prefersReducedMotion()?0:250,zoom:!0,loop:g.slides.length>1,enabled:g.slides.length>1,pagination:{el:".jp-swiper-pagination",clickable:!0},navigation:{nextEl:".jp-swiper-button-next",prevEl:".jp-swiper-button-prev"},initialSlide:u.startIndex,on:{init:function(){C(u.startIndex)}},preventClicks:!1,preventClicksPropagation:!1,preventInteractionOnTransition:!r.isTouch(),threshold:5})).on("slideChange",function(e){g.isOpen&&(C(e.realIndex),g.overlay.classList.remove("jp-carousel-hide-control
1s"))}),e.on("zoomChange",function(e,t){t>1&&(function(e){if(e&&e.attrs.zoomSrc&&e.attrs.zoomSrc!==e.attrs.src){var t=e.el.querySelector("img");t&&!t.hasAttribute("data-zoom-loaded")&&(t.setAttribute("data-zoom-loaded",1),t.src=e.attrs.zoomSrc)}}(g.currentSlide),g.overlay.classList.add("jp-carousel-hide-controls")),1===t&&g.overlay.classList.remove("jp-carousel-hide-controls")}),e.on("doubleTap",function(e){if(clearTimeout(c),1===e.zoom.scale)var t=setTimeout(function(){g.overlay.classList.remove("jp-carousel-hide-controls"),clearTimeout(t)},150)}),e.on("tap",function(){e.zoom.scale>1&&(c=setTimeout(function(){g.overlay.classList.toggle("jp-carousel-hide-controls")},150))}),r.fadeIn(g.overlay,function(){r.emitEvent(g.overlay,"jp_carousel.afterOpen")})}}function P(e){if("click"!==e.type){if("keydown"===e.type){const t=document.activeElement.parentElement,r=t&&t.classList.contains("tiled-gallery__item");" "!==e.key&&"Enter"!==e.key||!r||$(e)}}else $(e)}function D(e){return(e||"").split("?")[0].replace(/\/$/,"")}function J(e){"A"===e.tagName&&(e=e.querySelector("img")||e);var t=e.parentElement,o=t?t.parentElement:null,a=null;if(o&&o.classList.contains("wp-block-image")?a=t.getAttribute("href"):t&&t.classList.contains("wp-block-image")&&t.querySelector(":scope > a")&&(a=t.querySelector(":scope > a").getAttribute("href")),a){var n=D(a),i=D(e.getAttribute("data-orig-file")),l=D(e.getAttribute("data-permalink"));if(n!==i&&n!==l)return!1}return!(t&&t.classList.contains("gallery-caption")||r.matches(t,"figcaption"))}function $(e){if(window.CSS&&window.CSS.supports&&window.CSS.supports("display","grid")){var t,o=e.target,a=r.closest(o,d);if(a){if(!(t=a)||!t.getAttribute("data-carousel-extra"))return;if(!J(o))return;document.documentElement.style.height="auto",e.preventDefault(),e.stopPropagation();var n=r.closest(o,p),i=Array.prototype.indexOf.call(a.querySelectorAll(p),n);z(a,{startIndex:i})}}}document.body.addEventListener("click",P),document.body.addEventListener("keydown",P),document.querySelectorAll(m+"img").forEach(function(e){J(e)&&S(e)}),1===Number(jetpackCarouselStrings.single_image_gallery)&&(L(),document.body.addEventListener("is.post-load",function(){L()})),window.addEventListener("hashchange",function(){var e=/jp-carousel-(\d+)/;if(window.location.hash&&e.test(window.location.hash)){if(window.location.hash!==c||!g.isOpen)if(window.location.hash&&g.gallery&&!g.isOpen&&history.back)history.back();else{c=window.location.hash;for(var t=window.location.hash.match(e),r=parseInt(t[1],10),o=document.querySelectorAll(d),a=0;a<o.length;a++){for(var n,i=o[a],l=i.querySelectorAll("img"),s=0;s<l.length;s++)if(parseInt(l[s].getAttribute("data-attachment-id"),10)===r||parseInt(l[s].getAttribute("data-id"),10)===r){n=s;break}if(void 0!==n){x(i,n);break}}}}else g.isOpen&&k()}),window.location.hash&&r.emitEvent(window,"hashchange")}"loading"!==document.readyState?o():document.addEventListener("DOMContentLoaded",o)}();;
vendor: 15,088 bytes, lines 2-3
2/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ 3!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",F=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",B=new RegExp(M+"+","g"),$=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="<a id='"+S+"'></a><select id='"+S+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&j.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+"").replace(re,ie)},se.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(D),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}
vendor: 4,356 bytes, line 3
3:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(B," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(h,e,t,g,v){var y="nth"!==h.slice(0,3),m="last"!==h.slice(-4),x="of-type"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?"nextSibling":"previousSibling",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l="only"===h&&!u&&"nextSibling"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if(1===a.nodeType&&++d&&a===e){i[h]=[k,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1]),!1===d)while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e))break;return(d-=v)===g||d%g==0&&0<=d/g}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error("unsupported pseudo: "+e);return a[S]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,"$1"));return s[S]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||"")||se.error("unsupported lang: "+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);function me(){}function xe(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&"parentNode"===c,p=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[k,p];
vendor: 10,068 bytes, line 3
3if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[S]||(e[S]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===k&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[S]&&(v=Ce(v)),y&&!y[S]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v){i=Te(p,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a))}if(e){if(y||d){if(y){i=[],o=p.length;while(o--)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}o=p.length;while(o--)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return Ce(1<s&&we(c),1<s&&xe(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&xe(e))}c.push(t)}return we(c)}return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($," ")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=A[e+" "];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[S]?i.push(a):o.push(a);(a=A(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=k+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==C||(T(o),n=!E);while(s=v[a++])if(s(o,t||C,n)){r.push(o);break}i&&(k=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(k=h,w=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=G.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=S.split("").sort(D).join("")===S,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement("fieldset"))}),ce(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||fe("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||fe(R,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);S.find=d,S.expr=d.selectors,S.expr[":"]=S.expr.pseudos,S.uniqueSort=S.unique=d.uniqueSort,S.text=d.getText,S.isXMLDoc=d.isXML,S.contains=d.contains,S.escapeSelector=d.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&S(e).is(n))break;r.push(e)}return r},T=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},k=S.expr.match.needsContext;function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var N=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1<i.call(n,e)!==r}):S.filter(n,e,r)}S.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?S.find.matchesSelector(r,e)?[r]:[]:S.find.matches(e,S.grep(t,function(e){return 1===e.nodeType}))},S.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(S(e).filter(function(){for(t=0;t<r;t++)if(S.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)S.find(e,i[t],n);return 1<r?S.uniqueSort(n):n},filter:function(e){return this.pushStack(D(this,e||[],!1))},not:function(e){return this.pushStack(D(this,e||[],!0))},is:function(e){return!!D(this,"string"==typeof e&&k.test(e)?S(e):e||[],!1).length}});var j,q=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(S.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&S(e);if(!k.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&S.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?S.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?i.call(S(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(S.uniqueSort(S.merge(this.get(),S(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),S.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,"parentNode")},parentsUntil:function(e,t,n){return h(e,"parentNode",n)},next:function(e){return O(e,"nextSibling")},prev:function(e){return O(e,"previousSibling")},nextAll:function(e){return h(e,"nextSibling")},prevAll:function(e){return h(e,"previousSibling")},nextUntil:function(e,t,n){return h(e,"nextSibling",n)},prevUntil:function(e,t,n){return h(e,"previousSibling",n)},siblings:function(e){return T((e.parentNode||{}).firstChild,e)},children:function(e){return T(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(A(e,"template")&&(e=e.content||e),S.merge([],e.childNodes))}},function(r,i){S.fn[r]=function(e,t){var n=S.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=S.filter(t,n)),1<this.length&&(H[r]||S.uniqueSort(n),L.test(r)&&n.reverse()),this.pushStack(n)}});var P=/[^\x20\t\r\n\f]+/g;function R(e){return e}function M(e){throw e}function I(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}S.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},S.each(e.match(P)||[],function(e,t){n[t]=!0}),n):S.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){S.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return S.each(arguments,function(e,t){var n;while(-1<(n=S.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<S.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},S.extend({Deferred:function(e){var o=[["notify","progress",S.Callbacks("memory"),S.Callbacks("memory"),2],["resolve","done",S.Callbacks("once memory"),S.Callbacks("once memory"),0,"resolved"],["reject","fail",S.Callbacks("once memory"),S.Callbacks("once memory"),1,"rejected"]],i="pe
vendor: 8,547 bytes, line 3
3nding",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return S.Deferred(function(r){S.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,R,s),l(u,o,M,s)):(u++,t.call(e,l(u,o,R,s),l(u,o,M,s),l(u,o,R,o.notifyWith))):(a!==R&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){S.Deferred.exceptionHook&&S.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==M&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(S.Deferred.getStackHook&&(t.stackTrace=S.Deferred.getStackHook()),C.setTimeout(t))}}return S.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:R,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:R)),o[2][3].add(l(0,e,m(n)?n:M))}).promise()},promise:function(e){return null!=e?S.extend(e,a):a}},s={};return S.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=S.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(I(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||m(i[t]&&i[t].then)))return o.then();while(t--)I(i[t],a(t),o.reject);return o.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;S.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&W.test(e.name)&&C.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},S.readyException=function(e){C.setTimeout(function(){throw e})};var F=S.Deferred();function B(){E.removeEventListener("DOMContentLoaded",B),C.removeEventListener("load",B),S.ready()}S.fn.ready=function(e){return F.then(e)["catch"](function(e){S.readyException(e)}),this},S.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--S.readyWait:S.isReady)||(S.isReady=!0)!==e&&0<--S.readyWait||F.resolveWith(E,[S])}}),S.ready.then=F.then,"complete"===E.readyState||"loading"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(S.ready):(E.addEventListener("DOMContentLoaded",B),C.addEventListener("load",B));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===w(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(S(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},_=/^-ms-/,z=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function X(e){return e.replace(_,"ms-").replace(z,U)}var V=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function G(){this.expando=S.expando+G.uid++}G.uid=1,G.prototype={cache:function(e){var t=e[this.expando];return t||(t={},V(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[X(t)]=n;else for(r in t)i[X(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][X(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(X):(t=X(t))in r?[t]:t.match(P)||[]).length;while(n--)delete r[t[n]]}(void 0===t||S.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!S.isEmptyObject(t)}};var Y=new G,Q=new G,J=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,K=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(K,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:J.test(i)?JSON.parse(i):i)}catch(e){}Q.set(e,t,n)}else n=void 0;return n}S.extend({hasData:function(e){return Q.hasData(e)||Y.hasData(e)},data:function(e,t,n){return Q.access(e,t,n)},removeData:function(e,t){Q.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),S.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=Q.get(o),1===o.nodeType&&!Y.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=X(r.slice(5)),Z(o,r,i[r]));Y.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){Q.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=Q.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){Q.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){Q.remove(this,e)})}}),S.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,S.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=S.queue(e,t),r=n.length,i=n.shift(),o=S._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){S.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Y.get(e,n)||Y.access(e,n,{empty:S.Callbacks("once memory").add(function(){Y.remove(e,[t+"queue",n])})})}}),S.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?S.queue(this[0],t):void 0===n?this:this.each(function(){var e=S.queue(this,t,n);S._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&S.dequeue(this,t)})},dequeue:function(e){return this.each(function(){S.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=S.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=Y.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,te=new RegExp("^(?:([+-])=|)("+ee+")([a-z%]*)$","i"),ne=["Top","Right","Bottom","Left"],re=E.documentElement,ie=function(e){return S.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return S.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&ie(e)&&"none"===S.css(e,"display")};function se(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return S.css(e,t,"")},u=s(),l=n&&n[3]||(S.cssNumber[t]?"":"px"),c=e.nodeType&&(S.cssNumber[t]||"px"!==l&&+u)&&te.exec(S.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)S.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,S.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ue={};function le(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=Y.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ue[s])||(o=a.body.appendChild(a.createElement(s)),u=S.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ue[s]=u)))):"none"!==n&&(l[c]="none",Y.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}S.fn.extend({show:function(){return le(this,!0)},hide:function(){return le(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?S(this).show():S(this).hide()})}});var ce,fe,pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone
vendor: 51,417 bytes, line 3
3=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="<textarea>x</textarea>",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="<option></option>",y.option=!!ce.lastChild;var ge={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],"globalEval",!t||Y.get(t[n],"globalEval"))}ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td,y.option||(ge.optgroup=ge.option=[1,"<select multiple='multiple'>","</select>"]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===w(o))S.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+S.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;S.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<S.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}var be=/^key/,we=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ce(){return!0}function Ee(){return!1}function Se(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function ke(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)ke(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ee;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return S().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=S.guid++)),e.each(function(){S.event.add(this,t,i,r,n)})}function Ae(e,i,o){o?(Y.set(e,i,!1),S.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(S.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Y.set(this,i,{value:S.event.trigger(S.extend(r[0],S.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&S.event.add(e,i,Ce)}S.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.get(t);if(V(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&S.find.matchesSelector(re,i),n.guid||(n.guid=S.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof S&&S.event.triggered!==e.type?S.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(P)||[""]).length;while(l--)d=g=(s=Te.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=S.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=S.event.special[d]||{},c=S.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&S.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),S.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(P)||[""]).length;while(l--)if(d=g=(s=Te.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=S.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||S.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)S.event.remove(e,d+t[l],n,r,!0);S.isEmptyObject(u)&&Y.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=S.event.fix(e),l=(Y.get(this,"events")||Object.create(null))[u.type]||[],c=S.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=S.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((S.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<S(i,this).index(l):S.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(S.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[S.expando]?e:new S.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click",Ce),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Ae(t,"click"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,"input")&&Y.get(t,"click")||A(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},S.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},S.Event=function(e,t){if(!(this instanceof S.Event))return new S.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ce:Ee,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&S.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[S.expando]=!0},S.Event.prototype={constructor:S.Event,isDefaultPrevented:Ee,isPropagationStopped:Ee,isImmediatePropagationStopped:Ee,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ce,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ce,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ce,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},S.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&be.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&we.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},S.event.addProp),S.each({focus:"focusin",blur:"focusout"},function(e,t){S.event.special[e]={setup:function(){return Ae(this,e,Se),!1},trigger:function(){return Ae(this,e),!0},delegateType:t}}),S.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){S.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||S.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),S.fn.extend({on:function(e,t,n,r){return ke(this,e,t,n,r)},one:function(e,t,n,r){return ke(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,S(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=Ee),this.each(function(){S.event.remove(this,e,n,t)})}});var Ne=/<script|<style|<link/i,De=/checked\s*(?:[^=]|=\s*.checked.)/i,je=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)S.event.add(t,i,s[i][n]);Q.hasData(e)&&(o=Q.access(e),a=S.extend({},o),Q.set(t,a))}}function Pe(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||1<f&&"string"==typeof d&&!y.checkClone&&De.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),Pe(t,r,i,o)});if(f&&(t=(e=xe(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=S.map(ve(e,"script"),Le)).length;c<f;c++)u=e,c!==p&&(u=S.clone(u,!0,!0),s&&S.merge(a,ve(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,S.map(a,He),c=0;c<s;c++)u=a[c],he.test(u.type||"")&&!Y.access(u,"globalEval")&&S.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?S._evalUrl&&!u.noModule&&S._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):b(u.textContent.replace(je,""),u,l))}return n}function Re(e,t,n){for(var r,i=t?S.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||S.cleanData(ve(r)),r.parentNode&&(n&&ie(r)&&ye(ve(r,"script")),r.parentNode.removeChild(r));return e}S.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||S.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;r<i;r++)Oe(o[r],a[r]);else Oe(e,c);return 0<(a=ve(c,"script")).length&&ye(a,!f&&ve(e,"script")),c},cleanData:function(e){for(var t,n,r,i=S.event.special,o=0;void 0!==(n=e[o]);o++)if(V(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?S.event.remove(n,r):S.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),S.fn.extend({detach:function(e){return Re(this,e,!0)},remove:function(e){return Re(this,e)},text:function(e){return $(this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Pe(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||qe(this,e).appendChild(e)})},prepend:function(){return Pe(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=qe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(S.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return S.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ne.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(S.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return Pe(this,arguments,function(e){var t=this.parentNode;S.inArray(this,n)<0&&(S.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),S.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){S.fn[e]=function(e){for(var t,n=[],r=S(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),S(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Me=new RegExp("^("+ee+")(?!px)[a-z%]+$","i"),Ie=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},We=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Fe=new RegExp(ne.join("|"),"i");function Be(e,t,n){var r,i,o,a,s=e.style;return(n=n||Ie(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=S.style(e,t)),!y.pixelBoxStyles()&&Me.test(a)&&Fe.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function $e(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",re.appendChild(u).appendChild(l);var e=C.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=E.createElement("div"),l=E.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",y.clearCloneStyle="content-box"===l.style.backgroundClip,S.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=E.createElement("table"),t=E.createElement("tr"),n=E.createElement("div"),e.style.cssText="position:absolute;left:-11111px",t.style.height="1px",n.style.height="9px",re.appendChild(e).appendChild(t).appendChild(n),r=C.getComputedStyle(t),a=3<parseInt(r.height),re.removeChild(e)),a}}))}();var _e=["Webkit","Moz","ms"],ze=E.createElement("div").style,Ue={};function Xe(e){var t=S.cssProps[e]||Ue[e];return t||(e in ze?e:Ue[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=_e.length;while(n--)if((e=_e[n]+t)in ze)return e}(e)||e)}var Ve=/^(none|table(?!-c[ea]).+)/,Ge=/^--/,Ye={position:"absolute",visibility:"hidden",display:"block"},Qe={letterSpacing:"0",fontWeight:"400"};function Je(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Ke(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=S.css(e,n+ne[a],!0,i)),r?("content"===n&&(u-=S.css(e,"padding"+ne[a],!0,i)),"margin"!==n&&(u-=S.css(e,"border"+ne[a]+"Width",!0,i))):(u+=S.css(e,"padding"+ne[a],!0,i),"padding"!==n?u+=S.css(e,"border"+ne[a]+"Width",!0,i):s+=S.css(e,"border"+ne[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function Ze(e,t,n){var r=Ie(e),i=(!y.boxSizingReliable()||n)&&"border-box"===S.css(e,"boxSizing",!1,r),o=i,a=Be(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Me.test(a)){if(!n)return a;a="auto"}return(!y.boxSizingReliable()&&i||!y.reliableTrDimensions()&&A(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===S.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===S.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Ke(e,t,n||(i?"border":"content"),o,r,a)+"px"}function et(e,t,n,r,i){return new et.prototype.init(e,t,n,r,i)}S.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Be(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=X(t),u=Ge.test(t),l=e.style;if(u||(t=Xe(s)),a=S.cssHooks[t]||S.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(S.cssNumber[s]?"":"px")),y.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=X(t);return Ge.test(t)||(t=Xe(s)),(a=S.cssHooks[t]||S.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Be(e,t,r)),"normal"===i&&t in Qe&&(i=Qe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),S.each(["height","width"],function(e,u){S.cssHooks[u]={get:function(e,t,n){if(t)return!Ve.test(S.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?Ze(e,u,n):We(e,Ye,function(){return Ze(e,u,n)})},set:function(e,t,n){var r,i=Ie(e),o=!y.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===S.css(e,"boxSizing",!1,i),s=n?Ke(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Ke(e,u,"border",!1,i)-.5)),s&&(r=te.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=S.css(e,u)),Je(0,t,s)}}}),S.cssHooks.marginLeft=$e(y.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Be(e,"marginLeft"))||e.getBoundingClientRect().left-We(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),S.each({margin:"",padding:"",border:"Width"},function(i,o){S.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(S.cssHooks[i+o].set=Je)}),S.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Ie(e),i=t.length;a<i;a++)o[t[a]]=S.css(e,t[a],!1,r);return o}return void 0!==n?S.style(e,t,n):S.css(e,t)},e,t,1<arguments.length)}}),((S.Tween=et).prototype={constructor:et,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||S.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(S.cssNumber[n]?"":"px")},cur:function(){var e=et.propHooks[this.prop];return e&&e.get?e.get(this):et.propHooks._default.get(this)},run:function(e){var t,n=et.propHooks[this.prop];return this.options.duration?this.pos=t=S.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):et.propHooks._default.set(this),this}}).init.prototype=et.prototype,(et.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=S.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){S.fx.step[e.prop]?S.fx.step[e.prop](e):1!==e.elem.nodeType||!S.cssHooks[e.prop]&&null==e.elem.style[Xe(e.prop)]?e.elem[e.prop]=e.now:S.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=et.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},S.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},S.fx=et.prototype.init,S.fx.step={};var tt,nt,rt,it,ot=/^(?:toggle|show|hide)$/,at=/queueHooks$/;function st(){nt&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(st):C.setTimeout(st,S.fx.interval),S.fx.tick())}function ut(){return C.setTimeout(function(){tt=void 0}),tt=Date.now()}function lt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=ne[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ct(e,t,n){for(var r,i=(ft.tweeners[t]||[]).concat(ft.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function ft(o,e,t){var n,a,r=0,i=ft.prefilters.length,s=S.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=tt||ut(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:S.extend({},e),opts:S.extend(!0,{specialEasing:{},easing:S.easing._default},t),originalProperties:e,originalOptions:t,startTime:tt||ut(),duration:t.duration,tweens:[],createTween:function(e,t){var n=S.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=X(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=S.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=ft.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(S._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return S.map(c,ct,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),S.fx.timer(S.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}S.Animation=S.extend(ft,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return se(n.elem,e,te.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=["*"]):e=e.match(P);for(var n,r=0,i=e.length;r<i;r++)n=e[r],ft.tweeners[n]=ft.tweeners[n]||[],ft.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),v=Y.get(e,"fxshow");for(r in n.queue||(null==(a=S._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,S.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],ot.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||S.style(e,r)}if((u=!S.isEmptyObject(t))||!S.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Y.get(e,"display")),"none"===(c=S.css(e,"display"))&&(l?c=l:(le([e],!0),l=e.style.display||l,c=S.css(e,"display"),le([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===S.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=Y.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&le([e],!0),p.done(function(){for(r in g||le([e]),Y.remove(e,"fxshow"),d)S.style(e,r,d[r])})),u=ct(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?ft.prefilters.unshift(e):ft.prefilters.push(e)}}),S.speed=function(e,t,n){var r=e&&"object"==typeof e?S.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return S.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in S.fx.speeds?r.duration=S.fx.speeds[r.duration]:r.duration=S.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&S.dequeue(this,r.queue)},r},S.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=S.isEmptyObject(t),o=S.speed(e,n,r),a=function(){var e=ft(this,S.extend({},t),o);(i||Y.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=S.timers,r=Y.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&at.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||S.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=Y.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=S.timers,o=n?n.length:0;for(t.finish=!0,S.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),S.each(["toggle","show","hide"],function(e,r){var i=S.fn[r];S.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(lt(r,!0),e,t,n)}}),S.each({slideDown:lt("show"),slideUp:lt("hide"),slideToggle:lt("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){S.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),S.timers=[],S.fx.tick=function(){var e,t=0,n=S.timers;for(tt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||S.fx.stop(),tt=void 0},S.fx.timer=function(e){S.timers.push(e),S.fx.start()},S.fx.interval=13,S.fx.start=function(){nt||(nt=!0,st())},S.fx.stop=function(){nt=null},S.fx.speeds={slow:600,fast:200,_default:400},S.fn.delay=function(r,e){return r=S.fx&&S.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},rt=E.createElement("input"),it=E.createElement("select").appendChild(E.createElement("option")),rt.type="checkbox",y.checkOn=""!==rt.value,y.optSelected=it.selected,(rt=E.createElement("input")).value="t",rt.type="radio",y.radioValue="t"===rt.value;var pt,dt=S.expr.attrHandle;S.fn.extend({attr:function(e,t){return $(this,S.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){S.removeAttr(this,e)})}}),S.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?S.prop(e,t,n):(1===o&&S.isXMLDoc(e)||(i=S.attrHooks[t.toLowerCase()]||(S.expr.match.bool.test(t)?pt:void 0)),void 0!==n?null===n?void S.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=S.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&"radio"===t&&A(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),pt={set:function(e,t,n){return!1===t?S.removeAttr(e,n):e.setAttribute(n,n),n}},S.each(S.expr.match.bool.source.match(/\w+/g),function(e,t){var a=dt[t]||S.find.attr;dt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=dt[o],dt[o]=r,r=null!=a(e,t,n)?o:null,dt[o]=i),r}});var ht=/^(?:input|select|textarea|button)$/i,gt=/^(?:a|area)$/i;function vt(e){return(e.match(P)||[]).join(" ")}function yt(e){return e.getAttribute&&e.getAttribute("class")||""}function mt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(P)||[]}S.fn.extend({prop:function(e,t){return $(this,S.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[S.propFix[e]||e]})}}),S.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&S.isXMLDoc(e)||(t=S.propFix[t]||t,i=S.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=S.find.attr(e,"tabindex");return t?parseInt(t,10):ht.test(e.nodeName)||gt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),y.optSelected||(S.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),S.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){S.propFix[this.toLowerCase()]=this}),S.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).addClass(t.call(this,e,yt(this)))});if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).removeClass(t.call(this,e,yt(this)))});if(!arguments.length)return this.attr("class","");if((e=mt(t)).length)while(n=this[u++])if(i=yt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=e[a++])while(-1<r.indexOf(" "+o+" "))r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(i,t){var o=typeof i,a="string"===o||Array.isArray(i);return"boolean"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){S(this).toggleClass(i.call(this,e,yt(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=S(this),r=mt(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&"boolean"!==o||((e=yt(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===i?"":Y.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+vt(yt(n))+" ").indexOf(t))return!0;return!1}});var xt=/\r/g;S.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,S(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=S.map(t,function(e){return null==e?"":e+""})),(r=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=S.valHooks[t.type]||S.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(xt,""):null==e?"":e:void 0}}),S.extend({valHooks:{option:{get:function(e){var t=S.find.attr(e,"value");return null!=t?t:vt(S.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,"optgroup"))){if(t=S(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=S.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<S.inArray(S.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),S.each(["radio","checkbox"],function(){S.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<S.inArray(S(e).val(),t)}},y.checkOn||(S.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),y.focusin="onfocusin"in C;var bt=/^(?:focusinfocus|focusoutblur)$/,wt=function(e){e.stopPropagation()};S.extend(S.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,"type")?e.type:e,h=v.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!bt.test(d+S.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[S.expando]?e:new S.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:S.makeArray(t,[e]),c=S.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,bt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(Y.get(o,"events")||Object.create(null))[e.type]&&Y.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&V(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!V(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),S.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,wt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,wt),S.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=S.extend(new S.Event,n,{type:e,isSimulated:!0});S.event.trigger(r,null,t)}}),S.fn.extend({trigger:function(e,t){return this.each(function(){S.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return S.event.trigger(e,t,n,!0)}}),y.focusin||S.each({focus:"focusin",blur:"focusout"},function(n,r){var i=function(e){S.event.simulate(r,e.target,S.event.fix(e))};S.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}});var Tt=C.location,Ct={guid:Date.now()},Et=/\?/;S.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||S.error("Invalid XML: "+e),t};var St=/\[\]$/,kt=/\r?\n/g,At=/^(?:submit|button|image|reset|file)$/i,Nt=/^(?:input|select|textarea|keygen)/i;function Dt(n,e,r,i){var t;if(Array.isArray(e))S.each(e,function(e,t){r||St.test(n)?i(n,t):Dt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==w(e))i(n,e);else for(t in e)Dt(n+"["+t+"]",e[t],r,i)}S.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!S.isPlainObject(e))S.each(e,function(){i(this.name,this.value)});else for(n in e)Dt(n,e[n],t,i);return r.join("&")},S.fn.extend({serialize:function(){return S.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=S.prop(this,"elements");return e?S.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!S(this).is(":disabled")&&Nt.test(this.nodeName)&&!At.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=S(this).val();return null==n?null:Array.isArray(n)?S.map(n,function(e){return{name:t.name,value:e.replace(kt,"\r\n")}}):{name:t.name,value:n.replace(kt,"\r\n")}}).get()}});var jt=/%20/g,qt=/#.*$/,Lt=/([?&])_=[^&]*/,Ht=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ot=/^(?:GET|HEAD)$/,Pt=/^\/\//,Rt={},Mt={},It="*/".concat("*"),Wt=E.createElement("a");function Ft(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(P)||[];if(m(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Bt(t,i,o,a){var s={},u=t===Mt;function l(e){var r;return s[e]=!0,S.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function $t(e,t){var n,r,i=S.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&S.extend(!0,e,r),e}Wt.href=Tt.href,S.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Tt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Tt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":It,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":S.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?$t($t(e,S.ajaxSettings),t):$t(S.ajaxSettings,e)},ajaxPrefilter:Ft(Rt),ajaxTransport:Ft(Mt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=S.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?S(y):S.event,x=S.Deferred(),b=S.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=Ht.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Tt.href)+"").replace(Pt,Tt.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(P)||[""],null==v.crossDomain){r=E.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Wt.protocol+"//"+Wt.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=S.param(v.data,v.traditional)),Bt(Rt,v,t,T),h)return T;for(i in(g=S.event&&v.global)&&0==S.active++&&S.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Ot.test(v.type),f=v.url.replace(qt,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(jt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(Et.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Lt,"$1"),o=(Et.test(f)?"&":"?")+"_="+Ct.guid+++o),v.url=f+o),v.ifModified&&(S.lastModified[f]&&T.setRequestHeader("If-Modified-Since",S.lastModified[f]),S.etag[f]&&T.setRequestHeader("If-None-Match",S.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+It+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Bt(Mt,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<S.inArray("script",v.dataTypes)&&(v.converters["text script"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(S.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(S.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--S.active||S.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return S.get(e,t,n,"json")},getScript:function(e,t){return S.get(e,void 0,t,"script")}}),S.each(["get","post"],function(e,i){S[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),S.ajax(S.extend({url:e,type:i,dataType:r,data:t,success:n},S.isPlainObject(e)&&e))}}),S.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),S._evalUrl=function(e,t,n){return S.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){S.globalEval(e,t,n)}})},S.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=S(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){S(this).wrapInner(n.call(this,e))}):this.each(function(){var e=S(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){S(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){S(this).replaceWith(this.childNodes)}),this}}),S.expr.pseudos.hidden=function(e){return!S.expr.pseudos.visible(e)},S.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},S.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var _t={0:200,1223:204},zt=S.ajaxSettings.xhr();y.cors=!!zt&&"withCredentials"in zt,y.ajax=zt=!!zt,S.ajaxTransport(function(i){var o,a;if(y.cors||zt&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(_t[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),S.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),S.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return S.globalEval(e),e}}}),S.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),S.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=S("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=vt(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&S.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?S("<div>").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var Gt=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;S.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),m(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||S.guid++,i},S.holdReady=function(e){e?S.readyWait++:S.ready(!0)},S.isArray=Array.isArray,S.parseJSON=JSON.parse,S.nodeName=A,S.isFunction=m,S.isWindow=x,S.camelCase=X,S.type=w,S.now=Date.now,S.isNumeric=function(e){var t=S.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},S.trim=function(e){return null==e?"":(e+"").replace(Gt,"")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return S});var Yt=C.jQuery,Qt=C.$;return S.noConflict=function(e){return C.$===S&&(C.$=Qt),e&&C.jQuery===S&&(C.jQuery=Yt),S},"undefined"==typeof e&&(C.jQuery=C.$=S),S});
4var H5P = window.H5P = window.H5P || {}; 5H5P.jQuery = jQuery.noConflict(true); 6H5P.jQuery.fn.__originalLoad = H5P.jQuery.load; 7 8H5P.jQuery.fn.load = function (url, params, callback) { 9 /** 10 * NOTE: 11 * This is needed in order to support old libraries that uses the .load() function 12 * for elements in the deprecated jQuery way (elem.load(fn)), the correct way to do this 13 * now is elem.on('load', fn) 14 */ 15 if (typeof url === "function") { 16 console.warn('You are using a deprecated H5P library. Please upgrade!'); 17 let args = Array.prototype.slice.call(arguments); 18 args.unshift('load'); 19 return H5P.jQuery.fn.on.apply(this, args); 20 } 21 22 return H5P.jQuery.fn.__originalLoad.apply(this, arguments); 23}; 24/*jshint multistr: true */ 25// TODO: Should we split up the generic parts needed by the editor(and others), and the parts needed to "run" H5Ps? 26 27/** @namespace */ 28var H5P = window.H5P = window.H5P || {}; 29 30/** 31 * Tells us if we're inside of an iframe. 32 * @member {boolean} 33 */ 34H5P.isFramed = (window.self !== window.parent); 35 36/** 37 * jQuery instance of current window. 38 * @member {H5P.jQuery} 39 */ 40H5P.$window = H5P.jQuery(window); 41 42/** 43 * List over H5P instances on the current page. 44 * @member {Array} 45 */ 46H5P.instances = []; 47 48// Detect if we support fullscreen, and what prefix to use. 49if (document.documentElement.requestFullscreen) { 50 /** 51 * Browser prefix to use when entering fullscreen mode. 52 * undefined means no fullscreen support. 53 * @member {string} 54 */ 55 H5P.fullScreenBrowserPrefix = ''; 56} 57else if (document.documentElement.webkitRequestFullScreen) { 58 H5P.safariBrowser = navigator.userAgent.match(/version\/([.\d]+)/i); 59 H5P.safariBrowser = (H5P.safariBrowser === null ? 0 : parseInt(H5P.safariBrowser[1])); 60 61 // Do not allow fullscreen for safari < 7. 62 if (H5P.safariBrowser === 0 || H5P.safariBrowser > 6) { 63 H5P.fullScreenBrowserPrefix = 'webkit'; 64 } 65} 66else if (document.documentElement.mozRequestFullScreen) { 67 H5P.fullScreenBrowserPrefix = 'moz'; 68} 69else if (document.documentElement.msRequestFullscreen) { 70 H5P.fullScreenBrowserPrefix = 'ms'; 71} 72 73/** 74 * Keep track of when the H5Ps where started. 75 * 76 * @type {Object[]} 77 */ 78H5P.opened = {}; 79 80/** 81 * Initialize H5P content. 82 * Scans for ".h5p-content" in the document and initializes H5P instances where found. 83 * 84 * @param {Object} target DOM Element 85 */ 86H5P.init = function (target) { 87 // Useful jQuery object. 88 if (H5P.$body === undefined) { 89 H5P.$body = H5P.jQuery(document.body); 90 } 91 92 // Determine if we can use full screen 93 if (H5P.fullscreenSupported === undefined) { 94 /** 95 * Use this variable to check if fullscreen is supported. Fullscreen can be 96 * restricted when embedding since not all browsers support the native 97 * fullscreen, and the semi-fullscreen solution doesn't work when embedded. 98 * @type {boolean} 99 */ 100 H5P.fullscreenSupported = !H5PIntegration.fullscreenDisabled && !H5P.fullscreenDisabled && (!(H5P.isFramed && H5P.externalEmbed !== false) || !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled)); 101 // -We should consider document.msFullscreenEnabled when they get their 102 // -element sizing corrected. Ref. https://connect.microsoft.com/IE/feedback/details/838286/ie-11-incorrectly-reports-dom-element-sizes-in-fullscreen-mode-when-fullscreened-element-is-within-an-iframe 103 // Update: Seems to be no need as they've moved on to Webkit 104 } 105 106 // Deprecated variable, kept to maintain backwards compatability 107 if (H5P.canHasFullScreen === undefined) { 108 /** 109 * @deprecated since version 1.11 110 * @type {boolean} 111 */ 112 H5P.canHasFullScreen = H5P.fullscreenSupported; 113 } 114 115 // H5Ps added in normal DIV. 116 H5P.jQuery('.h5p-content:not(.h5p-initialized)', target).each(function () { 117 var $element = H5P.jQuery(this).addClass('h5p-initialized'); 118 var $container = H5P.jQuery('<div class="h5p-container"></div>').appendTo($element); 119 var contentId = $element.data('content-id'); 120 var contentData = H5PIntegration.contents['cid-' + contentId]; 121 if (contentData === undefined) { 122 return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?'); 123 } 124 var library = { 125 library: contentData.library, 126 params: JSON.parse(contentData.jsonContent), 127 metadata: contentData.metadata 128 }; 129 130 // Apply theme density 131 let density = H5PIntegration.theme?.density ?? 'large'; 132 $element.addClass('h5p-' + density); 133 134 H5P.getUserData(contentId, 'state', function (err, previousState) { 135 if (previousState) { 136 library.userDatas = { 137 state: previousState 138 }; 139 } 140 else if (previousState === null && H5PIntegration.saveFreq) { 141 // Content has been reset. Display dialog. 142 delete contentData.contentUserData; 143 var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '<p>' + H5P.t('contentChanged') + '</p><p>' + H5P.t('startingOver') + '</p><div class="h5p-dialog-ok-button" tabIndex="0" role="button">OK</div>', $container); 144 H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { 145 146 var closeDialog = function (event) { 147 if (event.type === 'click' || event.which === 32) { 148 dialog.close(); 149 H5P.deleteUserData(contentId, 'state', 0); 150 } 151 }; 152 153 $dialog.find('.h5p-dialog-ok-button').click(closeDialog).keypress(closeDialog); 154 H5P.trigger(instance, 'resize'); 155 }).on('dialog-closed', function () { 156 H5P.trigger(instance, 'resize'); 157 }); 158 dialog.open(); 159 } 160 // If previousState is false we don't have a previous state 161 }); 162 163 // Create new instance. 164 var instance = H5P.newRunnable(library, contentId, $container, true, {standalone: true}); 165 166 H5P.offlineRequestQueue = new H5P.OfflineRequestQueue({instance: instance}); 167 168 // Check if we should add and display a fullscreen button for this H5P. 169 if (contentData.fullScreen == 1 && H5P.fullscreenSupported) { 170 H5P.jQuery(
171 '<div class="h5p-content-controls">' + 172 '<div role="button" ' + 173 'tabindex="0" ' + 174 'class="h5p-enable-fullscreen" ' + 175 'aria-label="' + H5P.t('fullscreen') + '" ' + 176 'title="' + H5P.t('fullscreen') + '">' + 177 '</div>' + 178 '</div>') 179 .prependTo($container) 180 .children() 181 .click(function () { 182 H5P.fullScreen($container, instance); 183 }) 184 .keydown(function (e) { 185 if (e.which === 32 || e.which === 13) { 186 H5P.fullScreen($container, instance); 187 return false; 188 } 189 }) 190 ; 191 } 192 193 /** 194 * Create action bar 195 */ 196 var displayOptions = contentData.displayOptions; 197 var displayFrame = false; 198 if (displayOptions.frame) { 199 // Special handling of copyrights 200 if (displayOptions.copyright) { 201 var copyrights = H5P.getCopyrights(instance, library.params, contentId, library.metadata); 202 if (!copyrights) { 203 displayOptions.copyright = false; 204 } 205 } 206 207 // Create action bar 208 var actionBar = new H5P.ActionBar(displayOptions); 209 var $actions = actionBar.getDOMElement(); 210 211 actionBar.on('reuse', function () { 212 H5P.openReuseDialog($actions, contentData, { 213 library: contentData.library, 214 params: JSON.parse(contentData.jsonContent), 215 metadata: contentData.metadata 216 }, instance, contentId); 217 instance.triggerXAPI('accessed-reuse'); 218 }); 219 actionBar.on('copyrights', function () { 220 var dialog = new H5P.Dialog('copyrights', H5P.t('copyrightInformation'), copyrights, $container, $actions.find('.h5p-copyrights')[0]); 221 dialog.open(true); 222 instance.triggerXAPI('accessed-copyright'); 223 }); 224 actionBar.on('embed', function () { 225 H5P.openEmbedDialog($actions, contentData.embedCode, contentData.resizeCode, { 226 width: $element.width(), 227 height: $element.height() 228 }, instance); 229 instance.triggerXAPI('accessed-embed'); 230 }); 231 232 if (actionBar.hasActions()) { 233 displayFrame = true; 234 $actions.insertAfter($container); 235 } 236 } 237 238 $element.addClass(displayFrame ? 'h5p-frame' : 'h5p-no-frame'); 239 240 // Keep track of when we started 241 H5P.opened[contentId] = new Date(); 242 243 // Handle events when the user finishes the content. Useful for logging exercise results. 244 H5P.on(instance, 'finish', function (event) { 245 if (event.data !== undefined) { 246 H5P.setFinished(contentId, event.data.score, event.data.maxScore, event.data.time); 247 } 248 }); 249 250 // Listen for xAPI events. 251 H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); 252 253 // Auto save current state if supported 254 if (H5PIntegration.saveFreq !== false && ( 255 instance.getCurrentState instanceof Function || 256 typeof instance.getCurrentState === 'function')) { 257 258 var saveTimer, save = function () { 259 var state = instance.getCurrentState(); 260 if (state !== undefined) { 261 H5P.setUserData(contentId, 'state', state, {deleteOnChange: true}); 262 } 263 if (H5PIntegration.saveFreq) { 264 // Continue autosave 265 saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); 266 } 267 }; 268 269 if (H5PIntegration.saveFreq) { 270 // Start autosave 271 saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); 272 } 273 274 // xAPI events will schedule a save in three seconds. 275 H5P.on(instance, 'xAPI', function (event) { 276 var verb = event.getVerb(); 277 if (verb === 'completed' || verb === 'progressed') { 278 clearTimeout(saveTimer); 279 saveTimer = setTimeout(save, 3000); 280 } 281 }); 282 } 283 284 if (H5P.isFramed) { 285 var resizeDelay; 286 if (H5P.externalEmbed === false) { 287 // Internal embed 288 // Make it possible to resize the iframe when the content changes size. This way we get no scrollbars. 289 var iframe = window.frameElement; 290 var resizeIframe = function () { 291 if (window.parent.H5P.isFullscreen) { 292 return; // Skip if full screen. 293 } 294 295 // Retain parent size to avoid jumping/scrolling 296 var parentHeight = iframe.parentElement.style.height; 297 iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; 298 299 // Note: Force layout reflow 300 // This fixes a flickering bug for embedded content on iPads 301 // @see https://github.com/h5p/h5p-moodle-plugin/issues/237 302 iframe.getBoundingClientRect(); 303 304 // Reset iframe height, in case content has shrinked. 305 iframe.style.height = '1px'; 306 307 // Resize iframe so all content is visible. 308 iframe.style.height = (iframe.contentDocument.body.scrollHeight) + 'px'; 309 310 // Free parent 311 iframe.parentElement.style.height = parentHeight; 312 }; 313 314 H5P.on(instance, 'resize', function () { 315 // Use a delay to make sure iframe is resized to the correct size. 316 clearTimeout(resizeDelay); 317 resizeDelay = setTimeout(function () { 318 resizeIframe(); 319 }, 1); 320 }); 321 } 322 else if (H5P.communicator) { 323 // External embed 324 var parentIsFriendly = false;
325 326 // Handle that the resizer is loaded after the iframe 327 H5P.communicator.on('ready', function () { 328 H5P.communicator.send('hello'); 329 }); 330 331 // Handle hello message from our parent window 332 H5P.communicator.on('hello', function () { 333 // Initial setup/handshake is done 334 parentIsFriendly = true; 335 336 // Make iframe responsive 337 document.body.style.height = 'auto'; 338 339 // Hide scrollbars for correct size 340 document.body.style.overflow = 'hidden'; 341 342 // Content need to be resized to fit the new iframe size 343 H5P.trigger(instance, 'resize'); 344 }); 345 346 // When resize has been prepared tell parent window to resize 347 H5P.communicator.on('resizePrepared', function () { 348 H5P.communicator.send('resize', { 349 scrollHeight: document.body.scrollHeight 350 }); 351 }); 352 353 H5P.communicator.on('resize', function () { 354 H5P.trigger(instance, 'resize'); 355 }); 356 357 H5P.on(instance, 'resize', function () { 358 if (H5P.isFullscreen) { 359 return; // Skip iframe resize 360 } 361 362 // Use a delay to make sure iframe is resized to the correct size. 363 clearTimeout(resizeDelay); 364 resizeDelay = setTimeout(function () { 365 // Only resize if the iframe can be resized 366 if (parentIsFriendly) { 367 H5P.communicator.send('prepareResize', { 368 scrollHeight: document.body.scrollHeight, 369 clientHeight: document.body.clientHeight 370 }); 371 } 372 else { 373 H5P.communicator.send('hello'); 374 } 375 }, 0); 376 }); 377 } 378 } 379 380 if (!H5P.isFramed || H5P.externalEmbed === false) { 381 // Resize everything when window is resized. 382 H5P.jQuery(window.parent).resize(function () { 383 H5P.trigger(instance, 'resize'); 384 }); 385 } 386 387 H5P.instances.push(instance); 388 389 // Resize content. 390 H5P.trigger(instance, 'resize'); 391 392 // Logic for hiding focus effects when using mouse 393 $element.addClass('using-mouse'); 394 $element.on('mousedown keydown keyup', function (event) { 395 $element.toggleClass('using-mouse', event.type === 'mousedown'); 396 }); 397 398 if (H5P.externalDispatcher) { 399 H5P.externalDispatcher.trigger('initialized'); 400 } 401 }); 402 403 // Insert H5Ps that should be in iframes. 404 H5P.jQuery('iframe.h5p-iframe:not(.h5p-initialized)', target).each(function () { 405 const iframe = this; 406 const $iframe = H5P.jQuery(iframe); 407 408 const contentId = $iframe.data('content-id'); 409 const contentData = H5PIntegration.contents['cid-' + contentId]; 410 const contentLanguage = contentData && contentData.metadata && contentData.metadata.defaultLanguage 411 ? contentData.metadata.defaultLanguage : 'en'; 412 413 const writeDocument = function () { 414 iframe.contentDocument.open();
415 iframe.contentDocument.write('<!doctype html><html class="h5p-iframe" lang="' + contentLanguage + '"><head>' + H5P.getHeadTags(contentId) + '</head><body><div class="h5p-content" data-content-id="' + contentId + '"/></body></html>'); 416 iframe.contentDocument.close(); 417 }; 418 419 $iframe.addClass('h5p-initialized') 420 if (iframe.contentDocument === null) { 421 // In some Edge cases the iframe isn't always loaded when the page is ready. 422 $iframe.on('load', writeDocument); 423 $iframe.attr('src', 'about:blank'); 424 } 425 else { 426 writeDocument(); 427 } 428 }); 429}; 430 431/** 432 * Loop through assets for iframe content and create a set of tags for head. 433 * 434 * @private 435 * @param {number} contentId 436 * @returns {string} HTML 437 */ 438H5P.getHeadTags = function (contentId) { 439 var createStyleTags = function (styles) { 440 var tags = ''; 441 for (var i = 0; i < styles.length; i++) { 442 tags += '<link rel="stylesheet" href="' + styles[i] + '">'; 443 } 444 return tags; 445 }; 446 447 var createScriptTags = function (scripts) { 448 var tags = ''; 449 for (var i = 0; i < scripts.length; i++) { 450 tags += '<script src="' + scripts[i] + '"></script>'; 451 } 452 return tags; 453 }; 454 455 return '<base target="_parent">' + 456 createStyleTags(H5PIntegration.core.styles) + 457 createStyleTags(H5PIntegration.contents['cid-' + contentId].styles) + 458 createScriptTags(H5PIntegration.core.scripts) + 459 createScriptTags(H5PIntegration.contents['cid-' + contentId].scripts) + 460 '<script>H5PIntegration = window.parent.H5PIntegration; var H5P = H5P || {}; H5P.externalEmbed = false;</script>'; 461}; 462 463/** 464 * When embedded the communicator helps talk to the parent page. 465 * 466 * @type {Communicator} 467 */ 468H5P.communicator = (function () { 469 /** 470 * @class 471 * @private 472 */ 473 function Communicator() { 474 var self = this; 475 476 // Maps actions to functions 477 var actionHandlers = {}; 478 479 // Register message listener 480 window.addEventListener('message', function receiveMessage(event) { 481 if (window.parent !== event.source || event.data.context !== 'h5p') { 482 return; // Only handle messages from parent and in the correct context 483 } 484 485 if (actionHandlers[event.data.action] !== undefined) { 486 actionHandlers[event.data.action](event.data); 487 } 488 } , false); 489 490 491 /** 492 * Register action listener. 493 * 494 * @param {string} action What you are waiting for 495 * @param {function} handler What you want done 496 */ 497 self.on = function (action, handler) { 498 actionHandlers[action] = handler; 499 }; 500 501 /** 502 * Send a message to the all mighty father. 503 * 504 * @param {string} action 505 * @param {Object} [data] payload 506 */ 507 self.send = function (action, data) { 508 if (data === undefined) { 509 data = {}; 510 } 511 data.context = 'h5p'; 512 data.action = action; 513 514 // Parent origin can be anything 515 window.parent.postMessage(data, '*'); 516 }; 517 } 518 519 return (window.postMessage && window.addEventListener ? new Communicator() : undefined); 520})(); 521 522/** 523 * Enter semi fullscreen for the given H5P instance 524 * 525 * @param {H5P.jQuery} $element Content container. 526 * @param {Object} instance 527 * @param {function} exitCallback Callback function called when user exits fullscreen. 528 * @param {H5P.jQuery} $body For internal use. Gives the body of the iframe. 529 */ 530H5P.semiFullScreen = function ($element, instance, exitCallback, body) { 531 H5P.fullScreen($element, instance, exitCallback, body, true); 532}; 533 534/** 535 * Enter fullscreen for the given H5P instance. 536 * 537 * @param {H5P.jQuery} $element Content container. 538 * @param {Object} instance 539 * @param {function} exitCallback Callback function called when user exits fullscreen. 540 * @param {H5P.jQuery} $body For internal use. Gives the body of the iframe. 541 * @param {Boolean} forceSemiFullScreen 542 */ 543H5P.fullScreen = function ($element, instance, exitCallback, body, forceSemiFullScreen) { 544 if (H5P.exitFullScreen !== undefined) { 545 return; // Cannot enter new fullscreen until previous is over 546 } 547 548 if (H5P.isFramed && H5P.externalEmbed === false) { 549 // Trigger resize on wrapper in parent window. 550 window.parent.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get(), forceSemiFullScreen); 551 H5P.isFullscreen = true; 552 H5P.exitFullScreen = function () { 553 window.parent.H5P.exitFullScreen(); 554 }; 555 H5P.on(instance, 'exitFullScreen', function () { 556 H5P.isFullscreen = false;
557 H5P.exitFullScreen = undefined; 558 }); 559 return; 560 } 561 562 var $container = $element; 563 var $classes, $iframe, $body; 564 if (body === undefined) { 565 $body = H5P.$body; 566 } 567 else { 568 // We're called from an iframe. 569 $body = H5P.jQuery(body); 570 $classes = $body.add($element.get()); 571 var iframeSelector = '#h5p-iframe-' + $element.parent().data('content-id'); 572 $iframe = H5P.jQuery(iframeSelector); 573 $element = $iframe.parent(); // Put iframe wrapper in fullscreen, not container. 574 } 575 576 $classes = $element.add(H5P.$body).add($classes); 577 578 /** 579 * Prepare for resize by setting the correct styles. 580 * 581 * @private 582 * @param {string} classes CSS 583 */ 584 var before = function (classes) { 585 $classes.addClass(classes); 586 587 if ($iframe !== undefined) { 588 // Set iframe to its default size(100%). 589 $iframe.css('height', ''); 590 } 591 }; 592 593 /** 594 * Gets called when fullscreen mode has been entered. 595 * Resizes and sets focus on content. 596 * 597 * @private 598 */ 599 var entered = function () { 600 // Do not rely on window resize events. 601 H5P.trigger(instance, 'resize'); 602 H5P.trigger(instance, 'focus'); 603 H5P.trigger(instance, 'enterFullScreen'); 604 }; 605 606 /** 607 * Gets called when fullscreen mode has been exited. 608 * Resizes and sets focus on content. 609 * 610 * @private 611 * @param {string} classes CSS 612 */ 613 var done = function (classes) { 614 H5P.isFullscreen = false; 615 $classes.removeClass(classes); 616 617 // Do not rely on window resize events. 618 H5P.trigger(instance, 'resize'); 619 H5P.trigger(instance, 'focus'); 620 621 H5P.exitFullScreen = undefined; 622 if (exitCallback !== undefined) { 623 exitCallback(); 624 } 625 626 H5P.trigger(instance, 'exitFullScreen'); 627 }; 628 629 H5P.isFullscreen = true; 630 if (H5P.fullScreenBrowserPrefix === undefined || forceSemiFullScreen === true) { 631 // Create semi fullscreen. 632 633 if (H5P.isFramed) { 634 return; // TODO: Should we support semi-fullscreen for IE9 & 10 ? 635 } 636 637 before('h5p-semi-fullscreen'); 638 var $disable = H5P.jQuery('<div role="button" tabindex="0" class="h5p-disable-fullscreen" title="' + H5P.t('disableFullscreen') + '" aria-label="' + H5P.t('disableFullscreen') + '"></div>').appendTo($container.find('.h5p-content-controls')); 639 var keyup, disableSemiFullscreen = H5P.exitFullScreen = function () { 640 if (prevViewportContent) { 641 // Use content from the previous viewport tag 642 h5pViewport.content = prevViewportContent; 643 } 644 else { 645 // Remove viewport tag 646 head.removeChild(h5pViewport); 647 } 648 $disable.remove(); 649 $body.unbind('keyup', keyup); 650 done('h5p-semi-fullscreen'); 651 }; 652 keyup = function (event) { 653 if (event.keyCode === 27) { 654 disableSemiFullscreen(); 655 } 656 }; 657 $disable.click(disableSemiFullscreen); 658 $body.keyup(keyup); 659 660 // Disable zoom 661 var prevViewportContent, h5pViewport; 662 var metaTags = document.getElementsByTagName('meta'); 663 for (var i = 0; i < metaTags.length; i++) { 664 if (metaTags[i].name === 'viewport') { 665 // Use the existing viewport tag 666 h5pViewport = metaTags[i]; 667 prevViewportContent = h5pViewport.content; 668 break; 669 } 670 } 671 if (!prevViewportContent) { 672 // Create a new viewport tag 673 h5pViewport = document.createElement('meta'); 674 h5pViewport.name = 'viewport'; 675 } 676 h5pViewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0'; 677 if (!prevViewportContent) { 678 // Insert the new viewport tag 679 var head = document.getElementsByTagName('head')[0]; 680 head.appendChild(h5pViewport); 681 } 682 683 entered(); 684 } 685 else { 686 // Create real fullscreen. 687 688 before('h5p-fullscreen'); 689 var first, eventName = (H5P.fullScreenBrowserPrefix === 'ms' ? 'MSFullscreenChange' : H5P.fullScreenBrowserPrefix + 'fullscreenchange'); 690 document.addEventListener(eventName, function fullscreenCallback() { 691 if (first === undefined) { 692 // We are entering fullscreen mode 693 first = false; 694 entered(); 695 return; 696 } 697 698 // We are exiting fullscreen 699 done('h5p-fullscreen'); 700 document.removeEventListener(eventName, fullscreenCallback, false); 701 }); 702 703 if (H5P.fullScreenBrowserPrefix === '') { 704 $element[0].requestFullscreen(); 705 } 706 else { 707 var method = (H5P.fullScreenBrowserPrefix === 'ms' ? 'msRequestFullscreen' : H5P.fullScreenBrowserPrefix + 'RequestFullScreen'); 708 var params = (H5P.fullScreenBrowserPrefix === 'webkit' && H5P.safariBrowser === 0 ? Element.ALLOW_KEYBOARD_INPUT : undefined); 709 $element[0][method](params); 710 } 711 712 // Allows everone to exit 713 H5P.exitFullScreen = function () { 714 if (H5P.fullScreenBrowserPrefix === '') { 715 document.exitFullscreen(); 716 } 717 else if (H5P.fullScreenBrowserPrefix === 'moz') { 718 document.mozCancelFullScreen(); 719 } 720 else { 721 document[H5P.fullScreenBrowserPrefix + 'ExitFullscreen'](); 722 } 723 }; 724 } 725}; 726 727(function () { 728 /** 729 * Helper for adding a query parameter to an existing path that may already 730 * contain one or a hash. 731 * 732 * @param {string} path 733 * @param {string} parameter 734 * @return {string} 735 */ 736 H5P.addQueryParameter = function (path, parameter) { 737 let newPath, secondSplit; 738 const firstSplit = path.split('?'); 739 if (firstSplit[1]) { 740 // There is already an existing query 741 secondSplit = firstSplit[1].split('#'); 742 newPath = firstSplit[0] + '?' + secondSplit[0] + '&'; 743 } 744 else { 745 // No existing query, just need to take care of the hash 746 secondSplit = firstSplit[0].split('#'); 747 newPath = secondSplit[0] + '?'; 748 } 749 newPath += parameter; 750 if (secondSplit[1]) { 751 // Add back the hash 752 newPath += '#' + secondSplit[1]; 753 } 754 return newPath; 755 }; 756 757 /** 758 * Helper for setting the crossOrigin attribute + the complete correct source. 759 * Note: This will start loading the resource. 760 * 761 * @param {Element} element DOM element, typically img, video or audio 762 * @param {Object} source File object from parameters/json_content (created by H5PEditor) 763 * @param {number} contentId Needed to determine the complete correct file path 764 */ 765 H5P.setSource = function (element, source, contentId) { 766 let path = source.path; 767 768 const crossOrigin = H5P.getCrossOrigin(source); 769 if (crossOrigin) { 770 element.crossOrigin = crossOrigin; 771 772 if (H5PIntegration.crossoriginCacheBuster) { 773 // Some sites may want to add a cache buster in case the same resource 774 // is used elsewhere without the crossOrigin attribute
775 path = H5P.addQueryParameter(path, H5PIntegration.crossoriginCacheBuster); 776 } 777 } 778 else { 779 // In case this element has been used before. 780 element.removeAttribute('crossorigin'); 781 } 782 783 element.src = H5P.getPath(path, contentId); 784 }; 785 786 /** 787 * Check if the given path has a protocol. 788 * 789 * @private 790 * @param {string} path 791 * @return {string} 792 */ 793 var hasProtocol = function (path) { 794 return path.match(/^[a-z0-9]+:\/\//i); 795 }; 796 797 /** 798 * Get the crossOrigin policy to use for img, video and audio tags on the current site. 799 * 800 * @param {Object|string} source File object from parameters/json_content - Can also be URL(deprecated usage) 801 * @returns {string|null} crossOrigin attribute value required by the source 802 */ 803 H5P.getCrossOrigin = function (source) { 804 if (typeof source !== 'object') { 805 // Deprecated usage. 806 return H5PIntegration.crossorigin && H5PIntegration.crossoriginRegex && source.match(H5PIntegration.crossoriginRegex) ? H5PIntegration.crossorigin : null; 807 } 808 809 if (H5PIntegration.crossorigin && !hasProtocol(source.path)) { 810 // This is a local file, use the local crossOrigin policy. 811 return H5PIntegration.crossorigin; 812 // Note: We cannot use this for all external sources since we do not know 813 // each server's individual policy. We could add support for a list of 814 // external sources and their policy later on. 815 } 816 }; 817 818 /** 819 * Find the path to the content files based on the id of the content. 820 * Also identifies and returns absolute paths. 821 * 822 * @param {string} path 823 * Relative to content folder or absolute. 824 * @param {number} contentId 825 * ID of the content requesting the path. 826 * @returns {string} 827 * Complete URL to path. 828 */ 829 H5P.getPath = function (path, contentId) { 830 if (hasProtocol(path)) { 831 return path; 832 } 833 834 var prefix; 835 var isTmpFile = (path.substr(-4,4) === '#tmp'); 836 if (contentId !== undefined && !isTmpFile) { 837 // Check for custom override URL 838 if (H5PIntegration.contents !== undefined && 839 H5PIntegration.contents['cid-' + contentId]) { 840 prefix = H5PIntegration.contents['cid-' + contentId].contentUrl; 841 } 842 if (!prefix) { 843 prefix = H5PIntegration.url + '/content/' + contentId; 844 } 845 } 846 else if (window.H5PEditor !== undefined) { 847 prefix = H5PEditor.filesPath; 848 } 849 else { 850 return; 851 } 852 853 if (!hasProtocol(prefix)) { 854 // Use absolute urls 855 prefix = window.location.protocol + "//" + window.location.host + prefix; 856 } 857 858 return prefix + '/' + path; 859 }; 860})(); 861 862/** 863 * THIS FUNCTION IS DEPRECATED, USE getPath INSTEAD 864 * Will be remove march 2016. 865 * 866 * Find the path to the content files folder based on the id of the content 867 * 868 * @deprecated 869 * Will be removed march 2016. 870 * @param contentId 871 * Id of the content requesting the path 872 * @returns {string} 873 * URL 874 */ 875H5P.getContentPath = function (contentId) { 876 return H5PIntegration.url + '/content/' + contentId; 877}; 878 879/** 880 * Get library class constructor from H5P by classname. 881 * Note that this class will only work for resolve "H5P.NameWithoutDot". 882 * Also check out {@link H5P.newRunnable} 883 * 884 * Used from libraries to construct instances of other libraries' objects by name. 885 * 886 * @param {string} name Name of library 887 * @returns {Object} Class constructor 888 */ 889H5P.classFromName = function (name) { 890 var arr = name.split("."); 891 return this[arr[arr.length - 1]]; 892}; 893 894/** 895 * A safe way of creating a new instance of a runnable H5P. 896 * 897 * @param {Object} library 898 * Library/action object form params. 899 * @param {number} contentId 900 * Identifies the content. 901 * @param {H5P.jQuery} [$attachTo] 902 * Element to attach the instance to. 903 * @param {boolean} [skipResize] 904 * Skip triggering of the resize event after attaching. 905 * @param {Object} [extras] 906 * Extra parameters for the H5P content constructor 907 * @returns {Object} 908 * Instance. 909 */ 910H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { 911 var nameSplit, versionSplit, machineName; 912 try { 913 nameSplit = library.library.split(' ', 2); 914 machineName = nameSplit[0]; 915 versionSplit = nameSplit[1].split('.', 2); 916 }
917 catch (err) { 918 return H5P.error('Invalid library string: ' + library.library); 919 } 920 921 if ((library.params instanceof Object) !== true || (library.params instanceof Array) === true) { 922 H5P.error('Invalid library params for: ' + library.library); 923 return H5P.error(library.params); 924 } 925 926 // Find constructor function 927 var constructor; 928 try { 929 nameSplit = nameSplit[0].split('.'); 930 constructor = window; 931 for (var i = 0; i < nameSplit.length; i++) { 932 constructor = constructor[nameSplit[i]]; 933 } 934 if (typeof constructor !== 'function') { 935 throw null; 936 } 937 } 938 catch (err) { 939 return H5P.error('Unable to find constructor for: ' + library.library); 940 } 941 942 if (extras === undefined) { 943 extras = {}; 944 } 945 if (library.subContentId) { 946 extras.subContentId = library.subContentId; 947 } 948 949 if (library.userDatas && library.userDatas.state && H5PIntegration.saveFreq) { 950 extras.previousState = library.userDatas.state; 951 } 952 953 if (library.metadata) { 954 extras.metadata = library.metadata; 955 } 956 957 // Makes all H5P libraries extend H5P.ContentType: 958 var standalone = extras.standalone || false; 959 // This order makes it possible for an H5P library to override H5P.ContentType functions! 960 constructor.prototype = H5P.jQuery.extend({}, H5P.ContentType(standalone).prototype, constructor.prototype); 961 962 var instance; 963 // Some old library versions have their own custom third parameter. 964 // Make sure we don't send them the extras. 965 // (they will interpret it as something else) 966 if (H5P.jQuery.inArray(library.library, ['H5P.CoursePresentation 1.0', 'H5P.CoursePresentation 1.1', 'H5P.CoursePresentation 1.2', 'H5P.CoursePresentation 1.3']) > -1) { 967 instance = new constructor(library.params, contentId); 968 } 969 else { 970 instance = new constructor(library.params, contentId, extras); 971 } 972 973 if (instance.$ === undefined) { 974 instance.$ = H5P.jQuery(instance); 975 } 976 977 if (instance.contentId === undefined) { 978 instance.contentId = contentId; 979 } 980 if (instance.subContentId === undefined && library.subContentId) { 981 instance.subContentId = library.subContentId; 982 } 983 if (instance.parent === undefined && extras && extras.parent) { 984 instance.parent = extras.parent; 985 } 986 if (instance.libraryInfo === undefined) { 987 instance.libraryInfo = { 988 versionedName: library.library, 989 versionedNameNoSpaces: machineName + '-' + versionSplit[0] + '.' + versionSplit[1], 990 machineName: machineName, 991 majorVersion: versionSplit[0], 992 minorVersion: versionSplit[1] 993 }; 994 } 995 996 if ($attachTo !== undefined) { 997 $attachTo.toggleClass('h5p-standalone', standalone); 998 instance.attach($attachTo); 999 H5P.trigger(instance, 'domChanged', { 1000 '$target': $attachTo, 1001 'library': machineName, 1002 'key': 'newLibrary' 1003 }, {'bubbles': true, 'external': true}); 1004 1005 if (skipResize === undefined || !skipResize) { 1006 // Resize content. 1007 H5P.trigger(instance, 'resize'); 1008 } 1009 } 1010 return instance; 1011}; 1012 1013/** 1014 * Used to print useful error messages. (to JavaScript error console) 1015 * 1016 * @param {*} err Error to print. 1017 */ 1018H5P.error = function (err) { 1019 if (window.console !== undefined && console.error !== undefined) { 1020 console.error(err.stack ? err.stack : err); 1021 } 1022}; 1023 1024/** 1025 * Translate text strings. 1026 * 1027 * @param {string} key 1028 * Translation identifier, may only contain a-zA-Z0-9. No spaces or special chars. 1029 * @param {Object} [vars] 1030 * Data for placeholders. 1031 * @param {string} [ns] 1032 * Translation namespace. Defaults to H5P. 1033 * @returns {string} 1034 * Translated text 1035 */ 1036H5P.t = function (key, vars, ns) { 1037 if (ns === undefined) { 1038 ns = 'H5P'; 1039 } 1040 1041 if (H5PIntegration.l10n[ns] === undefined) { 1042 return '[Missing translation namespace "' + ns + '"]'; 1043 } 1044 1045 if (H5PIntegration.l10n[ns][key] === undefined) { 1046 return '[Missing translation "' + key + '" in "' + ns + '"]'; 1047 } 1048 1049 var translation = H5PIntegration.l10n[ns][key]; 1050 1051 if (vars !== undefined) { 1052 // Replace placeholder with variables. 1053 for (var placeholder in vars) { 1054 translation = translation.replace(placeholder, vars[placeholder]); 1055 } 1056 } 1057 1058 return translation; 1059}; 1060 1061/** 1062 * Creates a new popup dialog over the H5P content. 1063 * 1064 * @class 1065 * @param {string} name 1066 * Used for html class. 1067 * @param {string} title 1068 * Used for header. 1069 * @param {string} content 1070 * Displayed inside the dialog. 1071 * @param {H5P.jQuery} $element 1072 * Which DOM element the dialog should be inserted after. 1073 * @param {H5P.jQuery} $returnElement 1074 * Which DOM element the focus should be moved to on close 1075 */ 1076H5P.Dialog = function (name, title, content, $element, $returnElement) { 1077 /** @alias H5P.Dialog# */ 1078 var self = this; 1079 this.activeElement = document.activeElement; 1080 var $dialog = H5P.jQuery('<div class="h5p-popup-dialog h5p-' + name + '-dialog" aria-labelledby="' + name + '-dialog-header" aria-modal="true" role="dialog" tabindex="-1">\ 1081 <div class="h5p-inner">\ 1082 <h2 id="' + name + '-dialog-header">' + title + '</h2>\ 1083 <div class="h5p-scroll-content">' + content + '</div>\ 1084 <div class="h5p-close" role="button" tabindex="0" aria-label="' + H5P.t('close') + '" title="' + H5P.t('close') + '"></div>\ 1085 </div>\ 1086 </div>') 1087 .insertAfter($element) 1088 .click(function (e) { 1089 if (e && e.originalEvent && e.originalEvent.preventClosing) { 1090 return; 1091 } 1092 1093 self.close(); 1094 }) 1095 .children('.h5p-inner') 1096 .click(function (e) { 1097 e.originalEvent.preventClosing = true; 1098 }) 1099 .find('.h5p-close') 1100 .click(function () { 1101 self.close(); 1102 }) 1103 .keypress(function (e) { 1104 if (e.which === 13 || e.which === 32) { 1105 self.close(); 1106 return false;
1107 } 1108 }) 1109 .end() 1110 .find('a') 1111 .click(function (e) { 1112 e.stopPropagation(); 1113 }) 1114 .end() 1115 .end(); 1116 1117 /** 1118 * Opens the dialog. 1119 */ 1120 self.open = function (scrollbar) { 1121 if (scrollbar) { 1122 $dialog.css('height', '100%'); 1123 } 1124 setTimeout(function () { 1125 $dialog.addClass('h5p-open'); // Fade in 1126 // Triggering an event, in case something has to be done after dialog has been opened. 1127 H5P.jQuery(self).trigger('dialog-opened', [$dialog]); 1128 $dialog.focus(); 1129 }, 1); 1130 }; 1131 1132 /** 1133 * Closes the dialog. 1134 */ 1135 self.close = function () { 1136 $dialog.removeClass('h5p-open'); // Fade out 1137 setTimeout(function () { 1138 $dialog.remove(); 1139 H5P.jQuery(self).trigger('dialog-closed', [$dialog]); 1140 $element.attr('tabindex', '-1'); 1141 if ($returnElement) { 1142 $returnElement.focus(); 1143 } 1144 else if(self.activeElement) { 1145 self.activeElement.focus(); 1146 } 1147 else { 1148 $element.focus(); 1149 } 1150 }, 200); 1151 }; 1152}; 1153 1154/** 1155 * Gather copyright information for the given content. 1156 * 1157 * @param {Object} instance 1158 * H5P instance to get copyright information for. 1159 * @param {Object} parameters 1160 * Parameters of the content instance. 1161 * @param {number} contentId 1162 * Identifies the H5P content 1163 * @param {Object} metadata 1164 * Metadata of the content instance. 1165 * @returns {string} Copyright information. 1166 */ 1167H5P.getCopyrights = function (instance, parameters, contentId, metadata) { 1168 var copyrights; 1169 1170 if (instance.getCopyrights !== undefined) { 1171 try { 1172 // Use the instance's own copyright generator 1173 copyrights = instance.getCopyrights(); 1174 } 1175 catch (err) { 1176 // Failed, prevent crashing page. 1177 } 1178 } 1179 1180 if (copyrights === undefined) { 1181 // Create a generic flat copyright list 1182 copyrights = new H5P.ContentCopyrights(); 1183 H5P.findCopyrights(copyrights, parameters, contentId); 1184 } 1185 1186 var metadataCopyrights = H5P.buildMetadataCopyrights(metadata, instance.libraryInfo.machineName); 1187 if (metadataCopyrights !== undefined) { 1188 copyrights.addMediaInFront(metadataCopyrights); 1189 } 1190 1191 if (copyrights !== undefined) { 1192 // Convert to string 1193 copyrights = copyrights.toString(); 1194 } 1195 return copyrights; 1196}; 1197 1198/** 1199 * Gather a flat list of copyright information from the given parameters. 1200 * 1201 * @param {H5P.ContentCopyrights} info 1202 * Used to collect all information in. 1203 * @param {(Object|Array)} parameters 1204 * To search for file objects in. 1205 * @param {number} contentId 1206 * Used to insert thumbnails for images. 1207 * @param {Object} extras - Extras. 1208 * @param {object} extras.metadata - Metadata 1209 * @param {object} extras.machineName - Library name of some kind. 1210 * Metadata of the content instance. 1211 */ 1212H5P.findCopyrights = function (info, parameters, contentId, extras) { 1213 // If extras are 1214 if (extras) { 1215 extras.params = parameters; 1216 buildFromMetadata(extras, extras.machineName, contentId); 1217 } 1218 1219 var lastContentTypeName; 1220 // Cycle through parameters 1221 for (var field in parameters) { 1222 if (!parameters.hasOwnProperty(field)) { 1223 continue; // Do not check 1224 } 1225 1226 /** 1227 * @deprecated This hack should be removed after 2017-11-01 1228 * The code that was using this was removed by HFP-574 1229 * This note was seen on 2018-04-04, and consultation with 1230 * higher authorities lead to keeping the code for now ;-) 1231 */ 1232 if (field === 'overrideSettings') { 1233 console.warn("The semantics field 'overrideSettings' is DEPRECATED and should not be used."); 1234 console.warn(parameters); 1235 continue; 1236 } 1237 1238 var value = parameters[field]; 1239 1240 if (value && value.library && typeof value.library === 'string') { 1241 lastContentTypeName = value.library.split(' ')[0]; 1242 } 1243 else if (value && value.library && typeof value.library === 'object') { 1244 lastContentTypeName = (value.library.library && typeof value.library.library === 'string') ? value.library.library.split(' ')[0] : lastContentTypeName; 1245 } 1246 1247 if (value instanceof Array) { 1248 // Cycle through array 1249 H5P.findCopyrights(info, value, contentId); 1250 } 1251 else if (value instanceof Object) { 1252 buildFromMetadata(value, lastContentTypeName, contentId); 1253 1254 // Check if object is a file with copyrights (old core) 1255 if (value.copyright === undefined || 1256 value.copyright.license === undefined || 1257 value.path === undefined || 1258 value.mime === undefined) { 1259 1260 // Nope, cycle throught object 1261 H5P.findCopyrights(info, value, contentId); 1262 } 1263 else { 1264 // Found file, add copyrights 1265 var copyrights = new H5P.MediaCopyright(value.copyright); 1266 if (value.width !== undefined && value.height !== undefined) { 1267 copyrights.setThumbnail(new H5P.Thumbnail(H5P.getPath(value.path, contentId), value.width, value.height)); 1268 } 1269 info.addMedia(copyrights); 1270 } 1271 } 1272 } 1273 1274 function buildFromMetadata(data, name, contentId) { 1275 if (data.metadata) { 1276 const metadataCopyrights = H5P.buildMetadataCopyrights(data.metadata, name); 1277 if (metadataCopyrights !== undefined) { 1278 if (data.params && data.params.contentName === 'Image' && data.params.file) { 1279 const path = data.params.file.path; 1280 const width = data.params.file.width; 1281 const height = data.params.file.height; 1282 metadataCopyrights.setThumbnail(new H5P.Thumbnail(H5P.getPath(path, contentId), width, height, data.params.alt)); 1283 } 1284 info.addMedia(metadataCopyrights); 1285 } 1286 } 1287 } 1288}; 1289 1290H5P.buildMetadataCopyrights = function (metadata) { 1291 if (metadata && metadata.license !== undefined && metadata.license !== 'U') { 1292 var dataset = { 1293 contentType: metadata.contentType, 1294 title: metadata.title, 1295 author: (metadata.authors && metadata.authors.length > 0) ? metadata.authors.map(function (author) { 1296 return (author.role) ? author.name + ' (' + author.role + ')' : author.name; 1297 }).join(', ') : undefined, 1298 source: metadata.source, 1299 year: (metadata.yearFrom) ? (metadata.yearFrom + ((metadata.yearTo) ? '-' + metadata.yearTo: '')) : undefined, 1300 license: metadata.license, 1301 version: metadata.licenseVersion, 1302 licenseExtras: metadata.licenseExtras, 1303 changes: (metadata.changes && metadata.changes.length > 0) ? metadata.changes.map(function (change) { 1304 return change.log + (change.author ? ', ' + change.author : '') + (change.date ? ', ' + change.date : ''); 1305 }).join(' / ') : undefined 1306 }; 1307 1308 return new H5P.MediaCopyright(dataset); 1309 } 1310}; 1311 1312/** 1313 * Display a dialog containing the download button and copy button. 1314 * 1315 * @param {H5P.jQuery} $element 1316 * @param {Object} contentData 1317 * @param {Object} library 1318 * @param {Object} instance 1319 * @param {number} contentId 1320 */ 1321H5P.openReuseDialog = function ($element, contentData, library, instance, contentId) { 1322 let html = ''; 1323 let buttonCount = 0; 1324 if (contentData.displayOptions.export) { 1325 html += '<button type="button" class="h5p-big-button h5p-download-button"><div class="h5p-button-title">Download as an .h5p file</div><div class="h5p-button-description">.h5p files may be uploaded to any web-site where H5P content may be created.</div></button>'; 1326 buttonCount += 1; 1327 } 1328 if (contentData.displayOptions.export && contentData.displayOptions.copy) { 1329 html += '<div class="h5p-horizontal-line-text"><span>or</span></div>'; 1330 } 1331 if (contentData.displayOptions.copy) { 1332 html += '<button type="button" class="h5p-big-button h5p-copy-button"><div class="h5p-button-title">Copy content</div><div class="h5p-button-description">Copied content may be pasted anywhere this content type is supported on this website.</div></button>'; 1333 buttonCount += 1; 1334 } 1335 1336 const dialog = new H5P.Dialog('reuse', H5P.t('reuseContent'), html, $element); 1337 1338 // Selecting embed code when dialog is opened 1339 H5P.jQuery(dialog).on('dialog-opened', function (e, $dialog) { 1340 const scrollContent = $dialog[0].querySelector('.h5p-scroll-content'); 1341 scrollContent.style.setProperty('--button-count', buttonCount); 1342 H5P.jQuery('<a href="https://h5p.org/node/442225" target="_blank">More Info</a>').click(function (e) { 1343 e.stopPropagation(); 1344 }).appendTo($dialog.find('h2')); 1345 $dialog.find('.h5p-download-button').click(function () { 1346 window.location.href = contentData.exportUrl; 1347 instance.triggerXAPI('downloaded'); 1348 dialog.close(); 1349 }); 1350 $dialog.find('.h5p-copy-button').click(function () { 1351 const item = new H5P.ClipboardItem(library); 1352 item.contentId = contentId; 1353 H5P.setClipboard(item); 1354 instance.triggerXAPI('copied'); 1355 dialog.close(); 1356 H5P.attachToastTo( 1357 H5P.jQuery('.h5p-content:first')[0], 1358 H5P.t('contentCopied'), 1359 { 1360 position: { 1361 horizontal: 'centered', 1362 vertical: 'centered', 1363 noOverflowX: true 1364 } 1365 } 1366 ); 1367 }); 1368 H5P.trigger(instance, 'resize'); 1369 }).on('dialog-closed', function () { 1370 H5P.trigger(instance, 'resize'); 1371 }); 1372 1373 dialog.open(); 1374}; 1375 1376/** 1377 * Display a dialog containing the embed code. 1378 * 1379 * @param {H5P.jQuery} $element 1380 * Element to insert dialog after. 1381 * @param {string} embedCode 1382 * The embed code. 1383 * @param {string} resizeCode 1384 * The advanced resize code 1385 * @param {Object} size 1386 * The content's size. 1387 * @param {number} size.width 1388 * @param {number} size.height 1389 */ 1390H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size, instance) { 1391 var fullEmbedCode = embedCode + resizeCode;
1392 var dialog = new H5P.Dialog('embed', H5P.t('embed'), '<textarea class="h5p-embed-code-container" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>' + H5P.t('size') + ': <input aria-label="'+ H5P.t('width') +'" type="text" value="' + Math.ceil(size.width) + '" class="h5p-embed-size"/> × <input aria-label="'+ H5P.t('width') +'" type="text" value="' + Math.ceil(size.height) + '" class="h5p-embed-size"/> px<br/><div role="button" tabindex="0" class="h5p-expander">' + H5P.t('showAdvanced') + '</div><div class="h5p-expander-content"><p>' + H5P.t('advancedHelp') + '</p><textarea class="h5p-embed-code-container" autocorrect="off" autocapitalize="off" spellcheck="false">' + resizeCode + '</textarea></div>', $element); 1393 1394 // Selecting embed code when dialog is opened 1395 H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { 1396 var $inner = $dialog.find('.h5p-inner'); 1397 var $scroll = $inner.find('.h5p-scroll-content'); 1398 var diff = $scroll.outerHeight() - $scroll.innerHeight(); 1399 var positionInner = function () { 1400 H5P.trigger(instance, 'resize'); 1401 }; 1402 1403 // Handle changing of width/height 1404 var $w = $dialog.find('.h5p-embed-size:eq(0)'); 1405 var $h = $dialog.find('.h5p-embed-size:eq(1)'); 1406 var getNum = function ($e, d) { 1407 var num = parseFloat($e.val()); 1408 if (isNaN(num)) { 1409 return d; 1410 } 1411 return Math.ceil(num); 1412 }; 1413 var updateEmbed = function () { 1414 $dialog.find('.h5p-embed-code-container:first').val(fullEmbedCode.replace(':w', getNum($w, size.width)).replace(':h', getNum($h, size.height))); 1415 }; 1416 1417 $w.change(updateEmbed); 1418 $h.change(updateEmbed); 1419 updateEmbed(); 1420 1421 // Select text and expand textareas 1422 $dialog.find('.h5p-embed-code-container').each(function () { 1423 H5P.jQuery(this).css('height', this.scrollHeight + 'px').focus(function () { 1424 H5P.jQuery(this).select(); 1425 }); 1426 }); 1427 $dialog.find('.h5p-embed-code-container').eq(0).select(); 1428 positionInner(); 1429 1430 // Expand advanced embed 1431 var expand = function () { 1432 var $expander = H5P.jQuery(this); 1433 var $content = $expander.next(); 1434 if ($content.is(':visible')) { 1435 $expander.removeClass('h5p-open').text(H5P.t('showAdvanced')).attr('aria-expanded', 'true'); 1436 $content.hide(); 1437 } 1438 else { 1439 $expander.addClass('h5p-open').text(H5P.t('hideAdvanced')).attr('aria-expanded', 'false'); 1440 $content.show(); 1441 } 1442 $dialog.find('.h5p-embed-code-container').each(function () { 1443 H5P.jQuery(this).css('height', this.scrollHeight + 'px'); 1444 }); 1445 positionInner(); 1446 }; 1447 $dialog.find('.h5p-expander').click(expand).keypress(function (event) { 1448 if (event.keyCode === 32) { 1449 expand.apply(this); 1450 return false; 1451 } 1452 }); 1453 }).on('dialog-closed', function () { 1454 H5P.trigger(instance, 'resize'); 1455 }); 1456 1457 dialog.open(); 1458}; 1459 1460/** 1461 * Show a toast message. 1462 * 1463 * The reference element could be dom elements the toast should be attached to, 1464 * or e.g. the document body for general toast messages. 1465 * 1466 * @param {DOM} element Reference element to show toast message for. 1467 * @param {string} message Message to show. 1468 * @param {object} [config] Configuration. 1469 * @param {string} [config.style=h5p-toast] Style name for the tooltip. 1470 * @param {number} [config.duration=3000] Toast message length in ms. 1471 * @param {object} [config.position] Relative positioning of the toast. 1472 * @param {string} [config.position.horizontal=centered] [before|left|centered|right|after]. 1473 * @param {string} [config.position.vertical=below] [above|top|centered|bottom|below]. 1474 * @param {number} [config.position.offsetHorizontal=0] Extra horizontal offset. 1475 * @param {number} [config.position.offsetVertical=0] Extra vetical offset. 1476 * @param {boolean} [config.position.noOverflowLeft=false] True to prevent overflow left. 1477 * @param {boolean} [config.position.noOverflowRight=false] True to prevent overflow right. 1478 * @param {boolean} [config.position.noOverflowTop=false] True to prevent overflow top. 1479 * @param {boolean} [config.position.noOverflowBottom=false] True to prevent overflow bottom. 1480 * @param {boolean} [config.position.noOverflowX=false] True to prevent overflow left and right. 1481 * @param {boolean} [config.position.noOverflowY=false] True to prevent overflow top and bottom. 1482 * @param {object} [config.position.overflowReference=document.body] DOM reference for overflow. 1483 */ 1484H5P.attachToastTo = function (element, message, config) { 1485 if (element === undefined || message === undefined) { 1486 return; 1487 } 1488 1489 const eventPath = function (evt) { 1490 var path = (evt.composedPath && evt.composedPath()) || evt.path; 1491 var target = evt.target; 1492 1493 if (path != null) { 1494 // Safari doesn't include Window, but it should. 1495 return (path.indexOf(window) < 0) ? path.concat(window) : path; 1496 } 1497 1498 if (target === window) { 1499 return [window]; 1500 } 1501 1502 function getParents(node, memo) { 1503 memo = memo || []; 1504 var parentNode = node.parentNode; 1505 1506 if (!parentNode) { 1507 return memo; 1508 } 1509 else { 1510 return getParents(parentNode, memo.concat(parentNode)); 1511 } 1512 } 1513 1514 return [target].concat(getParents(target), window); 1515 }; 1516 1517 /** 1518 * Handle click while toast is showing. 1519 */ 1520 const clickHandler = function (event) { 1521 /* 1522 * A common use case will be to attach toasts to buttons that are clicked. 1523 * The click would remove the toast message instantly without this check. 1524 * Children of the clicked element are also ignored. 1525 */ 1526 var path = eventPath(event); 1527 if (path.indexOf(element) !== -1) { 1528 return; 1529 } 1530 clearTimeout(timer); 1531 removeToast(); 1532 }; 1533 1534 1535 1536 /** 1537 * Remove the toast message. 1538 */ 1539 const removeToast = function () { 1540 document.removeEventListener('click', clickHandler); 1541 if (toast.parentNode) { 1542 toast.parentNode.removeChild(toast); 1543 } 1544 }; 1545 1546 /** 1547 * Get absolute coordinates for the toast. 1548 * 1549 * @param {DOM} element Reference element to show toast message for. 1550 * @param {DOM} toast Toast element. 1551 * @param {object} [position={}] Relative positioning of the toast message. 1552 * @param {string} [position.horizontal=centered] [before|left|centered|right|after]. 1553 * @param {string} [position.vertical=below] [above|top|centered|bottom|below]. 1554 * @param {number} [position.offsetHorizontal=0] Extra horizontal offset. 1555 * @param {number} [position.offsetVertical=0] Extra vetical offset. 1556 * @param {boolean} [position.noOverflowLeft=false] True to prevent overflow left. 1557 * @param {boolean} [position.noOverflowRight=false] True to prevent overflow right. 1558 * @param {boolean}
1558 [position.noOverflowTop=false] True to prevent overflow top. 1559 * @param {boolean} [position.noOverflowBottom=false] True to prevent overflow bottom. 1560 * @param {boolean} [position.noOverflowX=false] True to prevent overflow left and right. 1561 * @param {boolean} [position.noOverflowY=false] True to prevent overflow top and bottom. 1562 * @return {object} 1563 */ 1564 const getToastCoordinates = function (element, toast, position) { 1565 position = position || {}; 1566 position.offsetHorizontal = position.offsetHorizontal || 0; 1567 position.offsetVertical = position.offsetVertical || 0; 1568 1569 const toastRect = toast.getBoundingClientRect(); 1570 const elementRect = element.getBoundingClientRect(); 1571 1572 let left = 0; 1573 let top = 0; 1574 1575 // Compute horizontal position 1576 switch (position.horizontal) { 1577 case 'before': 1578 left = elementRect.left - toastRect.width - position.offsetHorizontal; 1579 break; 1580 case 'after': 1581 left = elementRect.left + elementRect.width + position.offsetHorizontal; 1582 break; 1583 case 'left': 1584 left = elementRect.left + position.offsetHorizontal; 1585 break; 1586 case 'right': 1587 left = elementRect.left + elementRect.width - toastRect.width - position.offsetHorizontal; 1588 break; 1589 case 'centered': 1590 left = elementRect.left + elementRect.width / 2 - toastRect.width / 2 + position.offsetHorizontal; 1591 break; 1592 default: 1593 left = elementRect.left + elementRect.width / 2 - toastRect.width / 2 + position.offsetHorizontal; 1594 } 1595 1596 // Compute vertical position 1597 switch (position.vertical) { 1598 case 'above': 1599 top = elementRect.top - toastRect.height - position.offsetVertical; 1600 break; 1601 case 'below': 1602 top = elementRect.top + elementRect.height + position.offsetVertical; 1603 break; 1604 case 'top': 1605 top = elementRect.top + position.offsetVertical; 1606 break; 1607 case 'bottom': 1608 top = elementRect.top + elementRect.height - toastRect.height - position.offsetVertical; 1609 break; 1610 case 'centered': 1611 top = elementRect.top + elementRect.height / 2 - toastRect.height / 2 + position.offsetVertical; 1612 break; 1613 default: 1614 top = elementRect.top + elementRect.height + position.offsetVertical; 1615 } 1616 1617 // Prevent overflow 1618 const overflowElement = document.body; 1619 const bounds = overflowElement.getBoundingClientRect(); 1620 if ((position.noOverflowLeft || position.noOverflowX) && (left < bounds.x)) { 1621 left = bounds.x; 1622 } 1623 if ((position.noOverflowRight || position.noOverflowX) && ((left + toastRect.width) > (bounds.x + bounds.width))) { 1624 left = bounds.x + bounds.width - toastRect.width; 1625 } 1626 if ((position.noOverflowTop || position.noOverflowY) && (top < bounds.y)) { 1627 top = bounds.y; 1628 } 1629 if ((position.noOverflowBottom || position.noOverflowY) && ((top + toastRect.height) > (bounds.y + bounds.height))) { 1630 left = bounds.y + bounds.height - toastRect.height; 1631 } 1632 1633 return {left: left, top: top}; 1634 }; 1635 1636 // Sanitization 1637 config = config || {}; 1638 config.style = config.style || 'h5p-toast'; 1639 config.duration = config.duration || 3000; 1640 1641 // Build toast 1642 const toast = document.createElement('div'); 1643 toast.setAttribute('id', config.style); 1644 toast.classList.add('h5p-toast-disabled'); 1645 toast.classList.add(config.style); 1646 1647 const msg = document.createElement('span'); 1648 msg.innerHTML = message; 1649 toast.appendChild(msg); 1650 1651 document.body.appendChild(toast); 1652 1653 // The message has to be set before getting the coordinates 1654 const coordinates = getToastCoordinates(element, toast, config.position); 1655 toast.style.left = Math.round(coordinates.left) + 'px'; 1656 toast.style.top = Math.round(coordinates.top) + 'px'; 1657 1658 toast.classList.remove('h5p-toast-disabled'); 1659 const timer = setTimeout(removeToast, config.duration); 1660 1661 // The toast can also be removed by clicking somewhere 1662 document.addEventListener('click', clickHandler); 1663}; 1664 1665/** 1666 * Copyrights for a H5P Content Library. 1667 * 1668 * @class 1669 */ 1670H5P.ContentCopyrights = function () { 1671 var label; 1672 var media = []; 1673 var content = []; 1674 1675 /** 1676 * Set label. 1677 * 1678 * @param {string} newLabel 1679 */ 1680 this.setLabel = function (newLabel) { 1681 label = newLabel; 1682 }; 1683 1684 /** 1685 * Add sub content. 1686 * 1687 * @param {H5P.MediaCopyright} newMedia 1688 */ 1689 this.addMedia = function (newMedia) { 1690 if (newMedia !== undefined) { 1691 media.push(newMedia); 1692 } 1693 }; 1694 1695 /** 1696 * Add sub content in front. 1697 * 1698 * @param {H5P.MediaCopyright} newMedia 1699 */ 1700 this.addMediaInFront = function (newMedia) { 1701 if (newMedia !== undefined) { 1702 media.unshift(newMedia); 1703 } 1704 }; 1705 1706 /** 1707 * Add sub content. 1708 * 1709 * @param {H5P.ContentCopyrights} newContent 1710 */ 1711 this.addContent = function (newContent) { 1712 if (newContent !== undefined) { 1713 content.push(newContent); 1714 } 1715 }; 1716 1717 /** 1718 * Print content copyright. 1719 * 1720 * @returns {string} HTML. 1721 */ 1722 this.toString = function () { 1723 var html = ''; 1724 1725 // Add media rights 1726 for (var i = 0; i < media.length; i++) { 1727 html += media[i]; 1728 } 1729 1730 // Add sub content rights 1731 for (i = 0; i < content.length; i++) { 1732 html += content[i]; 1733 } 1734 1735 1736 if (html !== '') { 1737 // Add a label to this info 1738 if (label !== undefined) { 1739 html = '<h3>' + label + '</h3>' + html; 1740 } 1741 1742 // Add wrapper 1743 html = '<div class="h5p-content-copyrights">' + html + '</div>'; 1744 } 1745 1746 return html; 1747 }; 1748}; 1749 1750/** 1751 * A ordered list of copyright fields for media. 1752 * 1753 * @class 1754 * @param {Object} copyright 1755 * Copyright information fields. 1756 * @param {Object} [labels] 1757 * Translation of labels. 1758 * @param {Array} [order] 1759 * Order of the fields. 1760 * @param {Object} [extraFields] 1761 * Add extra copyright fields. 1762 */ 1763H5P.MediaCopyright = function (copyright, labels, order, extraFields) { 1764 var thumbnail; 1765 var list = new H5P.DefinitionList(); 1766 1767 /** 1768 * Get translated label for field. 1769 * 1770 * @private 1771 * @param {string} fieldName 1772 * @returns {string} 1773 */ 1774 var getLabel = function (fieldName) { 1775 if (labels === undefined || labels[fieldName] === undefined) { 1776 return H5P.t(fieldName); 1777 } 1778 1779 return labels[fieldName]; 1780 }; 1781 1782 /** 1783 * Get humanized value for the license field. 1784 * 1785 * @private 1786 * @param {string} license 1787 * @param {string}
1787 [version] 1788 * @returns {string} 1789 */ 1790 var humanizeLicense = function (license, version) { 1791 var copyrightLicense = H5P.copyrightLicenses[license]; 1792 1793 // Build license string 1794 var value = ''; 1795 if (!(license === 'PD' && version)) { 1796 // Add license label 1797 value += (copyrightLicense.hasOwnProperty('label') ? copyrightLicense.label : copyrightLicense); 1798 } 1799 1800 // Check for version info 1801 var versionInfo; 1802 if (copyrightLicense.versions) { 1803 if (copyrightLicense.versions.default && (!version || !copyrightLicense.versions[version])) { 1804 version = copyrightLicense.versions.default; 1805 } 1806 if (version && copyrightLicense.versions[version]) { 1807 versionInfo = copyrightLicense.versions[version]; 1808 } 1809 } 1810 1811 if (versionInfo) { 1812 // Add license version 1813 if (value) { 1814 value += ' '; 1815 } 1816 value += (versionInfo.hasOwnProperty('label') ? versionInfo.label : versionInfo); 1817 } 1818 1819 // Add link if specified 1820 var link; 1821 if (copyrightLicense.hasOwnProperty('link')) { 1822 link = copyrightLicense.link.replace(':version', copyrightLicense.linkVersions ? copyrightLicense.linkVersions[version] : version); 1823 } 1824 else if (versionInfo && copyrightLicense.hasOwnProperty('link')) { 1825 link = versionInfo.link; 1826 } 1827 if (link) { 1828 value = '<a href="' + link + '" target="_blank">' + value + '</a>'; 1829 } 1830 1831 // Generate parenthesis 1832 var parenthesis = ''; 1833 if (license !== 'PD' && license !== 'C') { 1834 parenthesis += license; 1835 } 1836 if (version && version !== 'CC0 1.0') { 1837 if (parenthesis && license !== 'GNU GPL') { 1838 parenthesis += ' '; 1839 } 1840 parenthesis += version; 1841 } 1842 if (parenthesis) { 1843 value += ' (' + parenthesis + ')'; 1844 } 1845 if (license === 'C') { 1846 value += ' ©'; 1847 } 1848 1849 return value; 1850 }; 1851 1852 if (copyright !== undefined) { 1853 // Add the extra fields 1854 for (var field in extraFields) { 1855 if (extraFields.hasOwnProperty(field)) { 1856 copyright[field] = extraFields[field]; 1857 } 1858 } 1859 1860 if (order === undefined) { 1861 // Set default order 1862 order = ['contentType', 'title', 'license', 'author', 'year', 'source', 'licenseExtras', 'changes']; 1863 } 1864 1865 for (var i = 0; i < order.length; i++) { 1866 var fieldName = order[i]; 1867 if (copyright[fieldName] !== undefined && copyright[fieldName] !== '') { 1868 var humanValue = copyright[fieldName]; 1869 if (fieldName === 'license') { 1870 humanValue = humanizeLicense(copyright.license, copyright.version); 1871 } 1872 if (fieldName === 'source') { 1873 humanValue = (humanValue) ? '<a href="' + humanValue + '" target="_blank">' + humanValue + '</a>' : undefined; 1874 } 1875 list.add(new H5P.Field(getLabel(fieldName), humanValue)); 1876 } 1877 } 1878 } 1879 1880 /** 1881 * Set thumbnail. 1882 * 1883 * @param {H5P.Thumbnail} newThumbnail 1884 */ 1885 this.setThumbnail = function (newThumbnail) { 1886 thumbnail = newThumbnail; 1887 }; 1888 1889 /** 1890 * Checks if this copyright is undisclosed. 1891 * I.e. only has the license attribute set, and it's undisclosed. 1892 * 1893 * @returns {boolean} 1894 */ 1895 this.undisclosed = function () { 1896 if (list.size() === 1) { 1897 var field = list.get(0); 1898 if (field.getLabel() === getLabel('license') && field.getValue() === humanizeLicense('U')) { 1899 return true; 1900 } 1901 } 1902 return false; 1903 }; 1904 1905 /** 1906 * Print media copyright. 1907 * 1908 * @returns {string} HTML. 1909 */ 1910 this.toString = function () { 1911 var html = ''; 1912 1913 if (this.undisclosed()) { 1914 return html; // No need to print a copyright with a single undisclosed license. 1915 } 1916 1917 if (thumbnail !== undefined) { 1918 html += thumbnail; 1919 } 1920 html += list; 1921 1922 if (html !== '') { 1923 html = '<div class="h5p-media-copyright">' + html + '</div>'; 1924 } 1925 1926 return html; 1927 }; 1928}; 1929 1930/** 1931 * A simple and elegant class for creating thumbnails of images. 1932 * 1933 * @class 1934 * @param {string} source 1935 * @param {number} width 1936 * @param {number} height 1937 * @param {string} alt 1938 * alternative text for the thumbnail 1939 */ 1940H5P.Thumbnail = function (source, width, height, alt) {
1941 var thumbWidth, thumbHeight = 100; 1942 if (width !== undefined) { 1943 thumbWidth = Math.round(thumbHeight * (width / height)); 1944 } 1945 1946 /** 1947 * Print thumbnail. 1948 * 1949 * @returns {string} HTML. 1950 */ 1951 this.toString = function () { 1952 return '<img src="' + source + '" alt="' + (alt ? alt : '') + '" class="h5p-thumbnail" height="' + thumbHeight + '"' + (thumbWidth === undefined ? '' : ' width="' + thumbWidth + '"') + '/>'; 1953 }; 1954}; 1955 1956/** 1957 * Simple data structure class for storing a single field. 1958 * 1959 * @class 1960 * @param {string} label 1961 * @param {string} value 1962 */ 1963H5P.Field = function (label, value) { 1964 /** 1965 * Public. Get field label. 1966 * 1967 * @returns {String} 1968 */ 1969 this.getLabel = function () { 1970 return label; 1971 }; 1972 1973 /** 1974 * Public. Get field value. 1975 * 1976 * @returns {String} 1977 */ 1978 this.getValue = function () { 1979 return value; 1980 }; 1981}; 1982 1983/** 1984 * Simple class for creating a definition list. 1985 * 1986 * @class 1987 */ 1988H5P.DefinitionList = function () { 1989 var fields = []; 1990 1991 /** 1992 * Add field to list. 1993 * 1994 * @param {H5P.Field} field 1995 */ 1996 this.add = function (field) { 1997 fields.push(field); 1998 }; 1999 2000 /** 2001 * Get Number of fields. 2002 * 2003 * @returns {number} 2004 */ 2005 this.size = function () { 2006 return fields.length; 2007 }; 2008 2009 /** 2010 * Get field at given index. 2011 * 2012 * @param {number} index 2013 * @returns {H5P.Field} 2014 */ 2015 this.get = function (index) { 2016 return fields[index]; 2017 }; 2018 2019 /** 2020 * Print definition list. 2021 * 2022 * @returns {string} HTML. 2023 */ 2024 this.toString = function () { 2025 var html = ''; 2026 for (var i = 0; i < fields.length; i++) { 2027 var field = fields[i]; 2028 html += '<dt>' + field.getLabel() + '</dt><dd>' + field.getValue() + '</dd>'; 2029 } 2030 return (html === '' ? html : '<dl class="h5p-definition-list">' + html + '</dl>'); 2031 }; 2032}; 2033 2034/** 2035 * THIS FUNCTION/CLASS IS DEPRECATED AND WILL BE REMOVED. 2036 * 2037 * Helper object for keeping coordinates in the same format all over. 2038 * 2039 * @deprecated 2040 * Will be removed march 2016. 2041 * @class 2042 * @param {number} x 2043 * @param {number} y 2044 * @param {number} w 2045 * @param {number} h 2046 */ 2047H5P.Coords = function (x, y, w, h) { 2048 if ( !(this instanceof H5P.Coords) ) 2049 return new H5P.Coords(x, y, w, h); 2050 2051 /** @member {number} */ 2052 this.x = 0; 2053 /** @member {number} */ 2054 this.y = 0; 2055 /** @member {number} */ 2056 this.w = 1; 2057 /** @member {number} */ 2058 this.h = 1; 2059 2060 if (typeof(x) === 'object') { 2061 this.x = x.x; 2062 this.y = x.y; 2063 this.w = x.w; 2064 this.h = x.h; 2065 } 2066 else { 2067 if (x !== undefined) { 2068 this.x = x; 2069 } 2070 if (y !== undefined) { 2071 this.y = y; 2072 } 2073 if (w !== undefined) { 2074 this.w = w; 2075 } 2076 if (h !== undefined) { 2077 this.h = h; 2078 } 2079 } 2080 return this; 2081}; 2082 2083/** 2084 * Parse library string into values. 2085 * 2086 * @param {string} library 2087 * library in the format "machineName majorVersion.minorVersion" 2088 * @returns {Object} 2089 * library as an object with machineName, majorVersion and minorVersion properties 2090 * return false if the library parameter is invalid 2091 */ 2092H5P.libraryFromString = function (library) { 2093 var regExp = /(.+)\s(\d+)\.(\d+)$/g; 2094 var res = regExp.exec(library); 2095 if (res !== null) { 2096 return { 2097 'machineName': res[1], 2098 'majorVersion': parseInt(res[2]), 2099 'minorVersion': parseInt(res[3]) 2100 }; 2101 } 2102 else { 2103 return false; 2104 } 2105}; 2106 2107/** 2108 * Get the path to the library 2109 * 2110 * @param {string} library 2111 * The library identifier in the format "machineName-majorVersion.minorVersion". 2112 * @returns {string} 2113 * The full path to the library. 2114 */ 2115H5P.getLibraryPath = function (library) { 2116 if (H5PIntegration && 2117 H5PIntegration.libraryDirectories && 2118 library in H5PIntegration.libraryDirectories) { 2119 // Use H5PIntegration.libraryDirectories if it exists for this library 2120 library = H5PIntegration.libraryDirectories[library]; 2121 } 2122 2123 if (H5PIntegration.urlLibraries !== undefined) { 2124 // This is an override for those implementations that has a different libraries URL, e.g. Moodle 2125 return H5PIntegration.urlLibraries + '/' + library; 2126 } 2127 else { 2128 return H5PIntegration.url + '/libraries/' + library; 2129 } 2130}; 2131 2132/** 2133 * Recursivly clone the given object. 2134 * 2135 * @param {Object|Array} object 2136 * Object to clone. 2137 * @param {boolean} [recursive] 2138 * @returns {Object|Array} 2139 * A clone of object. 2140 */ 2141H5P.cloneObject = function (object, recursive) { 2142 // TODO: Consider if this needs to be in core. Doesn't $.extend do the same? 2143 var clone = object instanceof Array ? [] : {}; 2144 2145 for (var i in object) { 2146 if (object.hasOwnProperty(i)) { 2147 if (recursive !== undefined && recursive && typeof object[i] === 'object') { 2148 clone[i] = H5P.cloneObject(object[i], recursive); 2149 } 2150 else { 2151 clone[i] = object[i]; 2152 } 2153 } 2154 } 2155 2156 return clone; 2157}; 2158 2159/** 2160 * Remove all empty spaces before and after the value. 2161 * 2162 * @param {string} value 2163 * @returns {string} 2164 */ 2165H5P.trim = function (value) { 2166 return value.replace(/^\s+|\s+$/g, ''); 2167 2168 // TODO: Only include this or String.trim(). What is best? 2169 // I'm leaning towards implementing the missing ones: http://kangax.github.io/compat-table/es5/ 2170 // So should we make this function deprecated? 2171}; 2172 2173/** 2174 * Recursive function that detects deep empty structures. 2175 * 2176 * @param {*} value 2177 * @returns {bool} 2178 */ 2179H5P.isEmpty = value => { 2180 if (!value && value !== 0 && value !== false) { 2181 return true; // undefined, null, NaN and empty strings. 2182 } 2183 else if (Array.isArray(value)) { 2184 for (let i = 0; i < value.length; i++) { 2185 if (!H5P.isEmpty(value[i])) { 2186 return false; // Array contains a non-empty value 2187 } 2188 } 2189 return true; // Empty array 2190 } 2191 else if (typeof value === 'object') { 2192 for (let prop in value) { 2193 if (value.hasOwnProperty(prop) && !H5P.isEmpty(value[prop])) { 2194 return false; // Object contains a non-empty value 2195 } 2196 } 2197 return true; // Empty object 2198 } 2199 return false;
2200}; 2201 2202/** 2203 * Check if JavaScript path/key is loaded. 2204 * 2205 * @param {string} path 2206 * @returns {boolean} 2207 */ 2208H5P.jsLoaded = function (path) { 2209 H5PIntegration.loadedJs = H5PIntegration.loadedJs || []; 2210 return H5P.jQuery.inArray(path, H5PIntegration.loadedJs) !== -1; 2211}; 2212 2213/** 2214 * Check if styles path/key is loaded. 2215 * 2216 * @param {string} path 2217 * @returns {boolean} 2218 */ 2219H5P.cssLoaded = function (path) { 2220 H5PIntegration.loadedCss = H5PIntegration.loadedCss || []; 2221 return H5P.jQuery.inArray(path, H5PIntegration.loadedCss) !== -1; 2222}; 2223 2224/** 2225 * Shuffle an array in place. 2226 * 2227 * @param {Array} array 2228 * Array to shuffle 2229 * @returns {Array} 2230 * The passed array is returned for chaining. 2231 */ 2232H5P.shuffleArray = function (array) { 2233 // TODO: Consider if this should be a part of core. I'm guessing very few libraries are going to use it. 2234 if (!(array instanceof Array)) { 2235 return; 2236 } 2237 2238 var i = array.length, j, tempi, tempj; 2239 if ( i === 0 ) return false; 2240 while ( --i ) { 2241 j = Math.floor( Math.random() * ( i + 1 ) ); 2242 tempi = array[i]; 2243 tempj = array[j]; 2244 array[i] = tempj; 2245 array[j] = tempi; 2246 } 2247 return array; 2248}; 2249 2250/** 2251 * Post finished results for user. 2252 * 2253 * @deprecated 2254 * Do not use this function directly, trigger the finish event instead. 2255 * Will be removed march 2016 2256 * @param {number} contentId 2257 * Identifies the content 2258 * @param {number} score 2259 * Achieved score/points 2260 * @param {number} maxScore 2261 * The maximum score/points that can be achieved 2262 * @param {number} [time] 2263 * Reported time consumption/usage 2264 */ 2265H5P.setFinished = function (contentId, score, maxScore, time) { 2266 var validScore = typeof score === 'number' || score instanceof Number; 2267 if (validScore && H5PIntegration.postUserStatistics === true) { 2268 /** 2269 * Return unix timestamp for the given JS Date. 2270 * 2271 * @private 2272 * @param {Date} date 2273 * @returns {Number} 2274 */ 2275 var toUnix = function (date) { 2276 return Math.round(date.getTime() / 1000); 2277 }; 2278 2279 // Post the results 2280 const data = { 2281 contentId: contentId, 2282 score: score, 2283 maxScore: maxScore, 2284 opened: toUnix(H5P.opened[contentId]), 2285 finished: toUnix(new Date()), 2286 time: time 2287 }; 2288 H5P.jQuery.post(H5PIntegration.ajax.setFinished, data) 2289 .fail(function () { 2290 H5P.offlineRequestQueue.add(H5PIntegration.ajax.setFinished, data); 2291 }); 2292 } 2293}; 2294 2295// Add indexOf to browsers that lack them. (IEs) 2296if (!Array.prototype.indexOf) { 2297 Array.prototype.indexOf = function (needle) { 2298 for (var i = 0; i < this.length; i++) { 2299 if (this[i] === needle) { 2300 return i; 2301 } 2302 } 2303 return -1; 2304 }; 2305} 2306 2307// Need to define trim() since this is not available on older IEs, 2308// and trim is used in several libs 2309if (String.prototype.trim === undefined) { 2310 String.prototype.trim = function () { 2311 return H5P.trim(this); 2312 }; 2313} 2314 2315/** 2316 * Trigger an event on an instance 2317 * 2318 * Helper function that triggers an event if the instance supports event handling 2319 * 2320 * @param {Object} instance 2321 * Instance of H5P content 2322 * @param {string} eventType 2323 * Type of event to trigger 2324 * @param {*} data 2325 * @param {Object} extras 2326 */ 2327H5P.trigger = function (instance, eventType, data, extras) { 2328 // Try new event system first 2329 if (instance.trigger !== undefined) { 2330 instance.trigger(eventType, data, extras); 2331 } 2332 // Try deprecated event system 2333 else if (instance.$ !== undefined && instance.$.trigger !== undefined) { 2334 instance.$.trigger(eventType); 2335 } 2336}; 2337 2338/** 2339 * Register an event handler 2340 * 2341 * Helper function that registers an event handler for an event type if 2342 * the instance supports event handling 2343 * 2344 * @param {Object} instance 2345 * Instance of H5P content 2346 * @param {string} eventType 2347 * Type of event to listen for 2348 * @param {H5P.EventCallback} handler 2349 * Callback that gets triggered for events of the specified type 2350 */ 2351H5P.on = function (instance, eventType, handler) { 2352 // Try new event system first 2353 if (instance.on !== undefined) { 2354 instance.on(eventType, handler); 2355 } 2356 // Try deprecated event system 2357 else if (instance.$ !== undefined && instance.$.on !== undefined) { 2358 instance.$.on(eventType, handler); 2359 } 2360}; 2361 2362/** 2363 * Generate random UUID 2364 * 2365 * @returns {string} UUID 2366 */ 2367H5P.createUUID = function () { 2368 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (char) { 2369 var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8); 2370 return newChar.toString(16); 2371 }); 2372}; 2373 2374/** 2375 * Create title 2376 * 2377 * @param {string} rawTitle 2378 * @param {number} maxLength 2379 * @returns {string} 2380 */ 2381H5P.createTitle = function (rawTitle, maxLength) { 2382 if (!rawTitle) { 2383 return ''; 2384 } 2385 if (maxLength === undefined) { 2386 maxLength = 60; 2387 } 2388 var title = H5P.jQuery('<div></div>') 2389 .text( 2390 // Strip tags 2391 rawTitle.replace(/(<([^>]+)>)/ig,"") 2392 // Escape 2393 ).text(); 2394 if (title.length > maxLength) { 2395 title = title.substr(0, maxLength - 3) + '...'; 2396 } 2397 return title; 2398}; 2399 2400// Wrap in privates 2401(function ($) { 2402 2403 /** 2404 * Creates ajax requests for inserting, updateing and deleteing 2405 * content user data. 2406 * 2407 * @private 2408 * @param {number} contentId What content to store the data for. 2409 * @param {string} dataType Identifies the set of data for this content. 2410 * @param {string} subContentId Identifies sub content 2411 * @param {function} [done] Callback when ajax is done. 2412 * @param {object} [data] To be stored for future use. 2413 * @param {boolean} [preload=false] Data is loaded when content is loaded. 2414 * @param {boolean}
2414 [invalidate=false] Data is invalidated when content changes. 2415 * @param {boolean} [async=true] 2416 */ 2417 function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) { 2418 if (H5PIntegration.user === undefined) { 2419 // Not logged in, no use in saving. 2420 done('Not signed in.'); 2421 return; 2422 } 2423 2424 var options = { 2425 url: H5PIntegration.ajax.contentUserData.replace(':contentId', contentId).replace(':dataType', dataType).replace(':subContentId', subContentId ? subContentId : 0), 2426 dataType: 'json', 2427 async: async === undefined ? true : async 2428 }; 2429 if (data !== undefined) { 2430 options.type = 'POST'; 2431 options.data = { 2432 data: (data === null ? 0 : data), 2433 preload: (preload ? 1 : 0), 2434 invalidate: (invalidate ? 1 : 0) 2435 }; 2436 } 2437 else { 2438 options.type = 'GET'; 2439 } 2440 if (done !== undefined) { 2441 options.error = function (xhr, error) { 2442 done(error); 2443 }; 2444 options.success = function (response) { 2445 if (!response.success) { 2446 done(response.message); 2447 return; 2448 } 2449 2450 if (response.data === false || response.data === undefined) { 2451 done(); 2452 return; 2453 } 2454 2455 done(undefined, response.data); 2456 }; 2457 } 2458 2459 $.ajax(options); 2460 } 2461 2462 /** 2463 * Get user data for given content. 2464 * 2465 * @param {number} contentId 2466 * What content to get data for. 2467 * @param {string} dataId 2468 * Identifies the set of data for this content. 2469 * @param {function} done 2470 * Callback with error and data parameters. 2471 * @param {string} [subContentId] 2472 * Identifies which data belongs to sub content. 2473 */ 2474 H5P.getUserData = function (contentId, dataId, done, subContentId) { 2475 if (!subContentId) { 2476 subContentId = 0; // Default 2477 } 2478 2479 H5PIntegration.contents = H5PIntegration.contents || {}; 2480 var content = H5PIntegration.contents['cid-' + contentId] || {}; 2481 var preloadedData = content.contentUserData; 2482 if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId] !== undefined) { 2483 if (preloadedData[subContentId][dataId] === 'RESET') { 2484 done(undefined, null); 2485 return; 2486 } 2487 try { 2488 done(undefined, JSON.parse(preloadedData[subContentId][dataId])); 2489 } 2490 catch (err) { 2491 done(err); 2492 } 2493 } 2494 else { 2495 contentUserDataAjax(contentId, dataId, subContentId, function (err, data) { 2496 if (err || data === undefined) { 2497 done(err, data); 2498 return; // Error or no data 2499 } 2500 2501 // Cache in preloaded 2502 if (content.contentUserData === undefined) { 2503 content.contentUserData = preloadedData = {}; 2504 } 2505 if (preloadedData[subContentId] === undefined) { 2506 preloadedData[subContentId] = {}; 2507 } 2508 preloadedData[subContentId][dataId] = data; 2509 2510 // Done. Try to decode JSON 2511 try { 2512 done(undefined, JSON.parse(data)); 2513 } 2514 catch (e) { 2515 done(e); 2516 } 2517 }); 2518 } 2519 }; 2520 2521 /** 2522 * Async error handling. 2523 * 2524 * @callback H5P.ErrorCallback 2525 * @param {*} error 2526 */ 2527 2528 /** 2529 * Set user data for given content. 2530 * 2531 * @param {number} contentId 2532 * What content to get data for. 2533 * @param {string} dataId 2534 * Identifies the set of data for this content. 2535 * @param {Object} data 2536 * The data that is to be stored. 2537 * @param {Object} [extras] 2538 * Extra properties 2539 * @param {string} [extras.subContentId] 2540 * Identifies which data belongs to sub content. 2541 * @param {boolean} [extras.preloaded=true] 2542 * If the data should be loaded when content is loaded. 2543 * @param {boolean} [extras.deleteOnChange=false] 2544 * If the data should be invalidated when the content changes. 2545 * @param {H5P.ErrorCallback} [extras.errorCallback] 2546 * Callback with error as parameters. 2547 * @param {boolean} [extras.async=true] 2548 */ 2549 H5P.setUserData = function (contentId, dataId, data, extras) { 2550 var options = H5P.jQuery.extend(true, {}, { 2551 subContentId: 0, 2552 preloaded: true, 2553 deleteOnChange: false, 2554 async: true 2555 }, extras); 2556 2557 try { 2558 data = JSON.stringify(data); 2559 } 2560 catch (err) { 2561 if (options.errorCallback) { 2562 options.errorCallback(err); 2563 } 2564 return; // Failed to serialize. 2565 } 2566 2567 var content = H5PIntegration.contents['cid-' + contentId]; 2568 if (content === undefined) { 2569 content = H5PIntegration.contents['cid-' + contentId] = {}; 2570 } 2571 if (!content.contentUserData) { 2572 content.contentUserData = {}; 2573 } 2574 var preloadedData = content.contentUserData; 2575 if (preloadedData[options.subContentId] === undefined) { 2576 preloadedData[options.subContentId] = {}; 2577 } 2578 if (data === preloadedData[options.subContentId][dataId]) { 2579 return; // No need to save this twice. 2580 } 2581 2582 preloadedData[options.subContentId][dataId] = data; 2583 contentUserDataAjax(contentId, dataId, options.subContentId, function (error) { 2584 if (options.errorCallback && error) { 2585 options.errorCallback(error); 2586 } 2587 }, data, options.preloaded, options.deleteOnChange, options.async); 2588 }; 2589 2590 /** 2591 * Delete user data for given content. 2592 * 2593 * @param {number} contentId
2594 * What content to remove data for. 2595 * @param {string} dataId 2596 * Identifies the set of data for this content. 2597 * @param {string} [subContentId] 2598 * Identifies which data belongs to sub content. 2599 */ 2600 H5P.deleteUserData = function (contentId, dataId, subContentId) { 2601 if (!subContentId) { 2602 subContentId = 0; // Default 2603 } 2604 2605 // Remove from preloaded/cache 2606 var preloadedData = H5PIntegration.contents['cid-' + contentId].contentUserData; 2607 if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { 2608 delete preloadedData[subContentId][dataId]; 2609 } 2610 2611 contentUserDataAjax(contentId, dataId, subContentId, undefined, null); 2612 }; 2613 2614 /** 2615 * Function for getting content for a certain ID 2616 * 2617 * @param {number} contentId 2618 * @return {Object} 2619 */ 2620 H5P.getContentForInstance = function (contentId) { 2621 var key = 'cid-' + contentId; 2622 var exists = H5PIntegration && H5PIntegration.contents && 2623 H5PIntegration.contents[key]; 2624 2625 return exists ? H5PIntegration.contents[key] : undefined; 2626 }; 2627 2628 /** 2629 * Prepares the content parameters for storing in the clipboard. 2630 * 2631 * @class 2632 * @param {Object} parameters The parameters for the content to store 2633 * @param {string} [genericProperty] If only part of the parameters are generic, which part 2634 * @param {string} [specificKey] If the parameters are specific, what content type does it fit 2635 * @returns {Object} Ready for the clipboard 2636 */ 2637 H5P.ClipboardItem = function (parameters, genericProperty, specificKey) { 2638 var self = this; 2639 2640 /** 2641 * Set relative dimensions when params contains a file with a width and a height. 2642 * Very useful to be compatible with wysiwyg editors. 2643 * 2644 * @private 2645 */ 2646 var setDimensionsFromFile = function () { 2647 if (!self.generic) { 2648 return; 2649 } 2650 var params = self.specific[self.generic]; 2651 if (!params.params.file || !params.params.file.width || !params.params.file.height) { 2652 return; 2653 } 2654 2655 self.width = 20; // % 2656 self.height = (params.params.file.height / params.params.file.width) * self.width; 2657 }; 2658 2659 if (!genericProperty) { 2660 genericProperty = 'action'; 2661 parameters = { 2662 action: parameters 2663 }; 2664 } 2665 2666 self.specific = parameters; 2667 2668 if (genericProperty && parameters[genericProperty]) { 2669 self.generic = genericProperty; 2670 } 2671 if (specificKey) { 2672 self.from = specificKey; 2673 } 2674 2675 if (window.H5PEditor && H5PEditor.contentId) { 2676 self.contentId = H5PEditor.contentId; 2677 } 2678 2679 if (!self.specific.width && !self.specific.height) { 2680 setDimensionsFromFile(); 2681 } 2682 }; 2683 2684 /** 2685 * Store item in the H5P Clipboard. 2686 * 2687 * @param {H5P.ClipboardItem|*} clipboardItem 2688 */ 2689 H5P.clipboardify = function (clipboardItem) { 2690 if (!(clipboardItem instanceof H5P.ClipboardItem)) { 2691 clipboardItem = new H5P.ClipboardItem(clipboardItem); 2692 } 2693 H5P.setClipboard(clipboardItem); 2694 }; 2695 2696 /** 2697 * Retrieve parsed clipboard data. 2698 * 2699 * @return {Object} 2700 */ 2701 H5P.getClipboard = function () { 2702 return parseClipboard(); 2703 }; 2704 2705 /** 2706 * Set item in the H5P Clipboard. 2707 * 2708 * @param {H5P.ClipboardItem|object} clipboardItem - Data to be set. 2709 */ 2710 H5P.setClipboard = function (clipboardItem) { 2711 localStorage.setItem('h5pClipboard', JSON.stringify(clipboardItem)); 2712 2713 // Trigger an event so all 'Paste' buttons may be enabled. 2714 H5P.externalDispatcher.trigger('datainclipboard', {reset: false}); 2715 }; 2716 2717 /** 2718 * Get config for a library 2719 * 2720 * @param string machineName 2721 * @return Object 2722 */ 2723 H5P.getLibraryConfig = function (machineName) { 2724 var hasConfig = H5PIntegration.libraryConfig && H5PIntegration.libraryConfig[machineName]; 2725 return hasConfig ? H5PIntegration.libraryConfig[machineName] : {}; 2726 }; 2727 2728 /** 2729 * Get item from the H5P Clipboard. 2730 * 2731 * @private 2732 * @return {Object} 2733 */ 2734 var parseClipboard = function () { 2735 var clipboardData = localStorage.getItem('h5pClipboard'); 2736 if (!clipboardData) { 2737 return; 2738 } 2739 2740 // Try to parse clipboard dat 2741 try { 2742 clipboardData = JSON.parse(clipboardData); 2743 } 2744 catch (err) { 2745 console.error('Unable to parse JSON from clipboard.', err); 2746 return; 2747 } 2748 2749 // Update file URLs and reset content Ids 2750 recursiveUpdate(clipboardData.specific, function (path) { 2751 var isTmpFile = (path.substr(-4, 4) === '#tmp'); 2752 if (!isTmpFile && clipboardData.contentId && !path.match(/^https?:\/\//i)) { 2753 // Comes from existing content 2754 2755 let prefix; 2756 if (H5PEditor.contentId) { 2757 // .. to existing content 2758 prefix = '../' + clipboardData.contentId + '/'; 2759 } 2760 else { 2761 // .. to new content 2762 prefix = (H5PEditor.contentRelUrl ? H5PEditor.contentRelUrl : '../content/') + clipboardData.contentId + '/'; 2763 } 2764 return path.substr(0, prefix.length) === prefix ? path : prefix + path; 2765 } 2766 2767 return path; // Will automatically be looked for in tmp folder 2768 }); 2769 2770 2771 if (clipboardData.generic) { 2772 // Use reference instead of key 2773 clipboardData.generic = clipboardData.specific[clipboardData.generic]; 2774 } 2775 2776 return clipboardData; 2777 }; 2778 2779 /** 2780 * Update file URLs and reset content IDs. 2781 * Useful when copying content. 2782 * 2783 * @private 2784 * @param {object} params Reference 2785 * @param {function} handler Modifies the path to work when pasted 2786 */ 2787 var recursiveUpdate = function (params, handler) { 2788 for (var prop in params) { 2789 if (params.hasOwnProperty(prop) && params[prop] instanceof Object) { 2790 var obj = params[prop]; 2791 if (obj.path !== undefined && obj.mime !== undefined) { 2792 obj.path = handler(obj.path); 2793 } 2794 else { 2795 if (obj.library !== undefined && obj.subContentId !== undefined) { 2796 // Avoid multiple content with same ID 2797 delete obj.subContentId; 2798 } 2799 recursiveUpdate(obj, handler); 2800 } 2801 } 2802 } 2803 }; 2804 2805 // Init H5P when page is fully loadded 2806 $(document).ready(function () { 2807 2808 window.addEventListener('storage', function (event) { 2809 // Pick up clipboard changes from other tabs 2810 if (event.key === 'h5pClipboard') { 2811 // Trigger an event so all 'Paste' buttons may be enabled. 2812 H5P.externalDispatcher.trigger('datainclipboard', {reset: event.newValue === null}); 2813 } 2814 }); 2815 2816 var ccVersions = { 2817 'default': '4.0', 2818 '4.0': H5P.t('licenseCC40'), 2819 '3.0': H5P.t('licenseCC30'), 2820 '2.5': H5P.t('licenseCC25'), 2821 '2.0': H5P.t('licenseCC20'), 2822 '1.0': H5P.t('licenseCC10'), 2823 }; 2824 2825 /** 2826 * Maps copyright license codes to their human readable counterpart. 2827 * 2828 * @type {Object} 2829 */ 2830 H5P.copyrightLicenses = { 2831 'U': H5P.t('licenseU'), 2832 'CC BY': { 2833 label: H5P.t('licenseCCBY'), 2834 link: 'http://creativecommons.org/licenses/by/:version', 2835 versions: ccVersions 2836 }, 2837 'CC BY-SA': { 2838 label: H5P.t('licenseCCBYSA'), 2839 link: 'http://creativecommons.org/licenses/by-sa/:version', 2840 versions: ccVersions 2841 }, 2842 'CC BY-ND': { 2843 label: H5P.t('licenseCCBYND'), 2844 link: 'http://creativecommons.org/licenses/by-nd/:version', 2845 versions: ccVersions 2846 }, 2847 'CC BY-NC': { 2848 label: H5P.t('licenseCCBYNC'), 2849 link: 'http://creativecommons.org/licenses/by-nc/:version', 2850 versions: ccVersions 2851 }, 2852 'CC BY-NC-SA': { 2853 label: H5P.t('licenseCCBYNCSA'), 2854 link: 'http://creativecommons.org/licenses/by-nc-sa/:version', 2855 versions: ccVersions 2856 }, 2857 'CC BY-NC-ND': { 2858 label: H5P.t('licenseCCBYNCND'), 2859 link: 'http://creativecommons.org/licenses/by-nc-nd/:version', 2860 versions: ccVersions 2861 }, 2862 'CC0 1.0': { 2863 label: H5P.t('licenseCC010'), 2864 link: 'https://creativecommons.org/publicdomain/zero/1.0/' 2865 }, 2866 'GNU GPL': { 2867 label: H5P.t('licenseGPL'), 2868 link: 'http://www.gnu.org/licenses/gpl-:version-standalone.html', 2869 linkVersions: { 2870 'v3': '3.0', 2871 'v2': '2.0', 2872 'v1': '1.0' 2873 }, 2874 versions: { 2875 'default': 'v3', 2876 'v3': H5P.t('licenseV3'),
2877 'v2': H5P.t('licenseV2'), 2878 'v1': H5P.t('licenseV1') 2879 } 2880 }, 2881 'PD': { 2882 label: H5P.t('licensePD'), 2883 versions: { 2884 'CC0 1.0': { 2885 label: H5P.t('licenseCC010'), 2886 link: 'https://creativecommons.org/publicdomain/zero/1.0/' 2887 }, 2888 'CC PDM': { 2889 label: H5P.t('licensePDM'), 2890 link: 'https://creativecommons.org/publicdomain/mark/1.0/' 2891 } 2892 } 2893 }, 2894 'ODC PDDL': '<a href="http://opendatacommons.org/licenses/pddl/1.0/" target="_blank">Public Domain Dedication and Licence</a>', 2895 'CC PDM': { 2896 label: H5P.t('licensePDM'), 2897 link: 'https://creativecommons.org/publicdomain/mark/1.0/' 2898 }, 2899 'C': H5P.t('licenseC'), 2900 }; 2901 2902 /** 2903 * Indicates if H5P is embedded on an external page using iframe. 2904 * @member {boolean} H5P.externalEmbed 2905 */ 2906 2907 // Relay events to top window. This must be done before H5P.init 2908 // since events may be fired on initialization. 2909 if (H5P.isFramed && H5P.externalEmbed === false) { 2910 H5P.externalDispatcher.on('*', function (event) { 2911 window.parent.H5P.externalDispatcher.trigger.call(this, event); 2912 }); 2913 } 2914 2915 /** 2916 * Prevent H5P Core from initializing. Must be overriden before document ready. 2917 * @member {boolean} H5P.preventInit 2918 */ 2919 if (!H5P.preventInit) { 2920 // Note that this start script has to be an external resource for it to 2921 // load in correct order in IE9. 2922 H5P.init(document.body); 2923 } 2924 2925 if (H5PIntegration.saveFreq !== false) { 2926 // When was the last state stored 2927 var lastStoredOn = 0; 2928 // Store the current state of the H5P when leaving the page. 2929 var storeCurrentState = function () { 2930 // Make sure at least 250 ms has passed since last save 2931 var currentTime = new Date().getTime(); 2932 if (currentTime - lastStoredOn > 250) { 2933 lastStoredOn = currentTime; 2934 for (var i = 0; i < H5P.instances.length; i++) { 2935 var instance = H5P.instances[i]; 2936 if (instance.getCurrentState instanceof Function || 2937 typeof instance.getCurrentState === 'function') { 2938 var state = instance.getCurrentState(); 2939 if (state !== undefined) { 2940 // Async is not used to prevent the request from being cancelled. 2941 H5P.setUserData(instance.contentId, 'state', state, {deleteOnChange: true, async: false}); 2942 } 2943 } 2944 } 2945 } 2946 }; 2947 // iPad does not support beforeunload, therefore using unload 2948 H5P.$window.one('beforeunload unload', function () { 2949 // Only want to do this once 2950 H5P.$window.off('pagehide beforeunload unload'); 2951 storeCurrentState(); 2952 }); 2953 // pagehide is used on iPad when tabs are switched 2954 H5P.$window.on('pagehide', storeCurrentState); 2955 } 2956 }); 2957 2958})(H5P.jQuery); 2959; 2960var H5P = window.H5P = window.H5P || {}; 2961 2962/** 2963 * The Event class for the EventDispatcher. 2964 * 2965 * @class 2966 * @param {string} type 2967 * @param {*} data 2968 * @param {Object} [extras] 2969 * @param {boolean} [extras.bubbles] 2970 * @param {boolean} [extras.external] 2971 */ 2972H5P.Event = function (type, data, extras) { 2973 this.type = type; 2974 this.data = data; 2975 var bubbles = false; 2976 2977 // Is this an external event? 2978 var external = false; 2979 2980 // Is this event scheduled to be sent externally? 2981 var scheduledForExternal = false; 2982 2983 if (extras === undefined) { 2984 extras = {}; 2985 } 2986 if (extras.bubbles === true) { 2987 bubbles = true; 2988 } 2989 if (extras.external === true) { 2990 external = true; 2991 } 2992 2993 /** 2994 * Prevent this event from bubbling up to parent 2995 */ 2996 this.preventBubbling = function () { 2997 bubbles = false; 2998 }; 2999 3000 /** 3001 * Get bubbling status 3002 * 3003 * @returns {boolean} 3004 * true if bubbling false otherwise 3005 */ 3006 this.getBubbles = function () { 3007 return bubbles; 3008 }; 3009 3010 /** 3011 * Try to schedule an event for externalDispatcher 3012 * 3013 * @returns {boolean} 3014 * true if external and not already scheduled, otherwise false 3015 */ 3016 this.scheduleForExternal = function () {
3017 if (external && !scheduledForExternal) { 3018 scheduledForExternal = true; 3019 return true; 3020 } 3021 return false; 3022 }; 3023}; 3024 3025/** 3026 * Callback type for event listeners. 3027 * 3028 * @callback H5P.EventCallback 3029 * @param {H5P.Event} event 3030 */ 3031 3032H5P.EventDispatcher = (function () { 3033 3034 /** 3035 * The base of the event system. 3036 * Inherit this class if you want your H5P to dispatch events. 3037 * 3038 * @class 3039 * @memberof H5P 3040 */ 3041 function EventDispatcher() { 3042 var self = this; 3043 3044 /** 3045 * Keep track of listeners for each event. 3046 * 3047 * @private 3048 * @type {Object} 3049 */ 3050 var triggers = {}; 3051 3052 /** 3053 * Add new event listener. 3054 * 3055 * @throws {TypeError} 3056 * listener must be a function 3057 * @param {string} type 3058 * Event type 3059 * @param {H5P.EventCallback} listener 3060 * Event listener 3061 * @param {Object} [thisArg] 3062 * Optionally specify the this value when calling listener. 3063 */ 3064 this.on = function (type, listener, thisArg) { 3065 if (typeof listener !== 'function') { 3066 throw TypeError('listener must be a function'); 3067 } 3068 3069 // Trigger event before adding to avoid recursion 3070 self.trigger('newListener', {'type': type, 'listener': listener}); 3071 3072 var trigger = {'listener': listener, 'thisArg': thisArg}; 3073 if (!triggers[type]) { 3074 // First 3075 triggers[type] = [trigger]; 3076 } 3077 else { 3078 // Append 3079 triggers[type].push(trigger); 3080 } 3081 }; 3082 3083 /** 3084 * Add new event listener that will be fired only once. 3085 * 3086 * @throws {TypeError} 3087 * listener must be a function 3088 * @param {string} type 3089 * Event type 3090 * @param {H5P.EventCallback} listener 3091 * Event listener 3092 * @param {Object} thisArg 3093 * Optionally specify the this value when calling listener. 3094 */ 3095 this.once = function (type, listener, thisArg) { 3096 if (!(listener instanceof Function)) { 3097 throw TypeError('listener must be a function'); 3098 } 3099 3100 var once = function (event) { 3101 self.off(event.type, once); 3102 listener.call(this, event); 3103 }; 3104 3105 self.on(type, once, thisArg); 3106 }; 3107 3108 /** 3109 * Remove event listener. 3110 * If no listener is specified, all listeners will be removed. 3111 * 3112 * @throws {TypeError} 3113 * listener must be a function 3114 * @param {string} type 3115 * Event type 3116 * @param {H5P.EventCallback} listener 3117 * Event listener 3118 */ 3119 this.off = function (type, listener) { 3120 if (listener !== undefined && !(listener instanceof Function)) { 3121 throw TypeError('listener must be a function'); 3122 } 3123 3124 if (triggers[type] === undefined) { 3125 return; 3126 } 3127 3128 if (listener === undefined) { 3129 // Remove all listeners 3130 delete triggers[type]; 3131 self.trigger('removeListener', type); 3132 return; 3133 } 3134 3135 // Find specific listener 3136 for (var i = 0; i < triggers[type].length; i++) { 3137 if (triggers[type][i].listener === listener) { 3138 triggers[type].splice(i, 1); 3139 self.trigger('removeListener', type, {'listener': listener}); 3140 break; 3141 } 3142 } 3143 3144 // Clean up empty arrays 3145 if (!triggers[type].length) { 3146 delete triggers[type]; 3147 } 3148 }; 3149 3150 /** 3151 * Try to call all event listeners for the given event type. 3152 * 3153 * @private 3154 * @param {string} Event type 3155 */ 3156 var call = function (type, event) { 3157 if (triggers[type] === undefined) { 3158 return; 3159 } 3160 3161 // Clone array (prevents triggers from being modified during the event) 3162 var handlers = triggers[type].slice(); 3163 3164 // Call all listeners 3165 for (var i = 0; i < handlers.length; i++) { 3166 var trigger = handlers[i]; 3167 var thisArg = (trigger.thisArg ? trigger.thisArg : this); 3168 trigger.listener.call(thisArg, event); 3169 } 3170 }; 3171 3172 /** 3173 * Dispatch event. 3174 * 3175 * @param {string|H5P.Event} event 3176 * Event object or event type as string 3177 * @param {*} [eventData] 3178 * Custom event data(used when event type as string is used as first 3179 * argument). 3180 * @param {Object} [extras] 3181 * @param {boolean} [extras.bubbles] 3182 * @param {boolean} [extras.external] 3183 */ 3184 this.trigger = function (event, eventData, extras) { 3185 if (event === undefined) { 3186 return; 3187 } 3188 if (event instanceof String || typeof event === 'string') { 3189 event = new H5P.Event(event, eventData, extras); 3190 } 3191 else if (eventData !== undefined) { 3192 event.data = eventData; 3193 } 3194 3195 // Check to see if this event should go externally after all triggering and bubbling is done
3196 var scheduledForExternal = event.scheduleForExternal(); 3197 3198 // Call all listeners 3199 call.call(this, event.type, event); 3200 3201 // Call all * listeners 3202 call.call(this, '*', event); 3203 3204 // Bubble 3205 if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && 3206 (self.parent.trigger instanceof Function || typeof self.parent.trigger === 'function')) { 3207 self.parent.trigger(event); 3208 } 3209 3210 if (scheduledForExternal) { 3211 H5P.externalDispatcher.trigger.call(this, event); 3212 } 3213 }; 3214 } 3215 3216 return EventDispatcher; 3217})(); 3218; 3219var H5P = window.H5P = window.H5P || {}; 3220 3221/** 3222 * Used for xAPI events. 3223 * 3224 * @class 3225 * @extends H5P.Event 3226 */ 3227H5P.XAPIEvent = function () { 3228 H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true, external: true}); 3229}; 3230 3231H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); 3232H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent; 3233 3234/** 3235 * Set scored result statements. 3236 * 3237 * @param {number} score 3238 * @param {number} maxScore 3239 * @param {object} instance 3240 * @param {boolean} completion 3241 * @param {boolean} success 3242 */ 3243H5P.XAPIEvent.prototype.setScoredResult = function (score, maxScore, instance, completion, success) { 3244 this.data.statement.result = {}; 3245 3246 if (typeof score !== 'undefined') { 3247 if (typeof maxScore === 'undefined') { 3248 this.data.statement.result.score = {'raw': score}; 3249 } 3250 else { 3251 this.data.statement.result.score = { 3252 'min': 0, 3253 'max': maxScore, 3254 'raw': score 3255 }; 3256 if (maxScore > 0) { 3257 this.data.statement.result.score.scaled = Math.round(score / maxScore * 10000) / 10000; 3258 } 3259 } 3260 } 3261 3262 if (typeof completion === 'undefined') { 3263 this.data.statement.result.completion = (this.getVerb() === 'completed' || this.getVerb() === 'answered'); 3264 } 3265 else { 3266 this.data.statement.result.completion = completion; 3267 } 3268 3269 if (typeof success !== 'undefined') { 3270 this.data.statement.result.success = success; 3271 } 3272 3273 if (instance && instance.activityStartTime) { 3274 var duration = Math.round((Date.now() - instance.activityStartTime ) / 10) / 100; 3275 // xAPI spec allows a precision of 0.01 seconds 3276 3277 this.data.statement.result.duration = 'PT' + duration + 'S'; 3278 } 3279}; 3280 3281/** 3282 * Set a verb. 3283 * 3284 * @param {string} verb 3285 * Verb in short form, one of the verbs defined at 3286 * {@link http://adlnet.gov/expapi/verbs/|ADL xAPI Vocabulary} 3287 * 3288 */ 3289H5P.XAPIEvent.prototype.setVerb = function (verb) { 3290 if (H5P.jQuery.inArray(verb, H5P.XAPIEvent.allowedXAPIVerbs) !== -1) { 3291 this.data.statement.verb = { 3292 'id': 'http://adlnet.gov/expapi/verbs/' + verb, 3293 'display': { 3294 'en-US': verb 3295 } 3296 }; 3297 } 3298 else if (verb.id !== undefined) { 3299 this.data.statement.verb = verb; 3300 } 3301}; 3302 3303/** 3304 * Get the statements verb id. 3305 * 3306 * @param {boolean} full 3307 * if true the full verb id prefixed by http://adlnet.gov/expapi/verbs/ 3308 * will be returned 3309 * @returns {string} 3310 * Verb or null if no verb with an id has been defined 3311 */ 3312H5P.XAPIEvent.prototype.getVerb = function (full) { 3313 var statement = this.data.statement; 3314 if ('verb' in statement) { 3315 if (full === true) { 3316 return statement.verb; 3317 } 3318 return statement.verb.id.slice(31); 3319 } 3320 else { 3321 return null; 3322 } 3323}; 3324 3325/** 3326 * Set the object part of the statement. 3327 * 3328 * The id is found automatically (the url to the content) 3329 * 3330 * @param {Object} instance 3331 * The H5P instance 3332 */ 3333H5P.XAPIEvent.prototype.setObject = function (instance) { 3334 if (instance.contentId) { 3335 this.data.statement.object = { 3336 'id': this.getContentXAPIId(instance), 3337 'objectType': 'Activity', 3338 'definition': { 3339 'extensions': { 3340 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId 3341 } 3342 } 3343 }; 3344 if (instance.subContentId) { 3345 this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-subContentId'] = instance.subContentId; 3346 // Don't set titles on main content, title should come from publishing platform 3347 if (typeof instance.getTitle === 'function') { 3348 this.data.statement.object.definition.name = { 3349 "en-US": instance.getTitle() 3350 }; 3351 } 3352 } 3353 else { 3354 var content = H5P.getContentForInstance(instance.contentId); 3355 if (content && content.metadata && content.metadata.title) { 3356 this.data.statement.object.definition.name = { 3357 "en-US": H5P.createTitle(content.metadata.title) 3358 }; 3359 } 3360 } 3361 } 3362 else { 3363 // Content types view always expect to have a contentId when they are displayed. 3364 // This is not the case if they are displayed in the editor as part of a preview. 3365 // The fix is to set an empty object with definition for the xAPI event, so all 3366 // the content types that rely on this does not have to handle it. This means 3367 // that content types that are being previewed will send xAPI completed events, 3368 // but since there are no scripts that catch these events in the editor, 3369 // this is not a problem. 3370 this.data.statement.object = { 3371 definition: {} 3372 }; 3373 } 3374}; 3375 3376/** 3377 * Set the context part of the statement. 3378 * 3379 * @param {Object} instance 3380 * The H5P instance 3381 */ 3382H5P.XAPIEvent.prototype.setContext = function (instance) { 3383 if (instance.parent && (instance.parent.contentId || instance.parent.subContentId)) { 3384 this.data.statement.context = { 3385 "contextActivities": { 3386 "parent": [ 3387 { 3388 "id": this.getContentXAPIId(instance.parent), 3389 "objectType": "Activity" 3390 } 3391 ] 3392 } 3393 }; 3394 } 3395 if (instance.libraryInfo) { 3396 if (this.data.statement.context === undefined) { 3397 this.data.statement.context = {"contextActivities":{}}; 3398 } 3399 this.data.statement.context.contextActivities.category = [ 3400 { 3401 "id": "http://h5p.org/libraries/" + instance.libraryInfo.versionedNameNoSpaces, 3402 "objectType": "Activity" 3403 } 3404 ]; 3405 } 3406}; 3407 3408/** 3409 * Set the actor. Email and name will be added automatically. 3410 */ 3411H5P.XAPIEvent.prototype.setActor = function () { 3412 if (H5PIntegration.user !== undefined) { 3413 this.data.statement.actor = {
3414 'name': H5PIntegration.user.name, 3415 'mbox': 'mailto:' + H5PIntegration.user.mail, 3416 'objectType': 'Agent' 3417 }; 3418 } 3419 else { 3420 var uuid; 3421 try { 3422 if (localStorage.H5PUserUUID) { 3423 uuid = localStorage.H5PUserUUID; 3424 } 3425 else { 3426 uuid = H5P.createUUID(); 3427 localStorage.H5PUserUUID = uuid; 3428 } 3429 } 3430 catch (err) { 3431 // LocalStorage and Cookies are probably disabled. Do not track the user. 3432 uuid = 'not-trackable-' + H5P.createUUID(); 3433 } 3434 this.data.statement.actor = { 3435 'account': { 3436 'name': uuid, 3437 'homePage': H5PIntegration.siteUrl 3438 }, 3439 'objectType': 'Agent' 3440 }; 3441 } 3442}; 3443 3444/** 3445 * Get the max value of the result - score part of the statement 3446 * 3447 * @returns {number} 3448 * The max score, or null if not defined 3449 */ 3450H5P.XAPIEvent.prototype.getMaxScore = function () { 3451 return this.getVerifiedStatementValue(['result', 'score', 'max']); 3452}; 3453 3454/** 3455 * Get the raw value of the result - score part of the statement 3456 * 3457 * @returns {number} 3458 * The score, or null if not defined 3459 */ 3460H5P.XAPIEvent.prototype.getScore = function () { 3461 return this.getVerifiedStatementValue(['result', 'score', 'raw']); 3462}; 3463 3464/** 3465 * Get content xAPI ID. 3466 * 3467 * @param {Object} instance 3468 * The H5P instance 3469 */ 3470H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { 3471 var xAPIId; 3472 if (instance.contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId]) { 3473 xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; 3474 if (instance.subContentId) { 3475 xAPIId += '?subContentId=' + instance.subContentId; 3476 } 3477 } 3478 return xAPIId; 3479}; 3480 3481/** 3482 * Check if this event is sent from a child (i.e not from grandchild) 3483 * 3484 * @return {Boolean} 3485 */ 3486H5P.XAPIEvent.prototype.isFromChild = function () { 3487 var parentId = this.getVerifiedStatementValue(['context', 'contextActivities', 'parent', 0, 'id']); 3488 return !parentId || parentId.indexOf('subContentId') === -1; 3489}; 3490 3491/** 3492 * Figure out if a property exists in the statement and return it 3493 * 3494 * @param {string[]} keys 3495 * List describing the property we're looking for. For instance 3496 * ['result', 'score', 'raw'] for result.score.raw 3497 * @returns {*} 3498 * The value of the property if it is set, null otherwise. 3499 */ 3500H5P.XAPIEvent.prototype.getVerifiedStatementValue = function (keys) { 3501 var val = this.data.statement; 3502 for (var i = 0; i < keys.length; i++) { 3503 if (val[keys[i]] === undefined) { 3504 return null; 3505 } 3506 val = val[keys[i]]; 3507 } 3508 return val; 3509}; 3510 3511/** 3512 * List of verbs defined at {@link http://adlnet.gov/expapi/verbs/|ADL xAPI Vocabulary} 3513 * 3514 * @type Array 3515 */ 3516H5P.XAPIEvent.allowedXAPIVerbs = [ 3517 'answered', 3518 'asked', 3519 'attempted', 3520 'attended', 3521 'commented', 3522 'completed', 3523 'exited', 3524 'experienced', 3525 'failed', 3526 'imported', 3527 'initialized', 3528 'interacted', 3529 'launched', 3530 'mastered', 3531 'passed', 3532 'preferred', 3533 'progressed', 3534 'registered', 3535 'responded', 3536 'resumed', 3537 'scored', 3538 'shared', 3539 'suspended', 3540 'terminated', 3541 'voided', 3542 3543 // Custom verbs used for action toolbar below content 3544 'downloaded', 3545 'copied', 3546 'accessed-reuse', 3547 'accessed-embed', 3548 'accessed-copyright' 3549]; 3550; 3551var H5P = window.H5P = window.H5P || {}; 3552 3553/** 3554 * The external event dispatcher. Others, outside of H5P may register and 3555 * listen for H5P Events here. 3556 * 3557 * @type {H5P.EventDispatcher} 3558 */ 3559H5P.externalDispatcher = new H5P.EventDispatcher(); 3560 3561// EventDispatcher extensions 3562 3563/** 3564 * Helper function for triggering xAPI added to the EventDispatcher. 3565 * 3566 * @param {string} verb 3567 * The short id of the verb we want to trigger 3568 * @param {Oject} [extra] 3569 * Extra properties for the xAPI statement 3570 */ 3571H5P.EventDispatcher.prototype.triggerXAPI = function (verb, extra) { 3572 this.trigger(this.createXAPIEventTemplate(verb, extra)); 3573}; 3574 3575/** 3576 * Helper function to create event templates added to the EventDispatcher. 3577 * 3578 * Will in the future be used to add representations of the questions to the 3579 * statements. 3580 * 3581 * @param {string} verb 3582 * Verb id in short form 3583 * @param {Object} [extra] 3584 * Extra values to be added to the statement 3585 * @returns {H5P.XAPIEvent} 3586 * Instance 3587 */ 3588H5P.EventDispatcher.prototype.createXAPIEventTemplate = function (verb, extra) { 3589 var event = new H5P.XAPIEvent(); 3590 3591 event.setActor(); 3592 event.setVerb(verb); 3593 if (extra !== undefined) { 3594 for (var i in extra) { 3595 event.data.statement[i] = extra[i]; 3596 } 3597 } 3598 if (!('object' in event.data.statement)) { 3599 event.setObject(this); 3600 } 3601 if (!('context' in event.data.statement)) { 3602 event.setContext(this); 3603 } 3604 return event; 3605}; 3606 3607/** 3608 * Helper function to create xAPI completed events 3609 * 3610 * DEPRECATED - USE triggerXAPIScored instead 3611 * 3612 * @deprecated 3613 * since 1.5, use triggerXAPIScored instead. 3614 * @param {number} score 3615 * Will be set as the 'raw' value of the score object 3616 * @param {number} maxScore 3617 * will be set as the "max" value of the score object 3618 * @param {boolean} success 3619 * will be set as the "success" value of the result object 3620 */ 3621H5P.EventDispatcher.prototype.triggerXAPICompleted = function (score, maxScore, success) { 3622 this.triggerXAPIScored(score, maxScore, 'completed', true, success); 3623}; 3624 3625/** 3626 * Helper function to create scored xAPI events 3627 * 3628 * @param {number} score 3629 * Will be set as the 'raw' value of the score object 3630 * @param {number} maxScore 3631 * Will be set as the "max" value of the score object 3632 * @param {string} verb 3633 * Short form of adl verb 3634 * @param {boolean} completion
3635 * Is this a statement from a completed activity? 3636 * @param {boolean} success 3637 * Is this a statement from an activity that was done successfully? 3638 */ 3639H5P.EventDispatcher.prototype.triggerXAPIScored = function (score, maxScore, verb, completion, success) { 3640 var event = this.createXAPIEventTemplate(verb); 3641 event.setScoredResult(score, maxScore, this, completion, success); 3642 this.trigger(event); 3643}; 3644 3645H5P.EventDispatcher.prototype.setActivityStarted = function () { 3646 if (this.activityStartTime === undefined) { 3647 // Don't trigger xAPI events in the editor 3648 if (this.contentId !== undefined && 3649 H5PIntegration.contents !== undefined && 3650 H5PIntegration.contents['cid-' + this.contentId] !== undefined) { 3651 this.triggerXAPI('attempted'); 3652 } 3653 this.activityStartTime = Date.now(); 3654 } 3655}; 3656 3657/** 3658 * Internal H5P function listening for xAPI completed events and stores scores 3659 * 3660 * @param {H5P.XAPIEvent} event 3661 */ 3662H5P.xAPICompletedListener = function (event) { 3663 if ((event.getVerb() === 'completed' || event.getVerb() === 'answered') && !event.getVerifiedStatementValue(['context', 'contextActivities', 'parent'])) { 3664 var score = event.getScore(); 3665 var maxScore = event.getMaxScore(); 3666 var contentId = event.getVerifiedStatementValue(['object', 'definition', 'extensions', 'http://h5p.org/x-api/h5p-local-content-id']); 3667 H5P.setFinished(contentId, score, maxScore); 3668 } 3669}; 3670; 3671/** 3672 * H5P.ContentType is a base class for all content types. Used by newRunnable() 3673 * 3674 * Functions here may be overridable by the libraries. In special cases, 3675 * it is also possible to override H5P.ContentType on a global level. 3676 * 3677 * NOTE that this doesn't actually 'extend' the event dispatcher but instead 3678 * it creates a single instance which all content types shares as their base 3679 * prototype. (in some cases this may be the root of strange event behavior) 3680 * 3681 * @class 3682 * @augments H5P.EventDispatcher 3683 */ 3684H5P.ContentType = function (isRootLibrary) { 3685 3686 function ContentType() {} 3687 3688 // Inherit from EventDispatcher. 3689 ContentType.prototype = new H5P.EventDispatcher(); 3690 3691 /** 3692 * Is library standalone or not? Not beeing standalone, means it is 3693 * included in another library 3694 * 3695 * @return {Boolean} 3696 */ 3697 ContentType.prototype.isRoot = function () { 3698 return isRootLibrary; 3699 }; 3700 3701 /** 3702 * Returns the file path of a file in the current library 3703 * @param {string} filePath The path to the file relative to the library folder 3704 * @return {string} The full path to the file 3705 */ 3706 ContentType.prototype.getLibraryFilePath = function (filePath) { 3707 return H5P.getLibraryPath(this.libraryInfo.versionedNameNoSpaces) + '/' + filePath; 3708 }; 3709 3710 return ContentType; 3711}; 3712; 3713/* eslint-disable no-param-reassign */ 3714H5P.ConfirmationDialog = (function (EventDispatcher) { 3715 /** 3716 * Create a confirmation dialog 3717 * 3718 * @param [options] Options for confirmation dialog 3719 * @param [options.instance] Instance that uses confirmation dialog 3720 * @param [options.headerText] Header text 3721 * @param [options.dialogText] Dialog text 3722 * @param [options.cancelText] Cancel dialog button text 3723 * @param [options.confirmText] Confirm dialog button text 3724 * @param [options.hideCancel] Hide cancel button 3725 * @param [options.hideExit] Hide exit button 3726 * @param [options.skipRestoreFocus] Skip restoring focus when hiding the dialog 3727 * @param [options.classes] Extra classes for popup 3728 * @param [options.theme] Whether to use the new theme (true) or the old design (false) 3729 * @constructor 3730 */ 3731 function ConfirmationDialog(options) { 3732 EventDispatcher.call(this); 3733 const self = this; 3734 3735 // Make sure confirmation dialogs have unique id 3736 H5P.ConfirmationDialog.uniqueId += 1; 3737 const { uniqueId } = H5P.ConfirmationDialog; 3738 3739 // Default options 3740 options = options || {}; 3741 options.headerText = options.headerText || H5P.t('confirmDialogHeader'); 3742 options.dialogText = options.dialogText || H5P.t('confirmDialogBody'); 3743 options.cancelText = options.cancelText || H5P.t('cancelLabel'); 3744 options.closeText = options.closeText || H5P.t('close'); 3745 options.confirmText = options.confirmText || H5P.t('confirmLabel'); 3746 3747 /** 3748 * Handle confirming event 3749 * @param {Event} e 3750 */ 3751 function dialogConfirmed(e) { 3752 self.hide(); 3753 self.trigger('confirmed'); 3754 e.preventDefault(); 3755 } 3756 3757 /** 3758 * Handle dialog canceled 3759 * @param {Event} e Event. 3760 * @param {object} [options] Options. 3761 * @param {boolean} [options.wasExplicitChoice] 3762 * True if user chose cancel explicitly, otherwise (close, esc) false. 3763 */ 3764 function dialogCanceled(e, options = {}) { 3765 self.hide(); 3766 self.trigger('canceled', { wasExplicitChoice: options.wasExplicitChoice ?? false }); 3767 e.preventDefault(); 3768 } 3769 3770 /** 3771 * Handle tabbing through buttons with focus trapping. 3772 * @param {KeyboardEvent} event Keyboard event. 3773 */ 3774 function handleTabbing(event) { 3775 if (event.key !== 'Tab') { 3776 return; 3777 } 3778 3779 event.preventDefault(); 3780 3781 const currentIndex = focusableButtons.indexOf(event.target); 3782 const offset = event.shiftKey ? -1 : 1;
3783 const nextIndex = (currentIndex + offset + focusableButtons.length) % focusableButtons.length; 3784 3785 focusableButtons[nextIndex].focus(); 3786 } 3787 3788 // Offset of exit button 3789 const exitButtonOffset = 2 * 16; 3790 const shadowOffset = 8; 3791 3792 // Determine if we are too large for our container and must resize 3793 let resizeIFrame = false; 3794 3795 // Create background 3796 const popupBackground = document.createElement('div'); 3797 popupBackground.classList 3798 .add('h5p-confirmation-dialog-background', 'hidden', 'hiding'); 3799 3800 if (options.theme) { 3801 popupBackground.classList.add('h5p-theme'); 3802 } 3803 3804 if (window.H5PEditor) { 3805 popupBackground.classList.add('h5peditor'); 3806 3807 if (H5PIntegration.theme?.density) { 3808 popupBackground.classList.add(`h5p-${H5PIntegration.theme.density}`); 3809 } 3810 } 3811 3812 // Create outer popup 3813 const popup = document.createElement('div'); 3814 popup.classList.add('h5p-confirmation-dialog-popup', 'hidden'); 3815 if (options.classes) { 3816 options.classes.forEach((popupClass) => { 3817 popup.classList.add(popupClass); 3818 }); 3819 } 3820 3821 popup.setAttribute('role', 'alertdialog'); 3822 popup.setAttribute('aria-modal', 'true'); 3823 popup.setAttribute('aria-labelledby', `h5p-confirmation-dialog-header-text-${uniqueId}`); 3824 popup.setAttribute('aria-describedby', `h5p-confirmation-dialog-text-${uniqueId}`); 3825 popupBackground.appendChild(popup); 3826 popup.addEventListener('keydown', (e) => { 3827 if (e.key === 'Escape') { // Esc key 3828 // Exit dialog 3829 dialogCanceled(e); 3830 } 3831 }); 3832 popup.addEventListener('keydown', (event) => { 3833 handleTabbing(event); 3834 }); 3835 3836 // Popup header 3837 const header = document.createElement('div'); 3838 header.classList.add('h5p-confirmation-dialog-header'); 3839 popup.appendChild(header); 3840 3841 // Header text 3842 const headerText = document.createElement('div'); 3843 headerText.classList.add('h5p-confirmation-dialog-header-text'); 3844 headerText.id = `h5p-confirmation-dialog-dialog-header-text-${uniqueId}`; 3845 headerText.innerHTML = options.headerText; 3846 header.appendChild(headerText); 3847 3848 // Popup body 3849 const body = document.createElement('div'); 3850 body.classList.add('h5p-confirmation-dialog-body'); 3851 popup.appendChild(body); 3852 3853 // Popup text 3854 const text = document.createElement('div'); 3855 text.classList.add('h5p-confirmation-dialog-text'); 3856 text.innerHTML = options.dialogText; 3857 text.id = `h5p-confirmation-dialog-dialog-text-${uniqueId}`; 3858 body.appendChild(text); 3859 3860 // Popup buttons 3861 const buttons = document.createElement('div'); 3862 buttons.classList.add('h5p-confirmation-dialog-buttons'); 3863 body.appendChild(buttons); 3864 3865 // Cancel button 3866 if (!options.hideCancel) { 3867 const cancelButton = document.createElement('button'); 3868 if (!options.theme) { 3869 cancelButton.classList.add('h5p-core-cancel-button'); 3870 } 3871 else { 3872 cancelButton.classList.add('h5p-theme-button', 'h5p-theme-secondary-cta'); 3873 cancelButton.classList.add('h5p-theme-cancel'); 3874 } 3875 const cancelText = document.createElement('span'); 3876 cancelText.textContent = options.cancelText; 3877 cancelButton.appendChild(cancelText); 3878 cancelButton.addEventListener('click', (event) => { 3879 dialogCanceled(event, { wasExplicitChoice: true }); 3880 }); 3881 buttons.appendChild(cancelButton); 3882 } 3883 else { 3884 // Center remaining buttons 3885 buttons.classList.add('center'); 3886 } 3887 3888 // Confirm button 3889 const confirmButton = document.createElement('button'); 3890 if (!options.theme) { 3891 confirmButton.classList.add('h5p-core-button'); 3892 } 3893 // confirmButton.classList.add('h5p-confirmation-dialog-confirm-button'); 3894 confirmButton.setAttribute('aria-label', options.confirmText); 3895 3896 if (options.theme) { 3897 confirmButton.classList.add('h5p-theme-button', 'h5p-theme-primary-cta'); 3898 confirmButton.classList.add('h5p-theme-check'); 3899 } 3900 3901 confirmButton.addEventListener('click', dialogConfirmed); 3902 const confirmText = document.createElement('span'); 3903 confirmText.textContent = options.confirmText; 3904 confirmButton.appendChild(confirmText); 3905 buttons.appendChild(confirmButton); 3906 3907 let focusableButtons = [...buttons.childNodes]; 3908 3909 // Exit button 3910 if (!options.hideExit) { 3911 const exitButton = document.createElement('button'); 3912 exitButton.classList.add('h5p-confirmation-dialog-exit'); 3913 exitButton.setAttribute('aria-label', options.closeText); 3914 exitButton.addEventListener('click', dialogCanceled); 3915 if (options.theme) { 3916 header.appendChild(exitButton); 3917 } 3918 else { 3919 popup.appendChild(exitButton); 3920 } 3921 focusableButtons.push(exitButton); 3922 } 3923 3924 // Wrapper element 3925 let wrapperElement; 3926 3927 // Maintains hidden state of elements 3928 let wrapperSiblingsHidden = []; 3929 let popupSiblingsHidden = []; 3930 3931 // Element with focus before dialog 3932 let previouslyFocused; 3933 3934 /** 3935 * Set parent of confirmation dialog 3936 * @param {HTMLElement} wrapper 3937 * @returns {H5P.ConfirmationDialog} 3938 */ 3939 this.appendTo = function (wrapper) { 3940 wrapperElement = wrapper; 3941 return this; 3942 }; 3943 3944 /** 3945 * Hide siblings of element from assistive technology 3946 * 3947 * @param {HTMLElement} element 3948 * @returns {Array} The previous hidden state of all siblings 3949 */ 3950 const hideSiblings = function (element) { 3951 const hiddenSiblings = []; 3952 const siblings = element.parentNode.children; 3953 let i; 3954 for (i = 0; i < siblings.length; i += 1) { 3955 // Preserve hidden state 3956 hiddenSiblings[i] = !!siblings[i].getAttribute('aria-hidden'); 3957 3958 if (siblings[i] !== element) { 3959 if (siblings[i].getAttribute('aria-live')) { 3960 siblings[i].setAttribute('aria-busy', true); 3961 } 3962 else { 3963 siblings[i].setAttribute('aria-hidden', true); 3964 } 3965 } 3966 } 3967 return hiddenSiblings; 3968 }; 3969 3970 /** 3971 * Restores assistive technology state of element's siblings 3972 * 3973 * @param {HTMLElement} element 3974 * @param {Array} hiddenSiblings Hidden state of all siblings 3975 */ 3976 const restoreSiblings = function (element, hiddenSiblings) { 3977 const siblings = element.parentNode.children; 3978 let i; 3979 for (i = 0; i < siblings.length; i += 1) { 3980 if (siblings[i] !== element && !hiddenSiblings[i]) { 3981 if (siblings[i].getAttribute('aria-live')) { 3982 siblings[i].setAttribute('aria-busy', false); 3983 } 3984 else { 3985 siblings[i].removeAttribute('aria-hidden'); 3986 } 3987 } 3988 } 3989 }; 3990 3991 /** 3992 * Hide siblings in underlay from assistive technologies 3993 */ 3994 const disableUnderlay = function () { 3995 wrapperSiblingsHidden = hideSiblings(wrapperElement);
3996 popupSiblingsHidden = hideSiblings(popupBackground); 3997 }; 3998 3999 /** 4000 * Restore state of underlay for assistive technologies 4001 */ 4002 const restoreUnderlay = function () { 4003 restoreSiblings(wrapperElement, wrapperSiblingsHidden); 4004 restoreSiblings(popupBackground, popupSiblingsHidden); 4005 }; 4006 4007 /** 4008 * Fit popup to container. Makes sure it doesn't overflow. 4009 * @params {number} [offsetTop] Offset of popup 4010 */ 4011 const fitToContainer = function (offsetTop) { 4012 let popupOffsetTop = parseInt(popup.style.top, 10); 4013 if (offsetTop !== undefined) { 4014 popupOffsetTop = offsetTop; 4015 } 4016 4017 if (!popupOffsetTop) { 4018 popupOffsetTop = 0; 4019 } 4020 4021 // Overflows height 4022 if (popupOffsetTop + popup.offsetHeight > wrapperElement.offsetHeight) { 4023 popupOffsetTop = wrapperElement.offsetHeight - popup.offsetHeight - shadowOffset; 4024 } 4025 4026 if (popupOffsetTop - exitButtonOffset <= 0) { 4027 popupOffsetTop = exitButtonOffset + shadowOffset; 4028 4029 // We are too big and must resize 4030 resizeIFrame = true; 4031 } 4032 popup.style.top = `${popupOffsetTop}px`; 4033 }; 4034 4035 /** 4036 * Show confirmation dialog 4037 * @params {number} offsetTop Offset top 4038 * @returns {H5P.ConfirmationDialog} 4039 */ 4040 this.show = function (offsetTop) { 4041 // Capture focused item 4042 previouslyFocused = document.activeElement; 4043 wrapperElement.appendChild(popupBackground); 4044 popupBackground.classList.remove('hidden'); 4045 fitToContainer(offsetTop); 4046 popup.classList.remove('hidden'); 4047 popupBackground.addEventListener('transitionend', () => { 4048 buttons.firstChild.focus(); 4049 }, { once: true }); 4050 popupBackground.classList.remove('hiding'); 4051 disableUnderlay(); 4052 4053 // Resize iFrame if necessary 4054 if (resizeIFrame && options.instance) { 4055 const minHeight = parseInt(popup.offsetHeight, 10) 4056 + exitButtonOffset + (2 * shadowOffset); 4057 self.setViewPortMinimumHeight(minHeight); 4058 options.instance.trigger('resize'); 4059 resizeIFrame = false; 4060 } 4061 4062 // Detect if the user prefers reduced motion, because in that case 4063 // we cannot rely on transitionend triggering and we need to manually 4064 // focus the buttons. It should also be checked for each show, since a 4065 // user may change this setting at any time. 4066 const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); 4067 if (prefersReducedMotion.matches) { 4068 buttons.firstChild.focus(); 4069 } 4070 4071 return this; 4072 }; 4073 4074 /** 4075 * Hide confirmation dialog 4076 * @returns {H5P.ConfirmationDialog} 4077 */ 4078 this.hide = function () { 4079 restoreUnderlay(); 4080 popupBackground.classList.add('hiding'); 4081 popup.classList.add('hidden'); 4082 4083 // Restore focus 4084 if (!options.skipRestoreFocus) { 4085 previouslyFocused.focus(); 4086 } 4087 popupBackground.classList.add('hidden'); 4088 wrapperElement.removeChild(popupBackground); 4089 self.setViewPortMinimumHeight(null); 4090 4091 return this; 4092 }; 4093 4094 /** 4095 * Retrieve element 4096 * 4097 * @return {HTMLElement} 4098 */ 4099 this.getElement = function () { 4100 return popup; 4101 }; 4102 4103 /** 4104 * Get previously focused element 4105 * @return {HTMLElement} 4106 */ 4107 this.getPreviouslyFocused = function () { 4108 return previouslyFocused; 4109 }; 4110 4111 /** 4112 * Sets the minimum height of the view port 4113 * 4114 * @param {number|null} minHeight 4115 */ 4116 this.setViewPortMinimumHeight = function (minHeight) { 4117 const container = document.querySelector('.h5p-container') || document.body; 4118 container.style.minHeight = (typeof minHeight === 'number') ? (`${minHeight}px`) : minHeight; 4119 }; 4120 } 4121 4122 ConfirmationDialog.prototype = Object.create(EventDispatcher.prototype); 4123 ConfirmationDialog.prototype.constructor = ConfirmationDialog; 4124 4125 return ConfirmationDialog; 4126}(H5P.EventDispatcher)); 4127 4128H5P.ConfirmationDialog.uniqueId = -1; 4129; 4130/** 4131 * @class 4132 * @augments H5P.EventDispatcher 4133 * @param {Object} displayOptions 4134 * @param {boolean} displayOptions.export Triggers the display of the 'Download' button 4135 * @param {boolean} displayOptions.copyright Triggers the display of the 'Copyright' button 4136 * @param {boolean} displayOptions.embed Triggers the display of the 'Embed' button 4137 * @param {boolean} displayOptions.icon Triggers the display of the 'H5P icon' link 4138 */
4139H5P.ActionBar = (function ($, EventDispatcher) { 4140 "use strict"; 4141 4142 function ActionBar(displayOptions) { 4143 EventDispatcher.call(this); 4144 4145 /** @alias H5P.ActionBar# */ 4146 var self = this; 4147 4148 var hasActions = false; 4149 4150 // Create action bar 4151 var $actions = H5P.jQuery('<ul class="h5p-actions"></ul>'); 4152 4153 /** 4154 * Helper for creating action bar buttons. 4155 * 4156 * @private 4157 * @param {string} type 4158 * @param {string} customClass Instead of type class 4159 */ 4160 var addActionButton = function (type, customClass) { 4161 /** 4162 * Handles selection of action 4163 */ 4164 var handler = function () { 4165 self.trigger(type); 4166 }; 4167 4168 const $actionList = H5P.jQuery('<li/>', { 4169 'class': 'h5p-button h5p-noselect h5p-' + (customClass ? customClass : type), 4170 appendTo: $actions 4171 }); 4172 4173 const $actionButton = H5P.jQuery('<button/>', { 4174 tabindex: 0, 4175 'aria-label': H5P.t(type + 'Description'), 4176 html: H5P.t(type), 4177 on: { 4178 click: handler, 4179 keypress: function (e) { 4180 if (e.which === 32) { 4181 handler(); 4182 e.preventDefault(); // (since return false will block other inputs) 4183 } 4184 } 4185 }, 4186 appendTo: $actionList 4187 }); 4188 4189 H5P.Tooltip($actionButton.get(0)); 4190 4191 hasActions = true; 4192 }; 4193 4194 // Register action bar buttons 4195 if (displayOptions.export || displayOptions.copy) { 4196 // Add export button 4197 addActionButton('reuse', 'export'); 4198 } 4199 if (displayOptions.copyright) { 4200 addActionButton('copyrights'); 4201 } 4202 if (displayOptions.embed) { 4203 addActionButton('embed'); 4204 } 4205 if (displayOptions.icon) { 4206 // Add about H5P button icon 4207 const $h5pLogo = H5P.jQuery('<li><a class="h5p-link" href="http://h5p.org" target="_blank" aria-label="' + H5P.t('h5pDescription') + '"></a></li>').appendTo($actions); 4208 H5P.Tooltip($h5pLogo.find('.h5p-link').get(0)); 4209 hasActions = true; 4210 } 4211 4212 /** 4213 * Returns a reference to the dom element 4214 * 4215 * @return {H5P.jQuery} 4216 */ 4217 self.getDOMElement = function () { 4218 return $actions; 4219 }; 4220 4221 /** 4222 * Does the actionbar contain actions? 4223 * 4224 * @return {Boolean} 4225 */ 4226 self.hasActions = function () { 4227 return hasActions; 4228 }; 4229 } 4230 4231 ActionBar.prototype = Object.create(EventDispatcher.prototype); 4232 ActionBar.prototype.constructor = ActionBar; 4233 4234 return ActionBar; 4235 4236})(H5P.jQuery, H5P.EventDispatcher); 4237; 4238/** 4239 * Queue requests and handle them at your convenience 4240 * 4241 * @type {RequestQueue} 4242 */ 4243H5P.RequestQueue = (function ($, EventDispatcher) { 4244 /** 4245 * A queue for requests, will be automatically processed when regaining connection 4246 * 4247 * @param {boolean} [options.showToast] Show toast when losing or regaining connection 4248 * @constructor 4249 */ 4250 const RequestQueue = function (options) { 4251 EventDispatcher.call(this); 4252 this.processingQueue = false; 4253 options = options || {}; 4254 4255 this.showToast = options.showToast; 4256 this.itemName = 'requestQueue'; 4257 }; 4258 4259 /** 4260 * Add request to queue. Only supports posts currently. 4261 * 4262 * @param {string} url 4263 * @param {Object} data 4264 * @returns {boolean} 4265 */ 4266 RequestQueue.prototype.add = function (url, data) { 4267 if (!window.localStorage) { 4268 return false; 4269 } 4270 4271 let storedStatements = this.getStoredRequests(); 4272 if (!storedStatements) { 4273 storedStatements = []; 4274 } 4275 4276 storedStatements.push({ 4277 url: url, 4278 data: data, 4279 }); 4280 4281 window.localStorage.setItem(this.itemName, JSON.stringify(storedStatements)); 4282 4283 this.trigger('requestQueued', { 4284 storedStatements: storedStatements, 4285 processingQueue: this.processingQueue, 4286 }); 4287 return true; 4288 }; 4289 4290 /** 4291 * Get stored requests 4292 * 4293 * @returns {boolean|Array} Stored requests 4294 */ 4295 RequestQueue.prototype.getStoredRequests = function () { 4296 if (!window.localStorage) { 4297 return false; 4298 } 4299 4300 const item = window.localStorage.getItem(this.itemName); 4301 if (!item) { 4302 return []; 4303 } 4304 4305 return JSON.parse(item); 4306 }; 4307 4308 /** 4309 * Clear stored requests 4310 * 4311 * @returns {boolean} True if the storage was successfully cleared 4312 */ 4313 RequestQueue.prototype.clearQueue = function () { 4314 if (!window.localStorage) { 4315 return false;
4316 } 4317 4318 window.localStorage.removeItem(this.itemName); 4319 return true; 4320 }; 4321 4322 /** 4323 * Start processing of requests queue 4324 * 4325 * @return {boolean} Returns false if it was not possible to resume processing queue 4326 */ 4327 RequestQueue.prototype.resumeQueue = function () { 4328 // Not supported 4329 if (!H5PIntegration || !window.navigator || !window.localStorage) { 4330 return false; 4331 } 4332 4333 // Already processing 4334 if (this.processingQueue) { 4335 return false; 4336 } 4337 4338 // Attempt to send queued requests 4339 const queue = this.getStoredRequests(); 4340 const queueLength = queue.length; 4341 4342 // Clear storage, failed requests will be re-added 4343 this.clearQueue(); 4344 4345 // No items left in queue 4346 if (!queueLength) { 4347 this.trigger('emptiedQueue', queue); 4348 return true; 4349 } 4350 4351 // Make sure requests are not changed while they're being handled 4352 this.processingQueue = true; 4353 4354 // Process queue in original order 4355 this.processQueue(queue); 4356 return true 4357 }; 4358 4359 /** 4360 * Process first item in the request queue 4361 * 4362 * @param {Array} queue Request queue 4363 */ 4364 RequestQueue.prototype.processQueue = function (queue) { 4365 if (!queue.length) { 4366 return; 4367 } 4368 4369 this.trigger('processingQueue'); 4370 4371 // Make sure the requests are processed in a FIFO order 4372 const request = queue.shift(); 4373 4374 const self = this; 4375 $.post(request.url, request.data) 4376 .fail(self.onQueuedRequestFail.bind(self, request)) 4377 .always(self.onQueuedRequestProcessed.bind(self, queue)) 4378 }; 4379 4380 /** 4381 * Request fail handler 4382 * 4383 * @param {Object} request 4384 */ 4385 RequestQueue.prototype.onQueuedRequestFail = function (request) { 4386 // Queue the failed request again if we're offline 4387 if (!window.navigator.onLine) { 4388 this.add(request.url, request.data); 4389 } 4390 }; 4391 4392 /** 4393 * An item in the queue was processed 4394 * 4395 * @param {Array} queue Queue that was processed 4396 */ 4397 RequestQueue.prototype.onQueuedRequestProcessed = function (queue) { 4398 if (queue.length) { 4399 this.processQueue(queue); 4400 return; 4401 } 4402 4403 // Finished processing this queue 4404 this.processingQueue = false; 4405 4406 // Run empty queue callback with next request queue 4407 const requestQueue = this.getStoredRequests(); 4408 this.trigger('queueEmptied', requestQueue); 4409 }; 4410 4411 /** 4412 * Display toast message on the first content of current page 4413 * 4414 * @param {string} msg Message to display 4415 * @param {boolean} [forceShow] Force override showing the toast 4416 * @param {Object} [configOverride] Override toast message config 4417 */ 4418 RequestQueue.prototype.displayToastMessage = function (msg, forceShow, configOverride) { 4419 if (!this.showToast && !forceShow) { 4420 return; 4421 } 4422 4423 const config = H5P.jQuery.extend(true, {}, { 4424 position: { 4425 horizontal : 'centered', 4426 vertical: 'centered', 4427 noOverflowX: true, 4428 } 4429 }, configOverride); 4430 4431 H5P.attachToastTo(H5P.jQuery('.h5p-content:first')[0], msg, config); 4432 }; 4433 4434 return RequestQueue; 4435})(H5P.jQuery, H5P.EventDispatcher); 4436 4437/** 4438 * Request queue for retrying failing requests, will automatically retry them when you come online 4439 * 4440 * @type {offlineRequestQueue} 4441 */ 4442H5P.OfflineRequestQueue = (function (RequestQueue, Dialog) { 4443 4444 /** 4445 * Constructor 4446 * 4447 * @param {Object} [options] Options for offline request queue 4448 * @param {Object} [options.instance] The H5P instance which UI components are placed within 4449 */ 4450 const offlineRequestQueue = function (options) { 4451 const requestQueue = new RequestQueue(); 4452 4453 // We could handle requests from previous pages here, but instead we throw them away 4454 requestQueue.clearQueue(); 4455 4456 let startTime = null; 4457 const retryIntervals = [10, 20, 40, 60, 120, 300, 600]; 4458 let intervalIndex = -1; 4459 let currentInterval = null; 4460 let isAttached = false; 4461 let isShowing = false; 4462 let isLoading = false; 4463 const instance = options.instance; 4464 4465 const offlineDialog = new Dialog({ 4466 headerText: H5P.t('offlineDialogHeader'), 4467 dialogText: H5P.t('offlineDialogBody'), 4468 confirmText: H5P.t('offlineDialogRetryButtonLabel'), 4469 hideCancel: true, 4470 hideExit: true, 4471 classes: ['offline'], 4472 instance: instance, 4473 skipRestoreFocus: true, 4474 }); 4475 4476 const dialog = offlineDialog.getElement(); 4477 4478 // Add retry text to body 4479 const countDownText = document.createElement('div'); 4480 countDownText.classList.add('count-down'); 4481 countDownText.innerHTML = H5P.t('offlineDialogRetryMessage') 4482 .replace(':num', '<span class="count-down-num">0</span>'); 4483 4484 dialog.querySelector('.h5p-confirmation-dialog-text').appendChild(countDownText); 4485 const countDownNum = countDownText.querySelector('.count-down-num'); 4486 4487 // Create throbber 4488 const throbberWrapper = document.createElement('div'); 4489 throbberWrapper.classList.add('throbber-wrapper'); 4490 const throbber = document.createElement('div'); 4491 throbber.classList.add('sending-requests-throbber'); 4492 throbberWrapper.appendChild(throbber); 4493 4494 requestQueue.on('requestQueued', function (e) { 4495 // Already processing queue, wait until queue has finished processing before showing dialog 4496 if (e.data && e.data.processingQueue) { 4497 return; 4498 } 4499 4500 if (!isAttached) { 4501 const rootContent = document.body.querySelector('.h5p-content'); 4502 if (!rootContent) { 4503 return; 4504 } 4505 offlineDialog.appendTo(rootContent); 4506 rootContent.appendChild(throbberWrapper); 4507 isAttached = true; 4508 } 4509 4510 startCountDown(); 4511 }.bind(this)); 4512 4513 requestQueue.on('queueEmptied', function (e) { 4514 if (e.data && e.data.length) { 4515 // New requests were added while processing queue or requests failed again. Re-queue requests. 4516 startCountDown(true); 4517 return; 4518 } 4519 4520 // Successfully emptied queue 4521 clearInterval(currentInterval); 4522 toggleThrobber(false); 4523 intervalIndex = -1; 4524 if (isShowing) { 4525 offlineDialog.hide(); 4526 isShowing = false;
4527 } 4528 4529 requestQueue.displayToastMessage( 4530 H5P.t('offlineSuccessfulSubmit'), 4531 true, 4532 { 4533 position: { 4534 vertical: 'top', 4535 offsetVertical: '100', 4536 } 4537 } 4538 ); 4539 4540 }.bind(this)); 4541 4542 offlineDialog.on('confirmed', function () { 4543 // Show dialog on next render in case it is being hidden by the 'confirm' button 4544 isShowing = false; 4545 setTimeout(function () { 4546 retryRequests(); 4547 }, 100); 4548 }.bind(this)); 4549 4550 // Initialize listener for when requests are added to queue 4551 window.addEventListener('online', function () { 4552 retryRequests(); 4553 }.bind(this)); 4554 4555 // Listen for queued requests outside the iframe 4556 window.addEventListener('message', function (event) { 4557 const isValidQueueEvent = window.parent === event.source 4558 && event.data.context === 'h5p' 4559 && event.data.action === 'queueRequest'; 4560 4561 if (!isValidQueueEvent) { 4562 return; 4563 } 4564 4565 this.add(event.data.url, event.data.data); 4566 }.bind(this)); 4567 4568 /** 4569 * Toggle throbber visibility 4570 * 4571 * @param {boolean} [forceShow] Will force throbber visibility if set 4572 */ 4573 const toggleThrobber = function (forceShow) { 4574 isLoading = !isLoading; 4575 if (forceShow !== undefined) { 4576 isLoading = forceShow; 4577 } 4578 4579 if (isLoading && isShowing) { 4580 offlineDialog.hide(); 4581 isShowing = false; 4582 } 4583 4584 if (isLoading) { 4585 throbberWrapper.classList.add('show'); 4586 } 4587 else { 4588 throbberWrapper.classList.remove('show'); 4589 } 4590 }; 4591 /** 4592 * Retries the failed requests 4593 */ 4594 const retryRequests = function () { 4595 clearInterval(currentInterval); 4596 toggleThrobber(true); 4597 requestQueue.resumeQueue(); 4598 }; 4599 4600 /** 4601 * Increments retry interval 4602 */ 4603 const incrementRetryInterval = function () { 4604 intervalIndex += 1; 4605 if (intervalIndex >= retryIntervals.length) { 4606 intervalIndex = retryIntervals.length - 1; 4607 } 4608 }; 4609 4610 /** 4611 * Starts counting down to retrying queued requests. 4612 * 4613 * @param forceDelayedShow 4614 */ 4615 const startCountDown = function (forceDelayedShow) { 4616 // Already showing, wait for retry 4617 if (isShowing) { 4618 return; 4619 } 4620 4621 toggleThrobber(false); 4622 if (!isShowing) { 4623 if (forceDelayedShow) { 4624 // Must force delayed show since dialog may be hiding, and confirmation dialog does not 4625 // support this. 4626 setTimeout(function () { 4627 offlineDialog.show(0); 4628 }, 100); 4629 } 4630 else { 4631 offlineDialog.show(0); 4632 } 4633 } 4634 isShowing = true; 4635 startTime = new Date().getTime(); 4636 incrementRetryInterval(); 4637 clearInterval(currentInterval); 4638 currentInterval = setInterval(updateCountDown, 100); 4639 }; 4640 4641 /** 4642 * Updates the count down timer. Retries requests when time expires. 4643 */ 4644 const updateCountDown = function () { 4645 const time = new Date().getTime(); 4646 const timeElapsed = Math.floor((time - startTime) / 1000); 4647 const timeLeft = retryIntervals[intervalIndex] - timeElapsed; 4648 countDownNum.textContent = timeLeft.toString(); 4649 4650 // Retry interval reached, retry requests 4651 if (timeLeft <= 0) { 4652 retryRequests(); 4653 } 4654 }; 4655 4656 /** 4657 * Add request to offline request queue. Only supports posts for now. 4658 * 4659 * @param {string} url The request url 4660 * @param {Object} data The request data 4661 */ 4662 this.add = function (url, data) { 4663 // Only queue request if it failed because we are offline 4664 if (window.navigator.onLine) { 4665 return false; 4666 } 4667 4668 requestQueue.add(url, data); 4669 }; 4670 }; 4671 4672 return offlineRequestQueue; 4673})(H5P.RequestQueue, H5P.ConfirmationDialog); 4674; 4675/* global H5P */ 4676H5P.Tooltip = (function () { 4677 // Position (allowed and default) 4678 const Position = { 4679 allowed: ['top', 'bottom', 'left', 'right'], 4680 default: 'top', 4681 }; 4682 4683 /** {number} DELAY_SHOW_MS Delay before tooltip is shown */ 4684 const DELAY_SHOW_MS = 500; 4685 4686 /** {number} DELAY_HIDE_MS Delay before tooltip is hidden */ 4687 const DELAY_HIDE_MS = 500; 4688 4689 /** 4690 * Strips html tags and converts special characters. 4691 * Example: "<div>Me & you</div>" is converted to "Me & you". 4692 * 4693 * @param {String} text The text to be parsed 4694 * @returns {String} The parsed text 4695 */ 4696 function parseString(text) { 4697 if (text === null || text === undefined) { 4698 return ''; 4699 } 4700 const div = document.createElement('div'); 4701 div.innerHTML = text; 4702 return div.textContent; 4703 } 4704 4705 /** 4706 * Keep track of whether the user is using their mouse or keyboard to 4707 * navigate. Will determine whether tooltip should be shown on focus. 4708 */ 4709 let usingMouse; 4710 4711 function debounce(callback, delay) { 4712 let timeout = null; 4713 4714 return function (...args) { 4715 clearTimeout(timeout); 4716 timeout = setTimeout(() => { 4717 callback(...args); 4718 }, delay); 4719 }; 4720 } 4721 4722 // The mousemove listener is debounced for performance reasons 4723 document.addEventListener('mousemove', debounce(() => usingMouse = true, 100)); 4724 document.addEventListener('mousedown', () => usingMouse = true); 4725 document.addEventListener('keydown', () => usingMouse = false); 4726 4727 /** 4728 * Create an accessible tooltip 4729 * 4730 * @param {HTMLElement} triggeringElement The element that should trigger the tooltip 4731 * @param {Object} options Options for tooltip 4732 * @param {String} options.text The text to be displayed in the tooltip 4733 * If not set, will attempt to set text = options.tooltipSource of triggeringElement 4734 * @param {String[]} options.classes Extra css classes for the tooltip 4735 * @param {Boolean} options.ariaHidden Whether the hover should be read by screen readers or not (default: true) 4736 * @param {String} options.position Where the tooltip should appear in relation to the 4737 * triggeringElement. Accepted positions are "top" (default), "left", "right" and "bottom" 4738 * @param {String} options.tooltipSource 4739 * 4740 * @returns {object} returns all the public functions 4741 * 4742 * @constructor 4743 */ 4744 4745 function Tooltip(triggeringElement, options) { 4746 // Make sure tooltips have unique id 4747 H5P.Tooltip.uniqueId += 1; 4748 const tooltipId = `h5p-tooltip-${H5P.Tooltip.uniqueId}`; 4749 4750 // Default options 4751 options = options || {}; 4752 options.classes = options.classes || []; 4753 options.ariaHidden = options.ariaHidden || true; 4754 options.tooltipSource = options.tooltipSource || 'aria-label'; 4755 options.position = (options.position && Position.allowed.includes(options.position)) 4756 ? options.position 4757 : Position.default; 4758
4759 // Add our internal classes 4760 options.classes.push('h5p-tooltip'); 4761 if (options.position === 'left' || options.position === 'right') { 4762 options.classes.push('h5p-tooltip-narrow'); 4763 } 4764 4765 // Initiate state 4766 let hover = false; 4767 let focus = false; 4768 4769 // Function used by the escape listener 4770 const hideOnEscape = function (event) { 4771 if (event.key === 'Escape') { 4772 tooltip.classList.remove('h5p-tooltip-visible'); 4773 } 4774 }; 4775 4776 // Create element 4777 const tooltip = document.createElement('div'); 4778 tooltip.id = tooltipId; 4779 tooltip.role = 'tooltip'; 4780 tooltip.textContent = parseString(options.text || triggeringElement.getAttribute(options.tooltipSource) || ''); 4781 tooltip.setAttribute('aria-hidden', options.ariaHidden); 4782 tooltip.classList.add(...options.classes); 4783 4784 document.body.appendChild(tooltip); 4785 4786 // Aria-describedby will override aria-hidden 4787 if (!options.ariaHidden) { 4788 triggeringElement.setAttribute('aria-describedby', tooltipId); 4789 } 4790 4791 // Use a mutation observer to listen for options.tooltipSource being 4792 // changed for the triggering element. If so, update the tooltip. 4793 // Mutation observer will be used even if the original elements 4794 // doesn't have any options.tooltipSource. 4795 this.observer = new MutationObserver((mutations) => { 4796 const updatedText = mutations[0].target.getAttribute(options.tooltipSource); 4797 4798 if (tooltip.parentNode === null) { 4799 triggeringElement.appendChild(tooltip); 4800 } 4801 4802 tooltip.textContent = parseString(options.text || updatedText); 4803 4804 if (tooltip.textContent.trim().length === 0 && tooltip.classList.contains('h5p-tooltip-visible')) { 4805 tooltip.classList.remove('h5p-tooltip-visible'); 4806 } 4807 }); 4808 this.observer.observe(triggeringElement, { 4809 attributes: true, 4810 attributeFilter: [options.tooltipSource, 'class'], 4811 }); 4812 4813 // A reference to the H5P container (if any). If null, it means 4814 // this tooltip is not whithin an H5P. 4815 let h5pContainer; 4816 4817 // Timer responsible for displaying the tooltip x ms after it has been 4818 // triggered (either by mouseenter or focusin) 4819 let showTooltipTimer; 4820 4821 // Timer responsible for hiding the tooltip x ms after it has been untriggered 4822 let hideTooltipTimer; 4823 4824 // This timer makes sure the tooltip is not hidden when the mouse 4825 // moves from the trigger to the tooltip. 4826 let triggerMouseLeaveTimer; 4827 4828 /** 4829 * Makes the tooltip visible and activates it's functionality 4830 * 4831 * @param {UIEvent} event The triggering event 4832 */ 4833 const showTooltip = function (event, wait = true) { 4834 if (!event.target || event.target.disabled || event.target.getAttribute('aria-disabled') === 'true') { 4835 return; 4836 } 4837 4838 clearTimeout(hideTooltipTimer); // Prevent from hiding while supposed to show 4839 4840 if (wait === true) { 4841 // We don't want to show the tooltip right away. 4842 // Adding a 300 ms waiting period here. 4843 clearTimeout(showTooltipTimer); 4844 showTooltipTimer = setTimeout(() => { 4845 showTooltip(event, false); 4846 }, DELAY_SHOW_MS); 4847 return; 4848 } 4849 4850 // Don't show tooltip if it is empty 4851 if (tooltip.textContent.trim().length === 0) { 4852 return; 4853 } 4854 4855 if (event.type === 'mouseenter') { 4856 hover = true; 4857 } 4858 else { 4859 focus = true; 4860 } 4861 4862 // Reset placement 4863 tooltip.style.left = ''; 4864 tooltip.style.top = ''; 4865 4866 tooltip.classList.add('h5p-tooltip-visible'); 4867 4868 // Add listener to iframe body, as esc keypress would not be detected otherwise 4869 document.body.addEventListener('keydown', hideOnEscape, true); 4870 4871 // The section below makes sure the tooltip is completely visible 4872 4873 // H5P.Tooltip can be used both from within an H5P and elsewhere. 4874 // The below code is for figuring out the containing element. 4875 // h5pContainer has to be looked up the first time we show the tooltip, 4876 // since it might not be added to the DOM when H5P.Tooltip is invoked. 4877 if (h5pContainer === undefined) { 4878 // After the below, h5pContainer is either null or a reference to the 4879 // DOM element 4880 h5pContainer = triggeringElement.closest('.h5p-container'); 4881 } 4882 const rootRect = h5pContainer ? h5pContainer.getBoundingClientRect() : document.documentElement.getBoundingClientRect(); 4883 const triggerRect = triggeringElement.getBoundingClientRect(); 4884 let tooltipRect = tooltip.getBoundingClientRect(); 4885 4886 if (options.position === 'top') { 4887 // Places it centered above 4888 tooltip.style.left = `${triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2)}px`; 4889 tooltip.style.top = `${triggerRect.top - tooltipRect.height}px`; 4890 } 4891 else if (options.position === 'bottom') { 4892 // Places it centered below 4893 tooltip.style.left = `${triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2)}px`; 4894 tooltip.style.top = `${triggerRect.bottom}px`; 4895 } 4896 else if (options.position === 'left') { 4897 tooltip.style.left = `${triggerRect.left - tooltipRect.width}px`; 4898 tooltip.style.top = `${triggerRect.top + (triggerRect.height - tooltipRect.height) / 2}px`; 4899 // We trust this option makes the tooltip being shown 4900 return; 4901 } 4902 else if (options.position === 'right') { 4903 tooltip.style.left = `${triggerRect.right}px`; 4904 tooltip.style.top = `${triggerRect.top + (triggerRect.height - tooltipRect.height) / 2}px`; 4905 // We trust this option makes the tooltip being shown 4906 return; 4907 } 4908 4909 tooltipRect = tooltip.getBoundingClientRect(); 4910 const isVisible = tooltipRect.left >= 0 4911 && tooltipRect.top >= 0 4912 && tooltipRect.right <= rootRect.width 4913 && tooltipRect.bottom <= rootRect.height; 4914 4915 if (!isVisible) {
4916 // The tooltip placement needs to be adjusted. This logic will move the 4917 // tooltip either left or right if it's placed outside the root element 4918 tooltipRect = tooltip.getBoundingClientRect(); 4919 if (tooltipRect.left < 0) { 4920 tooltip.style.left = 0; 4921 } 4922 else if (tooltipRect.right > rootRect.width) { 4923 tooltip.style.left = ''; 4924 tooltip.style.right = 0; 4925 } 4926 } 4927 }; 4928 4929 /** 4930 * Hides the tooltip and removes listeners 4931 * 4932 * @param {UIEvent} event The triggering event 4933 */ 4934 const hideTooltip = function (event) { 4935 let hide = false; 4936 let wait = false; 4937 4938 if (event.type === 'click') { 4939 hide = true; 4940 } 4941 else { 4942 if (event.type === 'mouseleave') { 4943 wait = true; // Tooltip should not disappear right away 4944 hover = false; 4945 } 4946 else { 4947 focus = false; 4948 } 4949 4950 hide = (!hover && !focus); 4951 } 4952 4953 // Only hide tooltip if neither hovered nor focused 4954 if (hide) { 4955 clearTimeout(showTooltipTimer); // Prevent from showing while supposed to hide 4956 4957 const cleanupTooltip = () => { 4958 tooltip.classList.remove('h5p-tooltip-visible'); 4959 document.body.removeEventListener('keydown', hideOnEscape, true); // Remove iframe body listener 4960 }; 4961 4962 if (wait) { 4963 clearTimeout(hideTooltipTimer); 4964 hideTooltipTimer = setTimeout(() => { 4965 cleanupTooltip(); 4966 }, DELAY_HIDE_MS); 4967 } 4968 else { 4969 cleanupTooltip(); 4970 } 4971 } 4972 }; 4973 4974 // Add event listeners to triggeringElement 4975 triggeringElement.addEventListener('mouseenter', showTooltip); 4976 triggeringElement.addEventListener('mouseleave', (event) => { 4977 triggerMouseLeaveTimer = setTimeout(() => { 4978 hideTooltip(event); 4979 }, 1); 4980 }); 4981 triggeringElement.addEventListener('focusin', (event) => { 4982 if (!usingMouse) { 4983 showTooltip(event); 4984 } 4985 }); 4986 triggeringElement.addEventListener('focusout', hideTooltip); 4987 triggeringElement.addEventListener('click', hideTooltip); 4988 tooltip.addEventListener('mouseenter', () => { 4989 clearTimeout(triggerMouseLeaveTimer); 4990 }); 4991 tooltip.addEventListener('mouseleave', hideTooltip); 4992 4993 tooltip.addEventListener('click', (event) => { 4994 // Prevent clicks on the tooltip from triggering click 4995 // listeners on the triggering element 4996 event.stopPropagation(); 4997 event.preventDefault(); 4998 4999 // Hide the tooltip when it is clicked 5000 hideTooltip(event); 5001 }); 5002 5003 /** 5004 * Change the text displayed by the tooltip 5005 * 5006 * @param {String} text The new text to be displayed 5007 * Set to null to use options.tooltipSource of triggeringElement instead 5008 */ 5009 this.setText = function (text) { 5010 options.text = text; 5011 tooltip.textContent = parseString(options.text || triggeringElement.getAttribute(options.tooltipSource) || ''); 5012 }; 5013 5014 /** 5015 * Hide the tooltip 5016 */ 5017 this.hide = function () { 5018 hover = focus = false; 5019 tooltip.classList.remove('h5p-tooltip-visible'); 5020 }; 5021 5022 /** 5023 * Retrieve tooltip 5024 * 5025 * @return {HTMLElement} 5026 */ 5027 this.getElement = function () { 5028 return tooltip; 5029 }; 5030 5031 /** 5032 * Remove tooltip 5033 */ 5034 this.remove = function () { 5035 this.observer?.disconnect(); 5036 tooltip.remove(); 5037 }; 5038 5039 return { 5040 setText: this.setText, 5041 hide: this.hide, 5042 getElement: this.getElement, 5043 remove: this.remove, 5044 observer: this.observer, 5045 }; 5046 } 5047 5048 return Tooltip; 5049}()); 5050 5051H5P.Tooltip.uniqueId = -1; 5052;
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.