var H5P = H5P || {}; /** * Constructor. * * @param {object} params Options for this library. */ H5P.Text = function (params) { this.text = params.text === undefined ? 'New text' : params.text; }; /** * Wipe out the content of the wrapper and put our HTML in it. * * @param {jQuery} $wrapper */ H5P.Text.prototype.attach = function ($wrapper) { $wrapper.addClass('h5p-text').html(this.text); }; ; var H5P = H5P || {}; /** * Transition contains helper function relevant for transitioning */ H5P.Transition = (function ($) { /** * @class * @namespace H5P */ Transition = {}; /** * @private */ Transition.transitionEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'transition': 'transitionend', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd' }; /** * @private */ Transition.cache = []; /** * Get the vendor property name for an event * * @function H5P.Transition.getVendorPropertyName * @static * @private * @param {string} prop Generic property name * @return {string} Vendor specific property name */ Transition.getVendorPropertyName = function (prop) { if (Transition.cache[prop] !== undefined) { return Transition.cache[prop]; } var div = document.createElement('div'); // Handle unprefixed versions (FF16+, for example) if (prop in div.style) { Transition.cache[prop] = prop; } else { var prefixes = ['Moz', 'Webkit', 'O', 'ms']; var prop_ = prop.charAt(0).toUpperCase() + prop.substr(1); if (prop in div.style) { Transition.cache[prop] = prop; } else { for (var i = 0; i < prefixes.length; ++i) { var vendorProp = prefixes[i] + prop_; if (vendorProp in div.style) { Transition.cache[prop] = vendorProp; break; } } } } return Transition.cache[prop]; }; /** * Get the name of the transition end event * * @static * @private * @return {string} description */ Transition.getTransitionEndEventName = function () { return Transition.transitionEndEventNames[Transition.getVendorPropertyName('transition')] || undefined; }; /** * Helper function for listening on transition end events * * @function H5P.Transition.onTransitionEnd * @static * @param {domElement} $element The element which is transitioned * @param {function} callback The callback to be invoked when transition is finished * @param {number} timeout Timeout in milliseconds. Fallback if transition event is never fired */ Transition.onTransitionEnd = function ($element, callback, timeout) { // Fallback on 1 second if transition event is not supported/triggered timeout = timeout || 1000; Transition.transitionEndEventName = Transition.transitionEndEventName || Transition.getTransitionEndEventName(); var callbackCalled = false; var doCallback = function () { if (callbackCalled) { return; } $element.off(Transition.transitionEndEventName, callback); callbackCalled = true; clearTimeout(timer); callback(); }; var timer = setTimeout(function () { doCallback(); }, timeout); $element.on(Transition.transitionEndEventName, function () { doCallback(); }); }; /** * Wait for a transition - when finished, invokes next in line * * @private * * @param {Object[]} transitions Array of transitions * @param {H5P.jQuery} transitions[].$element Dom element transition is performed on * @param {number=} transitions[].timeout Timeout fallback if transition end never is triggered * @param {bool=} transitions[].break If true, sequence breaks after this transition * @param {number} index The index for current transition */ var runSequence = function (transitions, index) { if (index >= transitions.length) { return; } var transition = transitions[index]; H5P.Transition.onTransitionEnd(transition.$element, function () { if (transition.end) { transition.end(); } if (transition.break !== true) { runSequence(transitions, index+1); } }, transition.timeout || undefined); }; /** * Run a sequence of transitions * * @function H5P.Transition.sequence * @static * @param {Object[]} transitions Array of transitions * @param {H5P.jQuery} transitions[].$element Dom element transition is performed on * @param {number=} transitions[].timeout Timeout fallback if transition end never is triggered * @param {bool=} transitions[].break If true, sequence breaks after this transition */ Transition.sequence = function (transitions) { runSequence(transitions, 0); }; return Transition; })(H5P.jQuery); ; var H5P = H5P || {}; /** * Class responsible for creating a help text dialog */ H5P.JoubelHelpTextDialog = (function ($) { var numInstances = 0; /** * Display a pop-up containing a message. * * @param {H5P.jQuery} $container The container which message dialog will be appended to * @param {string} message The message * @param {string} closeButtonTitle The title for the close button * @return {H5P.jQuery} */ function JoubelHelpTextDialog(header, message, closeButtonTitle) { H5P.EventDispatcher.call(this); var self = this; numInstances++; var headerId = 'joubel-help-text-header-' + numInstances; var helpTextId = 'joubel-help-text-body-' + numInstances; var $helpTextDialogBox = $('
', { 'class': 'joubel-help-text-dialog-box', 'role': 'dialog', 'aria-labelledby': headerId, 'aria-describedby': helpTextId }); $('
', { 'class': 'joubel-help-text-dialog-background' }).appendTo($helpTextDialogBox); var $helpTextDialogContainer = $('
', { 'class': 'joubel-help-text-dialog-container' }).appendTo($helpTextDialogBox); $('
', { 'class': 'joubel-help-text-header', 'id': headerId, 'role': 'header', 'html': header }).appendTo($helpTextDialogContainer); $('
', { 'class': 'joubel-help-text-body', 'id': helpTextId, 'html': message, 'role': 'document', 'tabindex': 0 }).appendTo($helpTextDialogContainer); var handleClose = function () { $helpTextDialogBox.remove(); self.trigger('closed'); }; var $closeButton = $('
', { 'class': 'joubel-help-text-remove', 'role': 'button', 'title': closeButtonTitle, 'tabindex': 1, 'click': handleClose, 'keydown': function (event) { // 32 - space, 13 - enter if ([32, 13].indexOf(event.which) !== -1) { event.preventDefault(); handleClose(); } } }).appendTo($helpTextDialogContainer); /** * Get the DOM element * @return {HTMLElement} */ self.getElement = function () { return $helpTextDialogBox; }; self.focus = function () { $closeButton.focus(); }; } JoubelHelpTextDialog.prototype = Object.create(H5P.EventDispatcher.prototype); JoubelHelpTextDialog.prototype.constructor = JoubelHelpTextDialog; return JoubelHelpTextDialog; }(H5P.jQuery)); ; var H5P = H5P || {}; /** * Class responsible for creating auto-disappearing dialogs */ H5P.JoubelMessageDialog = (function ($) { /** * Display a pop-up containing a message. * * @param {H5P.jQuery} $container The container which message dialog will be appended to * @param {string} message The message * @return {H5P.jQuery} */ function JoubelMessageDialog ($container, message) { var timeout; var removeDialog = function () { $warning.remove(); clearTimeout(timeout); $container.off('click.messageDialog'); }; // Create warning popup: var $warning = $('
', { 'class': 'joubel-message-dialog', text: message }).appendTo($container); // Remove after 3 seconds or if user clicks anywhere in $container: timeout = setTimeout(removeDialog, 3000); $container.on('click.messageDialog', removeDialog); return $warning; } return JoubelMessageDialog; })(H5P.jQuery); ; var H5P = H5P || {}; /** * Class responsible for creating a circular progress bar */ H5P.JoubelProgressCircle = (function ($) { /** * Constructor for the Progress Circle * * @param {Number} number The amount of progress to display * @param {string} progressColor Color for the progress meter * @param {string} backgroundColor Color behind the progress meter */ function ProgressCircle(number, progressColor, fillColor, backgroundColor) { progressColor = progressColor || '#1a73d9'; fillColor = fillColor || '#f0f0f0'; backgroundColor = backgroundColor || '#ffffff'; var progressColorRGB = this.hexToRgb(progressColor); //Verify number try { number = Number(number); if (number === '') { throw 'is empty'; } if (isNaN(number)) { throw 'is not a number'; } } catch (e) { number = 'err'; } //Draw circle if (number > 100) { number = 100; } // We can not use rgba, since they will stack on top of each other. // Instead we create the equivalent of the rgba color // and applies this to the activeborder and background color. var progressColorString = 'rgb(' + parseInt(progressColorRGB.r, 10) + ',' + parseInt(progressColorRGB.g, 10) + ',' + parseInt(progressColorRGB.b, 10) + ')'; // Circle wrapper var $wrapper = $('
', { 'class': "joubel-progress-circle-wrapper" }); //Active border indicates progress var $activeBorder = $('
', { 'class': "joubel-progress-circle-active-border" }).appendTo($wrapper); //Background circle var $backgroundCircle = $('
', { 'class': "joubel-progress-circle-circle" }).appendTo($activeBorder); //Progress text/number $('', { 'text': number + '%', 'class': "joubel-progress-circle-percentage" }).appendTo($backgroundCircle); var deg = number * 3.6; if (deg <= 180) { $activeBorder.css('background-image', 'linear-gradient(' + (90 + deg) + 'deg, transparent 50%, ' + fillColor + ' 50%),' + 'linear-gradient(90deg, ' + fillColor + ' 50%, transparent 50%)') .css('border', '2px solid' + backgroundColor) .css('background-color', progressColorString); } else { $activeBorder.css('background-image', 'linear-gradient(' + (deg - 90) + 'deg, transparent 50%, ' + progressColorString + ' 50%),' + 'linear-gradient(90deg, ' + fillColor + ' 50%, transparent 50%)') .css('border', '2px solid' + backgroundColor) .css('background-color', progressColorString); } this.$activeBorder = $activeBorder; this.$backgroundCircle = $backgroundCircle; this.$wrapper = $wrapper; this.initResizeFunctionality(); return $wrapper; } /** * Initializes resize functionality for the progress circle */ ProgressCircle.prototype.initResizeFunctionality = function () { var self = this; $(window).resize(function () { // Queue resize setTimeout(function () { self.resize(); }); }); // First resize setTimeout(function () { self.resize(); }, 0); }; /** * Resize function makes progress circle grow or shrink relative to parent container */ ProgressCircle.prototype.resize = function () { var $parent = this.$wrapper.parent(); if ($parent !== undefined && $parent) { // Measurements var fontSize = parseInt($parent.css('font-size'), 10); // Static sizes var fontSizeMultiplum = 3.75; var progressCircleWidthPx = parseInt((fontSize / 4.5), 10) % 2 === 0 ? parseInt((fontSize / 4.5), 10) + 4 : parseInt((fontSize / 4.5), 10) + 5; var progressCircleOffset = progressCircleWidthPx / 2; var width = fontSize * fontSizeMultiplum; var height = fontSize * fontSizeMultiplum; this.$activeBorder.css({ 'width': width, 'height': height }); this.$backgroundCircle.css({ 'width': width - progressCircleWidthPx, 'height': height - progressCircleWidthPx, 'top': progressCircleOffset, 'left': progressCircleOffset }); } }; /** * Hex to RGB conversion * @param hex * @returns {{r: Number, g: Number, b: Number}} */ ProgressCircle.prototype.hexToRgb = function (hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; }; return ProgressCircle; }(H5P.jQuery)); ; var H5P = H5P || {}; H5P.SimpleRoundedButton = (function ($) { /** * Creates a new tip */ function SimpleRoundedButton(text) { var $simpleRoundedButton = $('
', { 'class': 'joubel-simple-rounded-button', 'title': text, 'role': 'button', 'tabindex': '0' }).keydown(function (e) { // 32 - space, 13 - enter if ([32, 13].indexOf(e.which) !== -1) { $(this).click(); e.preventDefault(); } }); $('', { 'class': 'joubel-simple-rounded-button-text', 'html': text }).appendTo($simpleRoundedButton); return $simpleRoundedButton; } return SimpleRoundedButton; }(H5P.jQuery)); ; var H5P = H5P || {}; /** * Class responsible for creating speech bubbles */ H5P.JoubelSpeechBubble = (function ($) { var $currentSpeechBubble; var $currentContainer; var $tail; var $innerTail; var removeSpeechBubbleTimeout; var currentMaxWidth; var DEFAULT_MAX_WIDTH = 400; var iDevice = navigator.userAgent.match(/iPod|iPhone|iPad/g) ? true : false; /** * Creates a new speech bubble * * @param {H5P.jQuery} $container The speaking object * @param {string} text The text to display * @param {number} maxWidth The maximum width of the bubble * @return {H5P.JoubelSpeechBubble} */ function JoubelSpeechBubble($container, text, maxWidth) { maxWidth = maxWidth || DEFAULT_MAX_WIDTH; currentMaxWidth = maxWidth; $currentContainer = $container; this.isCurrent = function ($tip) { return $tip.is($currentContainer); }; this.remove = function () { remove(); }; var fadeOutSpeechBubble = function ($speechBubble) { if (!$speechBubble) { return; } // Stop removing bubble clearTimeout(removeSpeechBubbleTimeout); $speechBubble.removeClass('show'); setTimeout(function () { if ($speechBubble) { $speechBubble.remove(); $speechBubble = undefined; } }, 500); }; if ($currentSpeechBubble !== undefined) { remove(); } var $h5pContainer = getH5PContainer($container); // Make sure we fade out old speech bubble fadeOutSpeechBubble($currentSpeechBubble); // Create bubble $tail = $('
'); $innerTail = $('
'); var $innerBubble = $( '
' + '
' + text + '
' + '
' ).prepend($innerTail); $currentSpeechBubble = $( '
' ).append([$tail, $innerBubble]) .appendTo($h5pContainer); // Show speech bubble with transition setTimeout(function () { $currentSpeechBubble.addClass('show'); }, 0); position($currentSpeechBubble, $currentContainer, maxWidth, $tail, $innerTail); // Handle click to close H5P.$body.on('mousedown.speechBubble', handleOutsideClick); // Handle window resizing H5P.$window.on('resize', '', handleResize); // Handle clicks when inside IV which blocks bubbling. $container.parents('.h5p-dialog') .on('mousedown.speechBubble', handleOutsideClick); if (iDevice) { H5P.$body.css('cursor', 'pointer'); } return this; } // Remove speechbubble if it belongs to a dom element that is about to be hidden H5P.externalDispatcher.on('domHidden', function (event) { if ($currentSpeechBubble !== undefined && event.data.$dom.find($currentContainer).length !== 0) { remove(); } }); /** * Returns the closest h5p container for the given DOM element. * * @param {object} $container jquery element * @return {object} the h5p container (jquery element) */ function getH5PContainer($container) { var $h5pContainer = $container.closest('.h5p-frame'); // Check closest h5p frame first, then check for container in case there is no frame. if (!$h5pContainer.length) { $h5pContainer = $container.closest('.h5p-container'); } return $h5pContainer; } /** * Event handler that is called when the window is resized. */ function handleResize() { position($currentSpeechBubble, $currentContainer, currentMaxWidth, $tail, $innerTail); } /** * Repositions the speech bubble according to the position of the container. * * @param {object} $currentSpeechbubble the speech bubble that should be positioned * @param {object} $container the container to which the speech bubble should point * @param {number} maxWidth the maximum width of the speech bubble * @param {object} $tail the tail (the triangle that points to the referenced container) * @param {object} $innerTail the inner tail (the triangle that points to the referenced container) */ function position($currentSpeechBubble, $container, maxWidth, $tail, $innerTail) { var $h5pContainer = getH5PContainer($container); // Calculate offset between the button and the h5p frame var offset = getOffsetBetween($h5pContainer, $container); var direction = (offset.bottom > offset.top ? 'bottom' : 'top'); var tipWidth = offset.outerWidth * 0.9; // Var needs to be renamed to make sense var bubbleWidth = tipWidth > maxWidth ? maxWidth : tipWidth; var bubblePosition = getBubblePosition(bubbleWidth, offset); var tailPosition = getTailPosition(bubbleWidth, bubblePosition, offset, $container.width()); // Need to set font-size, since element is appended to body. // Using same font-size as parent. In that way it will grow accordingly // when resizing var fontSize = 16;//parseFloat($parent.css('font-size')); // Set width and position of speech bubble $currentSpeechBubble.css(bubbleCSS( direction, bubbleWidth, bubblePosition, fontSize )); var preparedTailCSS = tailCSS(direction, tailPosition); $tail.css(preparedTailCSS); $innerTail.css(preparedTailCSS); } /** * Static function for removing the speechbubble */ var remove = function () { H5P.$body.off('mousedown.speechBubble'); H5P.$window.off('resize', '', handleResize); $currentContainer.parents('.h5p-dialog').off('mousedown.speechBubble'); if (iDevice) { H5P.$body.css('cursor', ''); } if ($currentSpeechBubble !== undefined) { // Apply transition, then remove speech bubble $currentSpeechBubble.removeClass('show'); // Make sure we remove any old timeout before reassignment clearTimeout(removeSpeechBubbleTimeout); removeSpeechBubbleTimeout = setTimeout(function () { $currentSpeechBubble.remove(); $currentSpeechBubble = undefined; }, 500); } // Don't return false here. If the user e.g. clicks a button when the bubble is visible, // we want the bubble to disapear AND the button to receive the event }; /** * Remove the speech bubble and container reference */ function handleOutsideClick(event) { if (event.target === $currentContainer[0]) { return; // Button clicks are not outside clicks } remove(); // There is no current container when a container isn't clicked $currentContainer = undefined; } /** * Calculate position for speech bubble * * @param {number} bubbleWidth The width of the speech bubble * @param {object} offset * @return {object} Return position for the speech bubble */ function getBubblePosition(bubbleWidth, offset) { var bubblePosition = {}; var tailOffset = 9; var widthOffset = bubbleWidth / 2; // Calculate top position bubblePosition.top = offset.top + offset.innerHeight; // Calculate bottom position bubblePosition.bottom = offset.bottom + offset.innerHeight + tailOffset; // Calculate left position if (offset.left < widthOffset) { bubblePosition.left = 3; } else if ((offset.left + widthOffset) > offset.outerWidth) { bubblePosition.left = offset.outerWidth - bubbleWidth - 3; } else { bubblePosition.left = offset.left - widthOffset + (offset.innerWidth / 2); } return bubblePosition; } /** * Calculate position for speech bubble tail * * @param {number} bubbleWidth The width of the speech bubble * @param {object} bubblePosition Speech bubble position * @param {object} offset * @param {number} iconWidth The width of the tip icon * @return {object} Return position for the tail */ function getTailPosition(bubbleWidth, bubblePosition, offset, iconWidth) { var tailPosition = {}; // Magic numbers. Tuned by hand so that the tail fits visually within // the bounds of the speech bubble. var leftBoundary = 9; var rightBoundary = bubbleWidth - 20; tailPosition.left = offset.left - bubblePosition.left + (iconWidth / 2) - 6; if (tailPosition.left < leftBoundary) { tailPosition.left = leftBoundary; } if (tailPosition.left > rightBoundary) { tailPosition.left = rightBoundary; } tailPosition.top = -6; tailPosition.bottom = -6; return tailPosition; } /** * Return bubble CSS for the desired growth direction * * @param {string} direction The direction the speech bubble will grow * @param {number} width The width of the speech bubble * @param {object} position Speech bubble position * @param {number} fontSize The size of the bubbles font * @return {object} Return CSS */ function bubbleCSS(direction, width, position, fontSize) { if (direction === 'top') { return { width: width + 'px', bottom: position.bottom + 'px', left: position.left + 'px', fontSize: fontSize + 'px', top: '' }; } else { return { width: width + 'px', top: position.top + 'px', left: position.left + 'px', fontSize: fontSize + 'px', bottom: '' }; } } /** * Return tail CSS for the desired growth direction * * @param {string} direction The direction the speech bubble will grow * @param {object} position Tail position * @return {object} Return CSS */ function tailCSS(direction, position) { if (direction === 'top') { return { bottom: position.bottom + 'px', left: position.left + 'px', top: '' }; } else { return { top: position.top + 'px', left: position.left + 'px', bottom: '' }; } } /** * Calculates the offset between an element inside a container and the * container. Only works if all the edges of the inner element are inside the * outer element. * Width/height of the elements is included as a convenience. * * @param {H5P.jQuery} $outer * @param {H5P.jQuery} $inner * @return {object} Position offset */ function getOffsetBetween($outer, $inner) { var outer = $outer[0].getBoundingClientRect(); var inner = $inner[0].getBoundingClientRect(); return { top: inner.top - outer.top, right: outer.right - inner.right, bottom: outer.bottom - inner.bottom, left: inner.left - outer.left, innerWidth: inner.width, innerHeight: inner.height, outerWidth: outer.width, outerHeight: outer.height }; } return JoubelSpeechBubble; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.JoubelThrobber = (function ($) { /** * Creates a new tip */ function JoubelThrobber() { // h5p-throbber css is described in core var $throbber = $('
', { 'class': 'h5p-throbber' }); return $throbber; } return JoubelThrobber; }(H5P.jQuery)); ; H5P.JoubelTip = (function ($) { var $conv = $('
'); /** * Creates a new tip element. * * NOTE that this may look like a class but it doesn't behave like one. * It returns a jQuery object. * * @param {string} tipHtml The text to display in the popup * @param {Object} [behaviour] Options * @param {string} [behaviour.tipLabel] Set to use a custom label for the tip button (you want this for good A11Y) * @param {boolean} [behaviour.helpIcon] Set to 'true' to Add help-icon classname to Tip button (changes the icon) * @param {boolean} [behaviour.showSpeechBubble] Set to 'false' to disable functionality (you may this in the editor) * @param {boolean} [behaviour.tabcontrol] Set to 'true' if you plan on controlling the tabindex in the parent (tabindex="-1") * @return {H5P.jQuery|undefined} Tip button jQuery element or 'undefined' if invalid tip */ function JoubelTip(tipHtml, behaviour) { // Keep track of the popup that appears when you click the Tip button var speechBubble; // Parse tip html to determine text var tipText = $conv.html(tipHtml).text().trim(); if (tipText === '') { return; // The tip has no textual content, i.e. it's invalid. } // Set default behaviour behaviour = $.extend({ tipLabel: tipText, helpIcon: false, showSpeechBubble: true, tabcontrol: false }, behaviour); // Create Tip button var $tipButton = $('
', { class: 'joubel-tip-container' + (behaviour.showSpeechBubble ? '' : ' be-quiet'), 'aria-label': behaviour.tipLabel, 'aria-expanded': false, role: 'button', tabindex: (behaviour.tabcontrol ? -1 : 0), click: function (event) { // Toggle show/hide popup toggleSpeechBubble(); event.preventDefault(); }, keydown: function (event) { if (event.which === 32 || event.which === 13) { // Space & enter key // Toggle show/hide popup toggleSpeechBubble(); event.stopPropagation(); event.preventDefault(); } else { // Any other key // Toggle hide popup toggleSpeechBubble(false); } }, // Add markup to render icon html: '' + '' + '' + '' + '' // IMPORTANT: All of the markup elements must have 'pointer-events: none;' }); const $tipAnnouncer = $('
', { 'class': 'hidden-but-read', 'aria-live': 'polite', appendTo: $tipButton, }); /** * Tip button interaction handler. * Toggle show or hide the speech bubble popup when interacting with the * Tip button. * * @private * @param {boolean} [force] 'true' shows and 'false' hides. */ var toggleSpeechBubble = function (force) { if (speechBubble !== undefined && speechBubble.isCurrent($tipButton)) { // Hide current popup speechBubble.remove(); speechBubble = undefined; $tipButton.attr('aria-expanded', false); $tipAnnouncer.html(''); } else if (force !== false && behaviour.showSpeechBubble) { // Create and show new popup speechBubble = H5P.JoubelSpeechBubble($tipButton, tipHtml); $tipButton.attr('aria-expanded', true); $tipAnnouncer.html(tipHtml); } }; return $tipButton; } return JoubelTip; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.JoubelSlider = (function ($) { /** * Creates a new Slider * * @param {object} [params] Additional parameters */ function JoubelSlider(params) { H5P.EventDispatcher.call(this); this.$slider = $('
', $.extend({ 'class': 'h5p-joubel-ui-slider' }, params)); this.$slides = []; this.currentIndex = 0; this.numSlides = 0; } JoubelSlider.prototype = Object.create(H5P.EventDispatcher.prototype); JoubelSlider.prototype.constructor = JoubelSlider; JoubelSlider.prototype.addSlide = function ($content) { $content.addClass('h5p-joubel-ui-slide').css({ 'left': (this.numSlides*100) + '%' }); this.$slider.append($content); this.$slides.push($content); this.numSlides++; if(this.numSlides === 1) { $content.addClass('current'); } }; JoubelSlider.prototype.attach = function ($container) { $container.append(this.$slider); }; JoubelSlider.prototype.move = function (index) { var self = this; if(index === 0) { self.trigger('first-slide'); } if(index+1 === self.numSlides) { self.trigger('last-slide'); } self.trigger('move'); var $previousSlide = self.$slides[this.currentIndex]; H5P.Transition.onTransitionEnd(this.$slider, function () { $previousSlide.removeClass('current'); self.trigger('moved'); }); this.$slides[index].addClass('current'); var translateX = 'translateX(' + (-index*100) + '%)'; this.$slider.css({ '-webkit-transform': translateX, '-moz-transform': translateX, '-ms-transform': translateX, 'transform': translateX }); this.currentIndex = index; }; JoubelSlider.prototype.remove = function () { this.$slider.remove(); }; JoubelSlider.prototype.next = function () { if(this.currentIndex+1 >= this.numSlides) { return; } this.move(this.currentIndex+1); }; JoubelSlider.prototype.previous = function () { this.move(this.currentIndex-1); }; JoubelSlider.prototype.first = function () { this.move(0); }; JoubelSlider.prototype.last = function () { this.move(this.numSlides-1); }; return JoubelSlider; })(H5P.jQuery); ; var H5P = H5P || {}; /** * @module */ H5P.JoubelScoreBar = (function ($) { /* Need to use an id for the star SVG since that is the only way to reference SVG filters */ var idCounter = 0; /** * Creates a score bar * @class H5P.JoubelScoreBar * @param {number} maxScore Maximum score * @param {string} [label] Makes it easier for readspeakers to identify the scorebar * @param {string} [helpText] Score explanation * @param {string} [scoreExplanationButtonLabel] Label for score explanation button */ function JoubelScoreBar(maxScore, label, helpText, scoreExplanationButtonLabel) { var self = this; self.maxScore = maxScore; self.score = 0; idCounter++; /** * @const {string} */ self.STAR_MARKUP = ''; /** * @function appendTo * @memberOf H5P.JoubelScoreBar# * @param {H5P.jQuery} $wrapper Dom container */ self.appendTo = function ($wrapper) { self.$scoreBar.appendTo($wrapper); }; /** * Create the text representation of the scorebar . * * @private * @return {string} */ var createLabel = function (score) { if (!label) { return ''; } return label.replace(':num', score).replace(':total', self.maxScore); }; /** * Creates the html for this widget * * @method createHtml * @private */ var createHtml = function () { // Container div self.$scoreBar = $('
', { 'class': 'h5p-joubelui-score-bar', }); var $visuals = $('
', { 'class': 'h5p-joubelui-score-bar-visuals', appendTo: self.$scoreBar }); // The progress bar wrapper self.$progressWrapper = $('
', { 'class': 'h5p-joubelui-score-bar-progress-wrapper', appendTo: $visuals }); self.$progress = $('
', { 'class': 'h5p-joubelui-score-bar-progress', 'html': createLabel(self.score), appendTo: self.$progressWrapper }); // The star $('
', { 'class': 'h5p-joubelui-score-bar-star', html: self.STAR_MARKUP }).appendTo($visuals); // The score container var $numerics = $('
', { 'class': 'h5p-joubelui-score-numeric', appendTo: self.$scoreBar, 'aria-hidden': true }); // The current score self.$scoreCounter = $('', { 'class': 'h5p-joubelui-score-number h5p-joubelui-score-number-counter', text: 0, appendTo: $numerics }); // The separator $('', { 'class': 'h5p-joubelui-score-number-separator', text: '/', appendTo: $numerics }); // Max score self.$maxScore = $('', { 'class': 'h5p-joubelui-score-number h5p-joubelui-score-max', text: self.maxScore, appendTo: $numerics }); if (helpText) { H5P.JoubelUI.createTip(helpText, { tipLabel: scoreExplanationButtonLabel ? scoreExplanationButtonLabel : helpText, helpIcon: true }).appendTo(self.$scoreBar); self.$scoreBar.addClass('h5p-score-bar-has-help'); } }; /** * Set the current score * @method setScore * @memberOf H5P.JoubelScoreBar# * @param {number} score */ self.setScore = function (score) { // Do nothing if score hasn't changed if (score === self.score) { return; } self.score = score > self.maxScore ? self.maxScore : score; self.updateVisuals(); }; /** * Increment score * @method incrementScore * @memberOf H5P.JoubelScoreBar# * @param {number=} incrementBy Optional parameter, defaults to 1 */ self.incrementScore = function (incrementBy) { self.setScore(self.score + (incrementBy || 1)); }; /** * Set the max score * @method setMaxScore * @memberOf H5P.JoubelScoreBar# * @param {number} maxScore The max score */ self.setMaxScore = function (maxScore) { self.maxScore = maxScore; }; /** * Updates the progressbar visuals * @memberOf H5P.JoubelScoreBar# * @method updateVisuals */ self.updateVisuals = function () { self.$progress.html(createLabel(self.score)); self.$scoreCounter.text(self.score); self.$maxScore.text(self.maxScore); setTimeout(function () { // Start the progressbar animation self.$progress.css({ width: ((self.score / self.maxScore) * 100) + '%' }); H5P.Transition.onTransitionEnd(self.$progress, function () { // If fullscore fill the star and start the animation self.$scoreBar.toggleClass('h5p-joubelui-score-bar-full-score', self.score === self.maxScore); self.$scoreBar.toggleClass('h5p-joubelui-score-bar-animation-active', self.score === self.maxScore); // Only allow the star animation to run once self.$scoreBar.one("animationend", function() { self.$scoreBar.removeClass("h5p-joubelui-score-bar-animation-active"); }); }, 600); }, 300); }; /** * Removes all classes * @method reset */ self.reset = function () { self.$scoreBar.removeClass('h5p-joubelui-score-bar-full-score'); }; createHtml(); } return JoubelScoreBar; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.JoubelProgressbar = (function ($) { /** * Joubel progressbar class * @method JoubelProgressbar * @constructor * @param {number} steps Number of steps * @param {Object} [options] Additional options * @param {boolean} [options.disableAria] Disable readspeaker assistance * @param {string} [options.progressText] A progress text for describing * current progress out of total progress for readspeakers. * e.g. "Slide :num of :total" */ function JoubelProgressbar(steps, options) { H5P.EventDispatcher.call(this); var self = this; this.options = $.extend({ progressText: 'Slide :num of :total' }, options); this.currentStep = 0; this.steps = steps; this.$progressbar = $('
', { 'class': 'h5p-joubelui-progressbar' }); this.$background = $('
', { 'class': 'h5p-joubelui-progressbar-background' }).appendTo(this.$progressbar); } JoubelProgressbar.prototype = Object.create(H5P.EventDispatcher.prototype); JoubelProgressbar.prototype.constructor = JoubelProgressbar; JoubelProgressbar.prototype.updateAria = function () { var self = this; if (this.options.disableAria) { return; } if (!this.$currentStatus) { this.$currentStatus = $('
', { 'class': 'h5p-joubelui-progressbar-slide-status-text', 'aria-live': 'assertive' }).appendTo(this.$progressbar); } var interpolatedProgressText = self.options.progressText .replace(':num', self.currentStep) .replace(':total', self.steps); this.$currentStatus.html(interpolatedProgressText); }; /** * Appends to a container * @method appendTo * @param {H5P.jquery} $container */ JoubelProgressbar.prototype.appendTo = function ($container) { this.$progressbar.appendTo($container); }; /** * Update progress * @method setProgress * @param {number} step */ JoubelProgressbar.prototype.setProgress = function (step) { // Check for valid value: if (step > this.steps || step < 0) { return; } this.currentStep = step; this.$background.css({ width: ((this.currentStep/this.steps)*100) + '%' }); this.updateAria(); }; /** * Increment progress with 1 * @method next */ JoubelProgressbar.prototype.next = function () { this.setProgress(this.currentStep+1); }; /** * Reset progressbar * @method reset */ JoubelProgressbar.prototype.reset = function () { this.setProgress(0); }; /** * Check if last step is reached * @method isLastStep * @return {Boolean} */ JoubelProgressbar.prototype.isLastStep = function () { return this.steps === this.currentStep; }; return JoubelProgressbar; })(H5P.jQuery); ; var H5P = H5P || {}; /** * H5P Joubel UI library. * * This is a utility library, which does not implement attach. I.e, it has to bee actively used by * other libraries * @module */ H5P.JoubelUI = (function ($) { /** * The internal object to return * @class H5P.JoubelUI * @static */ function JoubelUI() {} /* Public static functions */ /** * Create a tip icon * @method H5P.JoubelUI.createTip * @param {string} text The textual tip * @param {Object} params Parameters * @return {H5P.JoubelTip} */ JoubelUI.createTip = function (text, params) { return new H5P.JoubelTip(text, params); }; /** * Create message dialog * @method H5P.JoubelUI.createMessageDialog * @param {H5P.jQuery} $container The dom container * @param {string} message The message * @return {H5P.JoubelMessageDialog} */ JoubelUI.createMessageDialog = function ($container, message) { return new H5P.JoubelMessageDialog($container, message); }; /** * Create help text dialog * @method H5P.JoubelUI.createHelpTextDialog * @param {string} header The textual header * @param {string} message The textual message * @param {string} closeButtonTitle The title for the close button * @return {H5P.JoubelHelpTextDialog} */ JoubelUI.createHelpTextDialog = function (header, message, closeButtonTitle) { return new H5P.JoubelHelpTextDialog(header, message, closeButtonTitle); }; /** * Create progress circle * @method H5P.JoubelUI.createProgressCircle * @param {number} number The progress (0 to 100) * @param {string} progressColor The progress color in hex value * @param {string} fillColor The fill color in hex value * @param {string} backgroundColor The background color in hex value * @return {H5P.JoubelProgressCircle} */ JoubelUI.createProgressCircle = function (number, progressColor, fillColor, backgroundColor) { return new H5P.JoubelProgressCircle(number, progressColor, fillColor, backgroundColor); }; /** * Create throbber for loading * @method H5P.JoubelUI.createThrobber * @return {H5P.JoubelThrobber} */ JoubelUI.createThrobber = function () { return new H5P.JoubelThrobber(); }; /** * Create simple rounded button * @method H5P.JoubelUI.createSimpleRoundedButton * @param {string} text The button label * @return {H5P.SimpleRoundedButton} */ JoubelUI.createSimpleRoundedButton = function (text) { return new H5P.SimpleRoundedButton(text); }; /** * Create Slider * @method H5P.JoubelUI.createSlider * @param {Object} [params] Parameters * @return {H5P.JoubelSlider} */ JoubelUI.createSlider = function (params) { return new H5P.JoubelSlider(params); }; /** * Create Score Bar * @method H5P.JoubelUI.createScoreBar * @param {number=} maxScore The maximum score * @param {string} [label] Makes it easier for readspeakers to identify the scorebar * @return {H5P.JoubelScoreBar} */ JoubelUI.createScoreBar = function (maxScore, label, helpText, scoreExplanationButtonLabel) { return new H5P.JoubelScoreBar(maxScore, label, helpText, scoreExplanationButtonLabel); }; /** * Create Progressbar * @method H5P.JoubelUI.createProgressbar * @param {number=} numSteps The total numer of steps * @param {Object} [options] Additional options * @param {boolean} [options.disableAria] Disable readspeaker assistance * @param {string} [options.progressText] A progress text for describing * current progress out of total progress for readspeakers. * e.g. "Slide :num of :total" * @return {H5P.JoubelProgressbar} */ JoubelUI.createProgressbar = function (numSteps, options) { return new H5P.JoubelProgressbar(numSteps, options); }; /** * Create standard Joubel button * * @method H5P.JoubelUI.createButton * @param {object} params * May hold any properties allowed by jQuery. If href is set, an A tag * is used, if not a button tag is used. * @return {H5P.jQuery} The jquery element created */ JoubelUI.createButton = function(params) { var type = 'button'; if (params.href) { type = 'a'; } else { params.type = 'button'; } if (params.class) { params.class += ' h5p-joubelui-button'; } else { params.class = 'h5p-joubelui-button'; } return $('<' + type + '/>', params); }; /** * Fix for iframe scoll bug in IOS. When focusing an element that doesn't have * focus support by default the iframe will scroll the parent frame so that * the focused element is out of view. This varies dependening on the elements * of the parent frame. */ if (H5P.isFramed && !H5P.hasiOSiframeScrollFix && /iPad|iPhone|iPod/.test(navigator.userAgent)) { H5P.hasiOSiframeScrollFix = true; // Keep track of original focus function var focus = HTMLElement.prototype.focus; // Override the original focus HTMLElement.prototype.focus = function () { // Only focus the element if it supports it natively if ( (this instanceof HTMLAnchorElement || this instanceof HTMLInputElement || this instanceof HTMLSelectElement || this instanceof HTMLTextAreaElement || this instanceof HTMLButtonElement || this instanceof HTMLIFrameElement || this instanceof HTMLAreaElement) && // HTMLAreaElement isn't supported by Safari yet. !this.getAttribute('role')) { // Focus breaks if a different role has been set // In theory this.isContentEditable should be able to recieve focus, // but it didn't work when tested. // Trigger the original focus with the proper context focus.call(this); } }; } return JoubelUI; })(H5P.jQuery); ; H5P.Question = (function ($, EventDispatcher, JoubelUI) { /** * Extending this class make it alot easier to create tasks for other * content types. * * @class H5P.Question * @extends H5P.EventDispatcher * @param {string} type */ function Question(type) { var self = this; // Inheritance EventDispatcher.call(self); // Register default section order self.order = ['video', 'image', 'introduction', 'content', 'explanation', 'feedback', 'scorebar', 'buttons', 'read']; // Keep track of registered sections var sections = {}; // Buttons var buttons = {}; var buttonOrder = []; // Wrapper when attached var $wrapper; // Click element var clickElement; // ScoreBar var scoreBar; // Keep track of the feedback's visual status. var showFeedback; // Keep track of which buttons are scheduled for hiding. var buttonsToHide = []; // Keep track of which buttons are scheduled for showing. var buttonsToShow = []; // Keep track of the hiding and showing of buttons. var toggleButtonsTimer; var toggleButtonsTransitionTimer; var buttonTruncationTimer; // Keeps track of initialization of question var initialized = false; /** * @type {Object} behaviour Behaviour of Question * @property {Boolean} behaviour.disableFeedback Set to true to disable feedback section */ var behaviour = { disableFeedback: false, disableReadSpeaker: false }; // Keeps track of thumb state var imageThumb = true; // Keeps track of image transitions var imageTransitionTimer; // Keep track of whether sections is transitioning. var sectionsIsTransitioning = false; // Keep track of auto play state var disableAutoPlay = false; // Feedback transition timer var feedbackTransitionTimer; // Used when reading messages to the user var $read, readText; /** * Register section with given content. * * @private * @param {string} section ID of the section * @param {(string|H5P.jQuery)} [content] */ var register = function (section, content) { sections[section] = {}; var $e = sections[section].$element = $('
', { 'class': 'h5p-question-' + section, }); if (content) { $e[content instanceof $ ? 'append' : 'html'](content); } }; /** * Update registered section with content. * * @private * @param {string} section ID of the section * @param {(string|H5P.jQuery)} content */ var update = function (section, content) { if (content instanceof $) { sections[section].$element.html('').append(content); } else { sections[section].$element.html(content); } }; /** * Insert element with given ID into the DOM. * * @private * @param {array|Array|string[]} order * List with ordered element IDs * @param {string} id * ID of the element to be inserted * @param {Object} elements * Maps ID to the elements * @param {H5P.jQuery} $container * Parent container of the elements */ var insert = function (order, id, elements, $container) { // Try to find an element id should be after for (var i = 0; i < order.length; i++) { if (order[i] === id) { // Found our pos while (i > 0 && (elements[order[i - 1]] === undefined || !elements[order[i - 1]].isVisible)) { i--; } if (i === 0) { // We are on top. elements[id].$element.prependTo($container); } else { // Add after element elements[id].$element.insertAfter(elements[order[i - 1]].$element); } elements[id].isVisible = true; break; } } }; /** * Make feedback into a popup and position relative to click. * * @private * @param {string} [closeText] Text for the close button */ var makeFeedbackPopup = function (closeText) { var $element = sections.feedback.$element; var $parent = sections.content.$element; var $click = (clickElement != null ? clickElement.$element : null); $element.appendTo($parent).addClass('h5p-question-popup'); if (sections.scorebar) { sections.scorebar.$element.appendTo($element); } $parent.addClass('h5p-has-question-popup'); // Draw the tail var $tail = $('
', { 'class': 'h5p-question-feedback-tail' }).hide() .appendTo($parent); // Draw the close button var $close = $('
', { 'class': 'h5p-question-feedback-close', 'tabindex': 0, 'title': closeText, on: { click: function (event) { $element.remove(); $tail.remove(); event.preventDefault(); }, keydown: function (event) { switch (event.which) { case 13: // Enter case 32: // Space $element.remove(); $tail.remove(); event.preventDefault(); } } } }).hide().appendTo($element); if ($click != null) { if ($click.hasClass('correct')) { $element.addClass('h5p-question-feedback-correct'); $close.show(); sections.buttons.$element.hide(); } else { sections.buttons.$element.appendTo(sections.feedback.$element); } } positionFeedbackPopup($element, $click); }; /** * Position the feedback popup. * * @private * @param {H5P.jQuery} $element Feedback div * @param {H5P.jQuery} $click Visual click div */ var positionFeedbackPopup = function ($element, $click) { var $container = $element.parent(); var $tail = $element.siblings('.h5p-question-feedback-tail'); var popupWidth = $element.outerWidth(); var popupHeight = setElementHeight($element); var space = 15; var disableTail = false; var positionY = $container.height() / 2 - popupHeight / 2; var positionX = $container.width() / 2 - popupWidth / 2; var tailX = 0; var tailY = 0; var tailRotation = 0; if ($click != null) { // Edge detection for click, takes space into account var clickNearTop = ($click[0].offsetTop < space); var clickNearBottom = ($click[0].offsetTop + $click.height() > $container.height() - space); var clickNearLeft = ($click[0].offsetLeft < space); var clickNearRight = ($click[0].offsetLeft + $click.width() > $container.width() - space); // Click is not in a corner or close to edge, calculate position normally positionX = $click[0].offsetLeft - popupWidth / 2 + $click.width() / 2; positionY = $click[0].offsetTop - popupHeight - space; tailX = positionX + popupWidth / 2 - $tail.width() / 2; tailY = positionY + popupHeight - ($tail.height() / 2); tailRotation = 225; // If popup is outside top edge, position under click instead if (popupHeight + space > $click[0].offsetTop) { positionY = $click[0].offsetTop + $click.height() + space; tailY = positionY - $tail.height() / 2 ; tailRotation = 45; } // If popup is outside left edge, position left if (positionX < 0) { positionX = 0; } // If popup is outside right edge, position right if (positionX + popupWidth > $container.width()) { positionX = $container.width() - popupWidth; } // Special cases such as corner clicks, or close to an edge, they override X and Y positions if met if (clickNearTop && (clickNearLeft || clickNearRight)) { positionX = $click[0].offsetLeft + (clickNearLeft ? $click.width() : -popupWidth); positionY = $click[0].offsetTop + $click.height(); disableTail = true; } else if (clickNearBottom && (clickNearLeft || clickNearRight)) { positionX = $click[0].offsetLeft + (clickNearLeft ? $click.width() : -popupWidth); positionY = $click[0].offsetTop - popupHeight; disableTail = true; } else if (!clickNearTop && !clickNearBottom) { if (clickNearLeft || clickNearRight) { positionY = $click[0].offsetTop - popupHeight / 2 + $click.width() / 2; positionX = $click[0].offsetLeft + (clickNearLeft ? $click.width() + space : -popupWidth + -space); // Make sure this does not position the popup off screen if (positionX < 0) { positionX = 0; disableTail = true; } else { tailX = positionX + (clickNearLeft ? - $tail.width() / 2 : popupWidth - $tail.width() / 2); tailY = positionY + popupHeight / 2 - $tail.height() / 2; tailRotation = (clickNearLeft ? 315 : 135); } } } // Contain popup from overflowing bottom edge if (positionY + popupHeight > $container.height()) { positionY = $container.height() - popupHeight; if (popupHeight > $container.height() - ($click[0].offsetTop + $click.height() + space)) { disableTail = true; } } } else { disableTail = true; } // Contain popup from ovreflowing top edge if (positionY < 0) { positionY = 0; } $element.css({top: positionY, left: positionX}); $tail.css({top: tailY, left: tailX}); if (!disableTail) { $tail.css({ 'left': tailX, 'top': tailY, 'transform': 'rotate(' + tailRotation + 'deg)' }).show(); } else { $tail.hide(); } }; /** * Set element max height, used for animations. * * @param {H5P.jQuery} $element */ var setElementHeight = function ($element) { if (!$element.is(':visible')) { // No animation $element.css('max-height', 'none'); return; } // If this element is shown in the popup, we can't set width to 100%, // since it already has a width set in CSS var isFeedbackPopup = $element.hasClass('h5p-question-popup'); // Get natural element height var $tmp = $element.clone() .css({ 'position': 'absolute', 'max-height': 'none', 'width': isFeedbackPopup ? '' : '100%' }) .appendTo($element.parent()); // Need to take margins into account when calculating available space var sideMargins = parseFloat($element.css('margin-left')) + parseFloat($element.css('margin-right')); var tmpElWidth = $tmp.css('width') ? $tmp.css('width') : '100%'; $tmp.css('width', 'calc(' + tmpElWidth + ' - ' + sideMargins + 'px)'); // Apply height to element var h = Math.round($tmp.get(0).getBoundingClientRect().height); var fontSize = parseFloat($element.css('fontSize')); var relativeH = h / fontSize; $element.css('max-height', relativeH + 'em'); $tmp.remove(); if (h > 0 && sections.buttons && sections.buttons.$element === $element) { // Make sure buttons section is visible showSection(sections.buttons); // Resize buttons after resizing button section setTimeout(resizeButtons, 150); } return h; }; /** * Does the actual job of hiding the buttons scheduled for hiding. * * @private * @param {boolean} [relocateFocus] Find a new button to focus */ var hideButtons = function (relocateFocus) { for (var i = 0; i < buttonsToHide.length; i++) { hideButton(buttonsToHide[i].id); } buttonsToHide = []; if (relocateFocus) { self.focusButton(); } }; /** * Does the actual hiding. * @private * @param {string} buttonId */ var hideButton = function (buttonId) { // Using detach() vs hide() makes it harder to cheat. buttons[buttonId].$element.detach(); buttons[buttonId].isVisible = false; }; /** * Shows the buttons on the next tick. This is to avoid buttons flickering * If they're both added and removed on the same tick. * * @private */ var toggleButtons = function () { // If no buttons section, return if (sections.buttons === undefined) { return; } // Clear transition timer, reevaluate if buttons will be detached clearTimeout(toggleButtonsTransitionTimer); // Show buttons for (var i = 0; i < buttonsToShow.length; i++) { insert(buttonOrder, buttonsToShow[i].id, buttons, sections.buttons.$element); buttons[buttonsToShow[i].id].isVisible = true; } buttonsToShow = []; // Hide buttons var numToHide = 0; var relocateFocus = false; for (var j = 0; j < buttonsToHide.length; j++) { var button = buttons[buttonsToHide[j].id]; if (button.isVisible) { numToHide += 1; } if (button.$element.is(':focus')) { // Move focus to the first visible button. relocateFocus = true; } } var animationTimer = 150; if (sections.feedback && sections.feedback.$element.hasClass('h5p-question-popup')) { animationTimer = 0; } if (numToHide === sections.buttons.$element.children().length) { // All buttons are going to be hidden. Hide container using transition. hideSection(sections.buttons); // Detach buttons hideButtons(relocateFocus); } else { hideButtons(relocateFocus); // Show button section if (!sections.buttons.$element.is(':empty')) { showSection(sections.buttons); setElementHeight(sections.buttons.$element); // Trigger resize after animation toggleButtonsTransitionTimer = setTimeout(function () { self.trigger('resize'); }, animationTimer); } // Resize buttons to fit container resizeButtons(); } toggleButtonsTimer = undefined; }; /** * Allows for scaling of the question image. */ var scaleImage = function () { var $imgSection = sections.image.$element; clearTimeout(imageTransitionTimer); // Add this here to avoid initial transition of the image making // content overflow. Alternatively we need to trigger a resize. $imgSection.addClass('animatable'); if (imageThumb) { // Expand image $(this).attr('aria-expanded', true); $imgSection.addClass('h5p-question-image-fill-width'); imageThumb = false; imageTransitionTimer = setTimeout(function () { self.trigger('resize'); }, 600); } else { // Scale down image $(this).attr('aria-expanded', false); $imgSection.removeClass('h5p-question-image-fill-width'); imageThumb = true; imageTransitionTimer = setTimeout(function () { self.trigger('resize'); }, 600); } }; /** * Get scrollable ancestor of element * * @private * @param {H5P.jQuery} $element * @param {Number} [currDepth=0] Current recursive calls to ancestor, stop at maxDepth * @param {Number} [maxDepth=5] Maximum depth for finding ancestor. * @returns {H5P.jQuery} Parent element that is scrollable */ var findScrollableAncestor = function ($element, currDepth, maxDepth) { if (!currDepth) { currDepth = 0; } if (!maxDepth) { maxDepth = 5; } // Check validation of element or if we have reached document root if (!$element || !($element instanceof $) || document === $element.get(0) || currDepth >= maxDepth) { return; } if ($element.css('overflow-y') === 'auto') { return $element; } else { return findScrollableAncestor($element.parent(), currDepth + 1, maxDepth); } }; /** * Scroll to bottom of Question. * * @private */ var scrollToBottom = function () { if (!$wrapper || ($wrapper.hasClass('h5p-standalone') && !H5P.isFullscreen)) { return; // No scroll } var scrollableAncestor = findScrollableAncestor($wrapper); // Scroll to bottom of scrollable ancestor if (scrollableAncestor) { scrollableAncestor.animate({ scrollTop: $wrapper.css('height') }, "slow"); } }; /** * Resize buttons to fit container width * * @private */ var resizeButtons = function () { if (!buttons || !sections.buttons) { return; } var go = function () { // Don't do anything if button elements are not visible yet if (!sections.buttons.$element.is(':visible')) { return; } // Width of all buttons var buttonsWidth = { max: 0, min: 0, current: 0 }; for (var i in buttons) { var button = buttons[i]; if (button.isVisible) { setButtonWidth(buttons[i]); buttonsWidth.max += button.width.max; buttonsWidth.min += button.width.min; buttonsWidth.current += button.isTruncated ? button.width.min : button.width.max; } } var makeButtonsFit = function (availableWidth) { if (buttonsWidth.max < availableWidth) { // It is room for everyone on the right side of the score bar (without truncating) if (buttonsWidth.max !== buttonsWidth.current) { // Need to make everyone big restoreButtonLabels(buttonsWidth.current, availableWidth); } return true; } else if (buttonsWidth.min < availableWidth) { // Is it room for everyone on the right side of the score bar with truncating? if (buttonsWidth.current > availableWidth) { removeButtonLabels(buttonsWidth.current, availableWidth); } else { restoreButtonLabels(buttonsWidth.current, availableWidth); } return true; } return false; }; toggleFullWidthScorebar(false); var buttonSectionWidth = Math.floor(sections.buttons.$element.width()) - 1; if (!makeButtonsFit(buttonSectionWidth)) { // If we get here we need to wrap: toggleFullWidthScorebar(true); buttonSectionWidth = Math.floor(sections.buttons.$element.width()) - 1; makeButtonsFit(buttonSectionWidth); } }; // If visible, resize right away if (sections.buttons.$element.is(':visible')) { go(); } else { // If not visible, try on the next tick // Clear button truncation timer if within a button truncation function if (buttonTruncationTimer) { clearTimeout(buttonTruncationTimer); } buttonTruncationTimer = setTimeout(function () { buttonTruncationTimer = undefined; go(); }, 0); } }; var toggleFullWidthScorebar = function (enabled) { if (sections.scorebar && sections.scorebar.$element && sections.scorebar.$element.hasClass('h5p-question-visible')) { sections.buttons.$element.addClass('has-scorebar'); sections.buttons.$element.toggleClass('wrap', enabled); sections.scorebar.$element.toggleClass('full-width', enabled); } else { sections.buttons.$element.removeClass('has-scorebar'); } }; /** * Remove button labels until they use less than max width. * * @private * @param {Number} buttonsWidth Total width of all buttons * @param {Number} maxButtonsWidth Max width allowed for buttons */ var removeButtonLabels = function (buttonsWidth, maxButtonsWidth) { // Reverse traversal for (var i = buttonOrder.length - 1; i >= 0; i--) { var buttonId = buttonOrder[i]; var button = buttons[buttonId]; if (!button.isTruncated && button.isVisible) { var $button = button.$element; buttonsWidth -= button.width.max - button.width.min; // Remove label button.$element.attr('aria-label', $button.text()).html('').addClass('truncated'); button.isTruncated = true; if (buttonsWidth <= maxButtonsWidth) { // Buttons are small enough. return; } } } }; /** * Restore button labels until it fills maximum possible width without exceeding the max width. * * @private * @param {Number} buttonsWidth Total width of all buttons * @param {Number} maxButtonsWidth Max width allowed for buttons */ var restoreButtonLabels = function (buttonsWidth, maxButtonsWidth) { for (var i = 0; i < buttonOrder.length; i++) { var buttonId = buttonOrder[i]; var button = buttons[buttonId]; if (button.isTruncated && button.isVisible) { // Calculate new total width of buttons with a static pixel for consistency cross-browser buttonsWidth += button.width.max - button.width.min + 1; if (buttonsWidth > maxButtonsWidth) { return; } // Restore label button.$element.html(button.text); button.$element.removeClass('truncated'); button.isTruncated = false; } } }; /** * Helper function for finding index of keyValue in array * * @param {String} keyValue Value to be found * @param {String} key In key * @param {Array} array In array * @returns {number} */ var existsInArray = function (keyValue, key, array) { var i; for (i = 0; i < array.length; i++) { if (array[i][key] === keyValue) { return i; } } return -1; }; /** * Show a section * @param {Object} section */ var showSection = function (section) { section.$element.addClass('h5p-question-visible'); section.isVisible = true; }; /** * Hide a section * @param {Object} section */ var hideSection = function (section) { section.$element.css('max-height', ''); section.isVisible = false; setTimeout(function () { // Only hide if section hasn't been set to visible in the meantime if (!section.isVisible) { section.$element.removeClass('h5p-question-visible'); } }, 150); }; /** * Set behaviour for question. * * @param {Object} options An object containing behaviour that will be extended by Question */ self.setBehaviour = function (options) { $.extend(behaviour, options); }; /** * A video to display above the task. * * @param {object} params */ self.setVideo = function (params) { sections.video = { $element: $('
', { 'class': 'h5p-question-video' }) }; if (disableAutoPlay && params.params.playback) { params.params.playback.autoplay = false; } // Never fit to wrapper if (!params.params.visuals) { params.params.visuals = {}; } params.params.visuals.fit = false; sections.video.instance = H5P.newRunnable(params, self.contentId, sections.video.$element, true); var fromVideo = false; // Hack to avoid never ending loop sections.video.instance.on('resize', function () { fromVideo = true; self.trigger('resize'); fromVideo = false; }); self.on('resize', function () { if (!fromVideo) { sections.video.instance.trigger('resize'); } }); return self; }; /** * Will stop any playback going on in the task. */ self.pause = function () { if (sections.video && sections.video.isVisible) { sections.video.instance.pause(); } }; /** * Start playback of video */ self.play = function () { if (sections.video && sections.video.isVisible) { sections.video.instance.play(); } }; /** * Disable auto play, useful in editors. */ self.disableAutoPlay = function () { disableAutoPlay = true; }; /** * Add task image. * * @param {string} path Relative * @param {Object} [options] Options object * @param {string} [options.alt] Text representation * @param {string} [options.title] Hover text * @param {Boolean} [options.disableImageZooming] Set as true to disable image zooming */ self.setImage = function (path, options) { options = options ? options : {}; sections.image = {}; // Image container sections.image.$element = $('
', { 'class': 'h5p-question-image h5p-question-image-fill-width' }); // Inner wrap var $imgWrap = $('
', { 'class': 'h5p-question-image-wrap', appendTo: sections.image.$element }); // Image element var $img = $('', { src: H5P.getPath(path, self.contentId), alt: (options.alt === undefined ? '' : options.alt), title: (options.title === undefined ? '' : options.title), on: { load: function () { self.trigger('imageLoaded', this); self.trigger('resize'); } }, appendTo: $imgWrap }); // Disable image zooming if (options.disableImageZooming) { $img.css('maxHeight', 'none'); // Make sure we are using the correct amount of width at all times var determineImgWidth = function () { // Remove margins if natural image width is bigger than section width var imageSectionWidth = sections.image.$element.get(0).getBoundingClientRect().width; // Do not transition, for instant measurements $imgWrap.css({ '-webkit-transition': 'none', 'transition': 'none' }); // Margin as translateX on both sides of image. var diffX = 2 * ($imgWrap.get(0).getBoundingClientRect().left - sections.image.$element.get(0).getBoundingClientRect().left); if ($img.get(0).naturalWidth >= imageSectionWidth - diffX) { sections.image.$element.addClass('h5p-question-image-fill-width'); } else { // Use margin for small res images sections.image.$element.removeClass('h5p-question-image-fill-width'); } // Reset transition rules $imgWrap.css({ '-webkit-transition': '', 'transition': '' }); }; // Determine image width if ($img.is(':visible')) { determineImgWidth(); } else { $img.on('load', determineImgWidth); } // Skip adding zoom functionality return; } var sizeDetermined = false; var determineSize = function () { if (sizeDetermined || !$img.is(':visible')) { return; // Try again next time. } $imgWrap.addClass('h5p-question-image-scalable') .attr('aria-expanded', false) .attr('role', 'button') .attr('tabIndex', '0') .on('click', function (event) { if (event.which === 1) { scaleImage.apply(this); // Left mouse button click } }).on('keypress', function (event) { if (event.which === 32) { event.preventDefault(); // Prevent default behaviour; page scroll down scaleImage.apply(this); // Space bar pressed } }); sections.image.$element.removeClass('h5p-question-image-fill-width'); sizeDetermined = true; // Prevent any futher events }; self.on('resize', determineSize); return self; }; /** * Add the introduction section. * * @param {(string|H5P.jQuery)} content */ self.setIntroduction = function (content) { register('introduction', content); return self; }; /** * Add the content section. * * @param {(string|H5P.jQuery)} content * @param {Object} [options] * @param {string} [options.class] */ self.setContent = function (content, options) { register('content', content); if (options && options.class) { sections.content.$element.addClass(options.class); } return self; }; /** * Force readspeaker to read text. Useful when you have to use * setTimeout for animations. */ self.read = function (content) { if (!$read) { return; // Not ready yet } if (readText) { // Combine texts if called multiple times readText += (readText.substr(-1, 1) === '.' ? ' ' : '. ') + content; } else { readText = content; } // Set text $read.html(readText); setTimeout(function () { // Stop combining when done reading readText = null; $read.html(''); }, 100); }; /** * Read feedback */ self.readFeedback = function () { var invalidFeedback = behaviour.disableReadSpeaker || !showFeedback || !sections.feedback || !sections.feedback.$element; if (invalidFeedback) { return; } var $feedbackText = $('.h5p-question-feedback-content-text', sections.feedback.$element); if ($feedbackText && $feedbackText.html() && $feedbackText.html().length) { self.read($feedbackText.html()); } }; /** * Remove feedback * * @return {H5P.Question} */ self.removeFeedback = function () { clearTimeout(feedbackTransitionTimer); if (sections.feedback && showFeedback) { showFeedback = false; // Hide feedback & scorebar hideSection(sections.scorebar); hideSection(sections.feedback); sectionsIsTransitioning = true; // Detach after transition feedbackTransitionTimer = setTimeout(function () { // Avoiding Transition.onTransitionEnd since it will register multiple events, and there's no way to cancel it if the transition changes back to "show" while the animation is happening. if (!showFeedback) { sections.feedback.$element.children().detach(); sections.scorebar.$element.children().detach(); // Trigger resize after animation self.trigger('resize'); } sectionsIsTransitioning = false; scoreBar.setScore(0); }, 150); if ($wrapper) { $wrapper.find('.h5p-question-feedback-tail').remove(); } } return self; }; /** * Set feedback message. * * @param {string} [content] * @param {number} score The score * @param {number} maxScore The maximum score for this question * @param {string} [scoreBarLabel] Makes it easier for readspeakers to identify the scorebar * @param {string} [helpText] Help text that describes the score inside a tip icon * @param {object} [popupSettings] Extra settings for popup feedback * @param {boolean} [popupSettings.showAsPopup] Should the feedback display as popup? * @param {string} [popupSettings.closeText] Translation for close button text * @param {object} [popupSettings.click] Element representing where user clicked on screen */ self.setFeedback = function (content, score, maxScore, scoreBarLabel, helpText, popupSettings, scoreExplanationButtonLabel) { // Feedback is disabled if (behaviour.disableFeedback) { return self; } // Need to toggle buttons right away to avoid flickering/blinking // Note: This means content types should invoke hide/showButton before setFeedback toggleButtons(); clickElement = (popupSettings != null && popupSettings.click != null ? popupSettings.click : null); clearTimeout(feedbackTransitionTimer); var $feedback = $('
', { 'class': 'h5p-question-feedback-container' }); var $feedbackContent = $('
', { 'class': 'h5p-question-feedback-content' }).appendTo($feedback); // Feedback text $('
', { 'class': 'h5p-question-feedback-content-text', 'html': content }).appendTo($feedbackContent); var $scorebar = $('
', { 'class': 'h5p-question-scorebar-container' }); if (scoreBar === undefined) { scoreBar = JoubelUI.createScoreBar(maxScore, scoreBarLabel, helpText, scoreExplanationButtonLabel); } scoreBar.appendTo($scorebar); $feedbackContent.toggleClass('has-content', content !== undefined && content.length > 0); // Feedback for readspeakers if (!behaviour.disableReadSpeaker && scoreBarLabel) { self.read(scoreBarLabel.replace(':num', score).replace(':total', maxScore) + '. ' + (content ? content : '')); } showFeedback = true; if (sections.feedback) { // Update section update('feedback', $feedback); update('scorebar', $scorebar); } else { // Create section register('feedback', $feedback); register('scorebar', $scorebar); if (initialized && $wrapper) { insert(self.order, 'feedback', sections, $wrapper); insert(self.order, 'scorebar', sections, $wrapper); } } showSection(sections.feedback); showSection(sections.scorebar); resizeButtons(); if (popupSettings != null && popupSettings.showAsPopup == true) { makeFeedbackPopup(popupSettings.closeText); scoreBar.setScore(score); } else { // Show feedback section feedbackTransitionTimer = setTimeout(function () { setElementHeight(sections.feedback.$element); setElementHeight(sections.scorebar.$element); sectionsIsTransitioning = true; // Scroll to bottom after showing feedback scrollToBottom(); // Trigger resize after animation feedbackTransitionTimer = setTimeout(function () { sectionsIsTransitioning = false; self.trigger('resize'); scoreBar.setScore(score); }, 150); }, 0); } return self; }; /** * Set feedback content (no animation). * * @param {string} content * @param {boolean} [extendContent] True will extend content, instead of replacing it */ self.updateFeedbackContent = function (content, extendContent) { if (sections.feedback && sections.feedback.$element) { if (extendContent) { content = $('.h5p-question-feedback-content', sections.feedback.$element).html() + ' ' + content; } // Update feedback content html $('.h5p-question-feedback-content', sections.feedback.$element).html(content).addClass('has-content'); // Make sure the height is correct setElementHeight(sections.feedback.$element); // Need to trigger resize when feedback has finished transitioning setTimeout(self.trigger.bind(self, 'resize'), 150); } return self; }; /** * Set the content of the explanation / feedback panel * * @param {Object} data * @param {string} data.correct * @param {string} data.wrong * @param {string} data.text * @param {string} title Title for explanation panel * * @return {H5P.Question} */ self.setExplanation = function (data, title) { if (data) { var explainer = new H5P.Question.Explainer(title, data); if (sections.explanation) { // Update section update('explanation', explainer.getElement()); } else { register('explanation', explainer.getElement()); if (initialized && $wrapper) { insert(self.order, 'explanation', sections, $wrapper); } } } else if (sections.explanation) { // Hide explanation section sections.explanation.$element.children().detach(); } return self; }; /** * Checks to see if button is registered. * * @param {string} id * @returns {boolean} */ self.hasButton = function (id) { return (buttons[id] !== undefined); }; /** * @typedef {Object} ConfirmationDialog * @property {boolean} [enable] Must be true to show confirmation dialog * @property {Object} [instance] Instance that uses confirmation dialog * @property {jQuery} [$parentElement] Append to this element. * @property {Object} [l10n] Translatable fields * @property {string} [l10n.header] Header text * @property {string} [l10n.body] Body text * @property {string} [l10n.cancelLabel] * @property {string} [l10n.confirmLabel] */ /** * Register buttons for the task. * * @param {string} id * @param {string} text label * @param {function} clicked * @param {boolean} [visible=true] * @param {Object} [options] Options for button * @param {Object} [extras] Extra options * @param {ConfirmationDialog} [extras.confirmationDialog] Confirmation dialog */ self.addButton = function (id, text, clicked, visible, options, extras) { if (buttons[id]) { return self; // Already registered } if (sections.buttons === undefined) { // We have buttons, register wrapper register('buttons'); if (initialized) { insert(self.order, 'buttons', sections, $wrapper); } } extras = extras || {}; extras.confirmationDialog = extras.confirmationDialog || {}; options = options || {}; var confirmationDialog = self.addConfirmationDialogToButton(extras.confirmationDialog, clicked); /** * Handle button clicks through both mouse and keyboard * @private */ var handleButtonClick = function () { if (extras.confirmationDialog.enable && confirmationDialog) { // Show popups section if used if (!extras.confirmationDialog.$parentElement) { sections.popups.$element.removeClass('hidden'); } confirmationDialog.show($e.position().top); } else { clicked(); } }; buttons[id] = { isTruncated: false, text: text, isVisible: false }; // The button might be \n
\n
").click(function(){t.$mask.hide()}).appendTo(t.$container)),t.$mask.show(),t.$mask.find(".h5p-button-back").focus(),t.$mask},i.prototype.setDisabled=function(t){return t.attr("aria-disabled","true").attr("tabindex","-1")},i.prototype.isDisabled=function(t){return"true"===t.attr("aria-disabled")},i.prototype.removeDisabled=function(t){return t.removeAttr("aria-disabled").attr("tabindex","0")},i.prototype.hasUncompletedRequiredInteractions=function(t){var e=this;return(void 0!==t?e.getVisibleInteractionsAt(t):e.getVisibleInteractions()).some(function(t){return t.getRequiresCompletion()&&!t.hasFullScore()})},i.prototype.getVisibleInteractions=function(){return this.interactions.filter(function(t){return t.isVisible()})},i.prototype.getVisibleInteractionsAt=function(t){return this.interactions.filter(function(e){return e.visibleAt(t)})},i.prototype.showSolutions=function(){},i.prototype.getTitle=function(){return H5P.createTitle(this.contentData&&this.contentData.metadata&&this.contentData.metadata.title?this.contentData.metadata.title:"Interactive Video")},i.prototype.findNextInteractionToShow=function(t,e){for(var n=void 0,o=0;ot||i.from==t&&(void 0===e||o>e))&&(void 0===n||i.from0?n.hours+" "+e.hours+", ":"")+n.minutes+" "+e.minutes+", "+n.seconds+" "+e.seconds},i.secondsToMinutesAndHours=function(t){var e=Math.floor(t/60);return{seconds:Math.floor(t%60),minutes:e%60,hours:Math.floor(e/60)}};var S=function(t,e){e?t.setAttribute("tabindex","0"):t.removeAttribute("tabindex")},$=function(t,e){for(var n=0;n'+i[t]+""),c.popup.setAttribute("role","dialog");var $=d("h5p-chooser-close-button",l.ICON,i.close,g,"div","button");c.popup.appendChild($),c.control=d("h5p-control h5p-"+t,l.ICON,i[t],g,"div","button"),c.control.setAttribute("aria-haspopup","true"),c.overlayControl=d("h5p-minimal-button h5p-"+t,l.TEXT,i[t],g,"div","menuitem"),c.overlayControl.tabIndex="-1",c.overlayControl.classList.add("h5p-hide"),c.updateOptions(e)};c.prototype=Object.create(H5P.EventDispatcher.prototype),c.prototype.constructor=c;var u=function(t,e,n){var o=document.createElement(n||"div");return t&&(o.className=t),e&&(o.innerHTML=e),o},d=function(t,e,n,o,i,r){var s=u(t,e===l.TEXT?n:"",i);return s.tabIndex=0,s.setAttribute("role",r),e===l.ICON&&(s.title=n),s.addEventListener("click",function(t){o.call(s,t)},!1),s.addEventListener("keydown",function(t){32!==t.which&&13!==t.which||(t.preventDefault(),o.call(s,t))},!1),s};e.default=c},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createElement=e.toggleClass=e.toggleVisibility=e.show=e.hide=e.removeClass=e.addClass=e.classListContains=e.removeChild=e.querySelectorAll=e.nodeListToArray=e.querySelector=e.appendChild=e.toggleAttribute=e.attributeEquals=e.hasAttribute=e.removeAttribute=e.setAttribute=e.getAttribute=void 0;var o=n(1),i=e.getAttribute=(0,o.curry)(function(t,e){return e.getAttribute(t)}),r=e.setAttribute=(0,o.curry)(function(t,e,n){return n.setAttribute(t,e)}),s=(e.removeAttribute=(0,o.curry)(function(t,e){return e.removeAttribute(t)}),e.hasAttribute=(0,o.curry)(function(t,e){return e.hasAttribute(t)}),e.attributeEquals=(0,o.curry)(function(t,e,n){return n.getAttribute(t)===e}),e.toggleAttribute=(0,o.curry)(function(t,e){var n=i(t,e);r(t,(0,o.inverseBooleanString)(n),e)}),e.appendChild=(0,o.curry)(function(t,e){return t.appendChild(e)}),e.querySelector=(0,o.curry)(function(t,e){return e.querySelector(t)}),e.nodeListToArray=function(t){return Array.prototype.slice.call(t)}),a=(e.querySelectorAll=(0,o.curry)(function(t,e){return s(e.querySelectorAll(t))}),e.removeChild=(0,o.curry)(function(t,e){return t.removeChild(e)}),e.classListContains=(0,o.curry)(function(t,e){return e.classList.contains(t)}),e.addClass=(0,o.curry)(function(t,e){return e.classList.add(t)})),l=e.removeClass=(0,o.curry)(function(t,e){return e.classList.remove(t)}),c=e.hide=a("hidden"),u=e.show=l("hidden");e.toggleVisibility=(0,o.curry)(function(t,e){return(t?u:c)(e)}),e.toggleClass=(0,o.curry)(function(t,e,n){n.classList[e?"add":"remove"](t)}),e.createElement=function(t){var e=t.tag,n=t.id,o=t.classes,i=t.attributes,r=document.createElement(e);return n&&(r.id=n),o&&o.forEach(function(t){r.classList.add(t)}),i&&Object.keys(i).forEach(function(t){r.setAttribute(t,i[t])}),r}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});e.Eventful=function(){return{listeners:{},on:function(t,e,n){var o={listener:e,scope:n};return this.listeners[t]=this.listeners[t]||[],this.listeners[t].push(o),this},fire:function(t,e){return(this.listeners[t]||[]).every(function(t){return!1!==t.listener.call(t.scope||this,e)})},propagate:function(t,e){var n=this;t.forEach(function(t){return e.on(t,function(e){return n.fire(t,e)})})}}}},function(t,e,n){"use strict";function o(t,e,n){var o=this;H5P.EventDispatcher.call(o);var h,f,v,m,b=t.action;n&&(b.userDatas={state:n});var g,y,k,S,$=b.library.split(" ")[0],w=e.l10n[d($,t)?"content":"interaction"],C="H5P.Nil"!==$&&"button"===t.displayType,T=[b.params.contentName,C?a(t.label):"",t.libraryTitle].filter(s)[0],x=!1,P=!1,E=!1,I=t.action.metadata;this.on("open-dialog",function(){L()}),this.on("show-mask",function(){F(this.getElement())});var A=function(){return r.extend({},{backgroundColor:"rgb(255,255,255)",boxShadow:!0},t.visuals)},B=function(n){h=r("
",{tabindex:0,role:"button",class:"h5p-interaction "+g+(n?"":" h5p-hidden"),"aria-popup":"true","aria-expanded":"false","aria-label":T,css:{left:t.x+"%",top:t.y+"%",width:"",height:""},on:{click:function(){o.dialogDisabled||(L(),h.attr("aria-expanded","true"))},keydown:function(t){13!==t.which&&32!==t.which||o.dialogDisabled||(L(),h.attr("aria-expanded","true"),t.preventDefault())}}}),o.getRequiresCompletion()&&void 0===e.editor&&e.currentState!==H5P.InteractiveVideo.SEEKING&&L(!0),r("
",{class:"h5p-touch-area"}).appendTo(h),r("
",{class:"h5p-interaction-button"}).appendTo(h),e.editor&&h.hover(function(){h.is(".focused")||h.is(":focus")||e.dnb&&(!e.dnb||e.dnb.newElement)?(e.editor.hideInteractionTitle(),x=!1):(e.editor.showInteractionTitle(T,h),x=!0)},function(){e.editor.hideInteractionTitle(),x=!1}).focus(function(){e.editor.hideInteractionTitle(),x=!1}).click(function(){e.editor.hideInteractionTitle()});var i=s(a(t.label));t.label&&i&&(f=M(t.label,"h5p-interaction").appendTo(h)),o.trigger("display",h),setTimeout(function(){h&&h.removeClass("h5p-hidden")},0)},M=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return r("
",{class:"h5p-interaction-label "+e,html:'
'+t+"
"})},H=function(){return h=M(t.label,"h5p-interaction h5p-interaction-label-standalone"),h.css({left:t.x+"%",top:t.y+"%",width:"",height:"initial"}),o.trigger("display",h),setTimeout(function(){h&&h.removeClass("h5p-hidden")},0),h},D=function(e){if("timecode"===t.goto.type)e.click(function(e){1===e.which&&W({data:t.goto.time})}).keypress(function(e){32===e.which&&W({data:t.goto.time})}).attr("href","#").attr("tabindex","0");else{var n=t.goto.url;e.keypress(function(t){32===t.which&&this.click()}).attr({href:("other"!==n.protocol?n.protocol:"")+n.url,target:"_blank"})}return e.addClass("goto-clickable")},R=function(t){var n=!e.hasUncompletedRequiredInteractions(t);y&&y.trigger("hide"),h&&o.trigger("hide",h),o.isButton()?n&&e.dnb.dialog.close():(e.isMobileView&&n&&e.dnb.dialog.close(),h&&h.detach()),o.trigger("remove",h),n&&K(h)},O=function(){var t=document.createElement("button");return t.innerHTML=e.l10n.continueWithVideo,t.className="h5p-interaction-continue-button",t.addEventListener("click",function(){R(),e.play()}),t},V=function(t){if("H5P.Questionnaire"===$){if(t.find(".h5p-interaction-continue-button").length)return;var n=O(),o=t.find(".h5p-questionnaire-success-center");o.length&&o.get(0).appendChild(n),y.on("noSuccessScreen",function(){R(),e.play()})}"H5P.FreeTextQuestion"===$&&y.on("continue",function(){R(),e.play()})},q=function(t,e){var n=function(t){return e.target===t.get(0)},o=9===e.which,i=t.first(),r=t.last();o&&e.shiftKey&&n(i)?(r.focus(),e.preventDefault()):o&&n(r)&&(i.focus(),e.preventDefault())},L=function(n){var i=e.$container.find(".h5p-dialog-wrapper");"function"==typeof y.setActivityStarted&&"function"==typeof y.getScore&&y.setActivityStarted();var s=o.isGotoClickable(),a=i.find("[tabindex]"),u=r(s?"
":"
",{class:"h5p-dialog-interaction h5p-frame"});!s&&p($)&&u.attr("tabindex","0"),void 0!==e.editor?u.attr("tabindex",-1):0===a.length&&u.attr("tabindex",0),o.getRequiresCompletion()&&i.keydown(function(t){var e=i.find('[tabindex="0"], button, input').filter(":visible");q(e,t)});var d=s?D(u):u;if(y.attach(d),V(d),X(y)&&(o.score=y.getScore(),o.maxScore=y.getMaxScore()),o.hasFullScore()&&n){if(i.hasClass("h5p-hidden"))return}else o.getRequiresCompletion()&&!o.hasFullScore()&&(e.dnb.dialog.hideCloseButton(),e.dnb.dialog.disableOverlay=!0,i.click(function(){if(!o.hasFullScore()){e.showWarningMask().find(".h5p-button-back").click(function(){return u.find("button").last().focus()})}}));e.dnb.dialog.open(u),e.disableTabIndexes(),e.dnb.dialog.addLibraryClass($),e.dnb.dialog.toggleClass("goto-clickable-visualize",!(!s||!t.goto.visualize)),e.dnb.dialog.toggleClass("h5p-goto-timecode",c(t,l.TIME_CODE)),e.dnb.dialog.disableOverlay&&e.restorePosterTabIndexes();var f=function(){e.dnb.$dialogContainer.one("transitionend",function(){if(u.is(".h5p-image")){u.find("img").css({width:"",height:""})}});try{void 0!==y.pause&&(y.pause instanceof Function||"function"==typeof y.pause)&&y.pause()}catch(t){H5P.error(t)}h&&(h.focus(),h.attr("aria-expanded","false")),y&&y.trigger("hide")};e.dnb.dialog.once("close",f);var v=function t(){o.dialogWidth=e.dnb.dialog.getDialogWidth(),e.dnb.dialog.off("close",t)};if(e.dnb.dialog.on("close",v),"H5P.Image"===$){var m=e.dnb.dialog.getMaxSize(h),g=u.find("img");b.params.file.width&&b.params.file.height?N(g,m,{width:b.params.file.width,height:b.params.file.height},!e.isMobileView):(g.on("load",function(){g.is(":visible")&&N(g,m,{width:this.width,height:this.height},!e.isMobileView)}),e.dnb.dialog.position(h))}else e.isMobileView||("H5P.FreeTextQuestion"===$?e.dnb.dialog.position(h,{width:o.dialogWidth/16},"big"):"H5P.Text"!==$&&"H5P.Table"!==$?e.dnb.dialog.position(h,{width:o.dialogWidth/16},"medium"):e.dnb.dialog.position(h,{width:o.dialogWidth/16},null));if("H5P.Summary"===$){var k=0;H5P.on(y,"resize",function(){var t=u.height();(k>t+10||kn.height&&(o.width=o.width*n.height/o.height,o.height=n.height),o.width>n.width&&(o.height=o.height*n.width/o.width,o.width=n.width);var r=16/Number(t.css("fontSize").replace("px",""));t.css({width:o.width*r+"em",height:o.height*r+"em"}),i&&e.dnb.dialog.position(h,o)},W=function(n){o.isButton()&&e.dnb.dialog.close(),e.currentState!==H5P.Video.PAUSED&&e.currentState!==H5P.Video.ENDED||e.play();var i=n.data;i===t.duration.from&&(i+=.2),e.seek(i)},z=function(){var n=t.height||10,o=t.width||10,i=e.width/e.fontSize;return{height:n/(i/(e.$videoWrapper.width()/e.$videoWrapper.height()))*100+"%",width:o/i*100+"%"}},F=function(t){t.css("zIndex",52),e.showOverlayMask()},K=function(t){t&&t.css("zIndex",""),e.hideOverlayMask()},_=function(){var n=o.isGotoClickable(),i=z(),s=A();if(h=r("
",{"aria-label":e.l10n.interaction,tabindex:"-1",class:"h5p-interaction h5p-poster "+g+(n&&t.goto.visualize?" goto-clickable-visualize":""),css:{left:t.x+"%",top:t.y+"%",width:i.width,height:i.height}}),"H5P.IVHotspot"!==$){h.css("background",s.backgroundColor);var a=s.backgroundColor.split(",");if(a[3]){0===parseFloat(a[3].replace(")",""))&&h.addClass("h5p-transparent-interaction")}}!1===s.boxShadow&&h.addClass("h5p-box-shadow-disabled"),"H5P.Link"===$&&(h.css("height","auto"),h.css("width","auto"),void 0===e.editor&&h.click(function(){return window.open(y.getUrl()),e.pause(),!1})),m=r("
",{class:"h5p-interaction-outer"}).appendTo(h),v=r(n?"":"
",{class:"h5p-interaction-inner h5p-frame"}).appendTo(m),!n&&p($)&&v.attr("tabindex","0"),void 0!==e.editor&&y.disableAutoPlay&&y.disableAutoPlay();var l=n?D(v):v;y.attach(l),V(l),o.trigger("display",h),o.getRequiresCompletion()&&e.currentState!==H5P.InteractiveVideo.SEEKING&&void 0===e.editor&&!o.hasFullScore()&&(F(h),h.focus()),setTimeout(function(){H5P.trigger(y,"resize")},0),"function"==typeof y.setActivityStarted&&"function"==typeof y.getScore&&y.setActivityStarted()},j=function(){var n,i,r=!0;if(t.adaptivity&&(i=o.hasFullScore(),r=!o.getRequiresCompletion()||i,i?n=t.adaptivity.correct:i||(n=t.adaptivity.wrong)),!n||void 0===n.seekTo)return void(void 0!==y.hasButton&&(y.hasButton("iv-continue")||y.addButton("iv-continue",e.l10n.defaultAdaptivitySeekLabel,function(){R(),U()}),y[r?"showButton":"hideButton"]("iv-continue")));e.pause(),!n.allowOptOut&&h&&(o.isButton()?(e.dnb.dialog.disableOverlay=!0,e.dnb.dialog.hideCloseButton()):F(h));var s=i?"correct":"wrong",a=n.seekLabel?n.seekLabel:e.l10n.defaultAdaptivitySeekLabel;y.hideButton("iv-continue").addButton("iv-adaptivity-"+s,a,function(){R(n.seekTo),!i&&y.resetTask&&(y.resetTask(),y.hideButton("iv-adaptivity-"+s)),o.remove(),U(n.seekTo)}).showButton("iv-adaptivity-"+s,1).hideButton("iv-adaptivity-"+(i?"wrong":"correct"),1).hideButton("check-answer",1).hideButton("show-solution",1).hideButton("try-again",1),void 0!==y.disableInput&&(y.disableInput instanceof Function||"function"==typeof y.disableInput)&&y.disableInput(),setTimeout(function(){var t=n.message.replace("

","").replace("

","");y.updateFeedbackContent(t,!0),y.read(t)},0)},U=function(t){var n=G(),o=n.filter(function(t){return!t.isButton()});if(o.length?n=o:n.length&&e.$container.find(".h5p-dialog-wrapper .h5p-dialog").show(),n.length){var i=n[0];return i.isButton()?i.trigger("open-dialog"):i.trigger("show-mask"),void e.pause()}e.currentState!==H5P.Video.ENDED&&(void 0!==t&&(e.pause(),e.seek(t)),e.play(),e.controls.$play.focus())},G=function(){return e.getVisibleInteractions().filter(function(t){return t!==o}).filter(function(t){return t.getRequiresCompletion()&&!t.hasFullScore()})},Q=function(){var e=t.className;if(void 0===e){var n=b.library.split(" ")[0].toLowerCase().split(".");e=n[0]+"-"+n[1]+"-interaction"}return t.goto&&"timecode"===t.goto.type&&(e+=" h5p-goto-timecode"),e};o.isGotoClickable=function(){return-1!==["H5P.Text","H5P.Image"].indexOf($)&&t.goto&&-1!==["timecode","url"].indexOf(t.goto.type)},o.getCurrentState=function(){if(y&&(y.getCurrentState instanceof Function||"function"==typeof y.getCurrentState))return y.getCurrentState()},o.getDuration=function(){return{from:t.duration.from,to:t.duration.to+1}},o.getRequiresCompletion=function(){return!!t.adaptivity&&!!t.adaptivity.requireCompletion},o.pause=function(){return t.pause},o.isButton=function(){return"button"===t.displayType||!(!e.isMobileView||"H5P.IVHotspot"===$)&&("H5P.Image"!==$||!1!==t.buttonOnMobile)},o.isStandaloneLabel=function(){return"H5P.Nil"===$},o.isMainSummary=function(){return!0===t.mainSummary},o.selectDot=function(){e.preventSkipping||(e.seekingTo=!0,Math.floor(10*e.video.getCurrentTime())!==Math.floor(10*t.duration.from)&&(e.currentState===H5P.Video.VIDEO_CUED?(e.play(),e.seek(t.duration.from)):e.currentState===H5P.Video.PLAYING?e.seek(t.duration.from):(e.play(),e.seek(t.duration.from),e.pause())))},o.addDot=function(){if("H5P.Nil"===$)return r("
",{class:n});var n="h5p-seekbar-interaction "+g,i=r("
",{role:"menuitem",class:n,"aria-label":w+". "+T,title:T,css:{left:t.duration.from*e.oneSecondInPercentage+"%"},on:{click:o.selectDot,keydown:function(t){if(13===t.which||32===t.which)return o.selectDot(),!1}}});return e.preventSkipping&&i.attr("aria-disabled","true").attr("tabindex","-1"),o.$menuitem=i,i},o.isVisible=function(){return E},o.visibleAt=function(e){return!(e=t.duration.to+1)},o.toggle=function(n,i){if(!o.visibleAt(n))return E=!1,void(h&&(S&&(S.hideContextMenu(),S===e.dnb.focusedElement&&(S.blur(),delete e.dnb.focusedElement)),e.editor&&x&&(e.editor.hideInteractionTitle(),x=!1),o.remove()));if(!h)return E=!0,o.isStandaloneLabel()?H():o.isButton()?B(i):_(),void 0===e.editor?S=e.dnb.add(h,void 0,{dnbElement:S,disableContextMenu:!0}):(o.fit&&(e.editor.fit(h,t),o.fit=!1),h.focus(function(){e.pause()})),h},o.setTitle=function(t){h&&h.attr("aria-label",t),T=t},o.reCreateInteraction=function(){"H5P.IVHotspot"!==$&&h&&(y&&y.trigger("hide"),o.trigger("hide",h),h.detach(),o.isStandaloneLabel()?H():o.isButton()?B(!0):_())},o.resizeInteraction=function(){o.isStandaloneLabel()||H5P.trigger(y,"resize")},o.positionLabel=function(t){h&&o.isButton()&&f&&!o.isStandaloneLabel()&&(f.removeClass("h5p-left-label"),parseInt(h.css("left"))+f.position().left+f.outerWidth()>t&&f.addClass("h5p-left-label"))},o.setPosition=function(e,n){t.x=e,t.y=n,h.css({left:e+"%",top:n+"%"})},o.setSize=function(e,n){e&&(t.width=e),n&&(t.height=n),H5P.trigger(y,"resize")},o.remove=function(){h&&(o.trigger("domHidden",{$dom:h,key:"videoProgressedPast"},{bubbles:!0,external:!0}),y&&y.trigger("hide"),o.trigger("hide",h),h.detach(),h=void 0)},o.reCreate=function(){g=Q(),o.isStandaloneLabel()||(b.params=b.params||{},y=H5P.newRunnable(b,e.contentId,void 0,void 0,{parent:e,editing:void 0!==e.editor}),void 0===o.maxScore&&y.getMaxScore&&(o.maxScore=y.getMaxScore()),b.userDatas&&X(y)&&(o.score=y.getScore()),e.isTask||void 0===e.options.assets.endscreens||(y.isTask||void 0===y.isTask&&void 0!==y.showSolutions)&&(e.isTask=!0),y.on&&(y.on("xAPI",function(t){var n=t.getVerifiedStatementValue(["context","contextActivities","parent"])||[],i=t.getContentXAPIId(e),r="completed"===t.getVerb()||"answered"===t.getVerb(),s=n.some(function(t){return t.id===i});y.getScore&&(o.score=y.getScore()),y.getMaxScore&&(o.maxScore=y.getMaxScore()),s&&r&&t.getMaxScore()&&(o.score=null==t.getScore()?0:t.getScore(),o.maxScore=o.maxScore?o.maxScore:t.getMaxScore(),j()),o.setLastXAPIVerb(t.getVerb()),o.trigger(t)}),y.on("question-finished",function(){j()}),y.on("resize",function(){delete o.dialogWidth,e&&e.dnb&&e.dnb.dialog.removeStaticWidth()}),"H5P.IVHotspot"===$&&y.on("goto",W),"H5P.GoToQuestion"===$&&y.on("chosen",W)))};var X=function(t){return"undefined"!==(void 0===t?"undefined":i(t))&&"function"==typeof t.getScore&&"function"==typeof t.getMaxScore};o.setDnbElement=function(t){return S!==t&&(S=t,!0)},o.hasFullScore=function(){return o.score>=o.maxScore},o.getLibraryName=function(){return $},o.getMetadata=function(){return I},o.getTitle=function(){return T},o.isAnswerable=function(){return-1===u.indexOf(o.getLibraryName())&&!o.isStandaloneLabel()},o.setProgress=function(t){this.progress=t},o.getProgress=function(){return this.progress},o.setLastXAPIVerb=function(t){k=t},o.getLastXAPIVerb=function(){return k},o.getClass=function(){return g},o.getCopyrights=function(){if(!o.isStandaloneLabel()){var n=H5P.newRunnable(b,e.contentId);if(void 0!==n){var i=new H5P.ContentCopyrights;return i.addContent(H5P.getCopyrights(n,t,e.contentId)),i.setLabel(T+" "+H5P.InteractiveVideo.humanizeTime(t.duration.from)+" - "+H5P.InteractiveVideo.humanizeTime(t.duration.to)),i}}},o.getXAPIData=function(){if(y&&(y.getXAPIData instanceof Function||"function"==typeof y.getXAPIData))return y.getXAPIData()},o.getSubcontentId=function(){return b.subContentId},o.getElement=function(){return h},o.focusOnFirstTabbableElement=function(){if(h){var t=r(h.get(0)).find("[tabindex]");t&&t.length?t.get(0).focus():o.focus()}},o.focus=function(){h&&h.focus()},o.getClipboardData=function(){return H5P.DragNBar.clipboardify(H5PEditor.InteractiveVideo.clipboardKey,t,"action")},o.repositionToWrapper=function(e){if(h&&"H5P.IVHotspot"!==$&&"H5P.FreeTextQuestion"!==$){if(P&&(h.css({top:t.y+"%",left:t.x+"%"}),h.css(o.isButton()?{height:"",width:""}:z()),P=!1),h.position().top+h.height()>e.height()){var n=(e.height()-h.height())/e.height()*100;if(n<0){n=0;var i=e.height()/parseFloat(h.css("font-size"));h.css("height",i+"em")}h.css("top",n+"%"),P=!0}if(h.position().left+h.width()>e.width()){var r=(e.width()-h.width())/e.width()*100;if(r<0){r=0;var s=e.width()/parseFloat(h.css("font-size"));h.css("width",s+"em")}h.css("left",r+"%"),P=!0}}},o.resetTask=function(){void 0!==b.userDatas&&void 0!==b.userDatas.state&&delete b.userDatas.state,delete o.score,delete o.maxScore,o.reCreate()},o.getInstance=function(){return y},o.reCreate()}Object.defineProperty(e,"__esModule",{value:!0});var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r=H5P.jQuery,s=function(t){return void 0!==t&&"string"==typeof t&&t.length>0},a=function(t){return s(t)?r("
"+t+"
").text():void 0},l={TIME_CODE:"timecode",URL:"url"},c=function(t,e){return void 0!==t.goto&&t.goto.type===e},u=["H5P.Image","H5P.Nil","H5P.Table","H5P.Link","H5P.GoToQuestion","H5P.IVHotspot","H5P.Text"],d=function(t,e){return-1!==u.indexOf(t)||c(e,l.TIME_CODE)},h=["H5P.Text","H5P.Table"],p=function(t){return-1!==h.indexOf(t)};o.prototype=Object.create(H5P.EventDispatcher.prototype),o.prototype.constructor=o,o.PROGRESS_INTERACTED=0,o.PROGRESS_ANSWERED=1,e.default=o},function(t,e,n){"use strict";function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var i=function(){function t(t,e){for(var n=0;n0&&(this.interactionsAnnouncer.textContent="",this.interactionsAnnouncer.textContent="\n "+this.getAnnouncementMessage(t.length)+"\n "+this.getTitleAnnouncement(t.length,t[0])+"\n "+this.getPauseAnnouncement(t))}},{key:"getAnnouncementMessage",value:function(t){return 0===t?"":1===t?this.l10n.singleInteractionAnnouncement:this.l10n.multipleInteractionsAnnouncement}},{key:"getTitleAnnouncement",value:function(t,e){return 1===t?e.getTitle():""}},{key:"getPauseAnnouncement",value:function(t){return t.some(function(t){return t.pause()})?". "+this.l10n.videoPausedAnnouncement:""}}]),t}();e.default=r},function(t,e,n){"use strict";function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var i=function(){function t(t,e){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{content:"",maxWidth:"auto",style:"h5p-interactive-video-bubble",mode:"centered",focus:function(){}};o(this,t),this.$reference=e,this.maxWidth=n.maxWidth,this.style=n.style,this.mode=n.mode,this.focus=n.focus,this.$tail=r("
",{class:this.style+"-tail"}),this.$innerTail=r("
",{class:this.style+"-inner-tail"}),this.$content=r("
",{class:this.style+"-text"}),"string"==typeof n.content?this.$content.html(n.content):this.$content.append(n.content),this.$innerBubble=r("
",{class:this.style+"-inner"}).append(this.$content).prepend(this.$innerTail),this.$h5pContainer=this.$reference.closest(".h5p-interactive-video"),this.$bubble=r("
",{class:this.style,"aria-live":"polite"}).append([this.$tail,this.$innerBubble]).addClass(this.style+"-inactive").appendTo(this.$h5pContainer),s&&H5P.$body.css("cursor","pointer"),"centered"===this.mode&&this.$bubble.css({width:"auto"===this.maxWidth?"auto":this.maxWidth+"px"}),this.update()}return i(t,[{key:"update",value:function(){var t=this,e=this.getOffsetBetween(this.$h5pContainer,this.$reference),n="full"===this.mode?this.$bubble.outerWidth():Math.min(.9*e.outerWidth,"auto"===this.maxWidth?this.$bubble.outerWidth():this.maxWidth),o=this.getBubblePosition(n,e,this.mode);"centered"===this.mode&&this.$bubble.css({bottom:void 0===o.bottom?void 0:o.bottom+"px",left:o.left+"px"}),setTimeout(function(){var e=t.getTailPosition(t.$reference),n={bottom:e.bottom+"px",left:"string"==typeof e.left?e.left:e.left+"px"};t.$tail.css(n),t.$innerTail.css(n)},75)}},{key:"animate",value:function(){var t=this;this.$bubble.hasClass(this.style+"-inactive")&&(this.$bubble.removeClass(this.style+"-inactive").addClass(this.style+"-active"),setTimeout(function(){t.$bubble.removeClass(t.style+"-active").addClass(t.style+"-inactive")},2e3))}},{key:"setContent",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";this.$content.html(t),this.update()}},{key:"getContent",value:function(){return this.$content.get(0).outerHTML}},{key:"isActive",value:function(){return this.$bubble.hasClass(this.style+"-active")}},{key:"toggle",value:function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];t=void 0===t?!this.isActive():t,t&&n?(setTimeout(function(){e.$bubble.removeClass(e.style+"-preparing").addClass(e.style+"-active"),setTimeout(function(){return e.focus()},400)},100),this.$bubble.removeClass(this.style+"-inactive").addClass(this.style+"-preparing")):this.$bubble.toggleClass(this.style+"-inactive",!t).toggleClass(this.style+"-active",t),this.update()}},{key:"getBubblePosition",value:function(t,e,n){var o=t/2;return{bottom:"full"===n?void 0:e.bottom+e.innerHeight+4,left:"full"===n?(e.outerWidth-t)/2:e.left-o+16}}},{key:"getTailPosition",value:function(t){return{left:t.offset().left-this.$tail.parent().offset().left+8,top:-6,bottom:-6}}},{key:"getOffsetBetween",value:function(t,e){var n=t[0].getBoundingClientRect(),o=e[0].getBoundingClientRect();return{top:o.top-n.top,right:n.right-o.right,bottom:n.bottom-o.bottom,left:o.left-n.left+parseInt(e.css("marginLeft")),innerWidth:o.width,innerHeight:o.height,outerWidth:n.width,outerHeight:n.height}}},{key:"fullscreen",value:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,o=this.isMobilePhone(),i=t&&!o&&void 0!==e&&void 0!==n,r={maxHeight:"",top:""};i&&(r.maxHeight="calc("+n+"px - 1em - 9px)",r.top="calc((("+(e-n)+"px + 1em) / 2) - 9px)"),this.$bubble.toggleClass("mobile-fullscreen",o&&t),this.$bubble.css(r)}},{key:"isMobilePhone",value:function(){return/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(navigator.userAgent.substr(0,4))}}]),t}();e.default=a},function(t,e,n){"use strict";function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function r(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(e,"__esModule",{value:!0});var s=function(){function t(t,e){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{};o(this,e);var r=i(this,(e.__proto__||Object.getPrototypeOf(e)).call(this));return r.parent=t,r.l10n=l.extend({title:"@answered Questions answered",information:"You have answered @answered questions, click below to submit your answers.",informationNoAnswers:"You have not answered any questions.",informationMustHaveAnswer:"You have to answer at least one question before you can submit your answers.",submitButton:"Submit Answers",submitMessage:"Your answers have been submitted!",tableRowAnswered:"Answered questions",tableRowScore:"Score",answeredScore:"answered",tableRowSummaryWithScore:"You got @score out of @total points for the @question that appeared after @minutes minutes and @seconds seconds.",tableRowSummaryWithoutScore:"You have answered the @question that appeared after @minutes minutes and @seconds seconds."},n.l10n),r.buildDOM(),r}return r(e,t),s(e,[{key:"buildDOM",value:function(){var t=this;this.$endscreenIntroductionTitleText=l("
",{class:c+"-introduction-title-text",id:c+"-introduction-title-text"}),this.$closeButton=l("
",{role:"button",class:c+"-close-button",tabindex:"0","aria-label":this.parent.l10n.close}),(0,a.onClick)(this.$closeButton,function(){return t.parent.toggleEndscreen(!1)});var e=l("
",{class:c+"-introduction-title"}).append([this.$endscreenIntroductionTitleText,this.$closeButton]);this.$endscreenIntroductionText=l("
",{class:c+"-introduction-text",id:c+"-introduction-text"}),this.$submitButtonContainer=l("
",{class:c+"-submit-button-container "+u}),this.$submitButton=H5P.JoubelUI.createButton({class:c+"-submit-button",html:this.l10n.submitButton,appendTo:this.$submitButtonContainer,click:function(){return t.handleSubmit()}}),this.$endscreenOverviewTitle=l("
",{class:c+"-overview-title"}).append(l("
",{class:c+"-overview-title-answered-questions",html:this.l10n.tableRowAnswered})).append(l("
",{class:c+"-overview-title-score",html:this.l10n.tableRowScore})),this.$endscreenBottomTable=l("
",{class:c+"-overview-table"}),this.$endscreen=l("
",{class:c,role:"dialog","aria-labelledby":c+"-introduction-title-text","aria-describedby":c+"-introduction-text"}).append(l("
",{class:c+"-introduction"}).append(l("
",{class:c+"-star-symbol"})).append(l("
",{class:c+"-introduction-container"}).append([e,this.$endscreenIntroductionText,this.$submitButtonContainer]))).append(l("
",{class:c+"-overview"}).append(this.$endscreenOverviewTitle).append(this.$endscreenBottomTable))}},{key:"handleSubmit",value:function(){var t=this;this.$submitButtonContainer.hasClass(u)||(this.$submitButtonContainer.addClass(u),this.$endscreenIntroductionText.html('"),this.answered.forEach(function(e){if("completed"!==e.getLastXAPIVerb()&&"answered"!==e.getLastXAPIVerb()){var n=new H5P.XAPIEvent;n.data.statement=e.getXAPIData().statement,e.setLastXAPIVerb(n.getVerb()),t.trigger(n)}}),this.parent.triggerXAPIScored(this.parent.getUsersScore(),this.parent.getUsersMaxScore(),"completed"))}},{key:"getDOM",value:function(){return this.$endscreen}},{key:"buildTableRow",value:function(t,e,n,o){var i=this,r=d(n)&&d(o),s=r?this.l10n.tableRowSummaryWithScore:this.l10n.tableRowSummaryWithoutScore,u=this.parent.skippingPrevented()?" "+c+"-no-link":"",h=l("
",{class:c+"-overview-table-row"+u,role:"row",tabIndex:0,"aria-label":s.replace("@score",n).replace("@total",o).replace("@question",e).replace("@minutes",Math.floor(t/60)).replace("@seconds",t%60)});return(0,a.onClick)(h,function(){return i.jump(t)}),l("
",{class:c+"-overview-table-row-time",html:H5P.InteractiveVideo.humanizeTime(t),appendTo:h,"aria-hidden":!0}),l("
",{class:c+"-overview-table-row-title",html:e,appendTo:h,"aria-hidden":!0}),l("
",{class:c+"-overview-table-row-score",html:r?n+" / "+o:this.l10n.answeredScore,appendTo:h,"aria-hidden":!0}),h}},{key:"jump",value:function(t){this.parent.skippingPrevented()||(this.parent.seek(t),this.parent.toggleEndscreen(!1))}},{key:"update",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.answered=e.filter(function(t){return void 0!==t.getProgress()}).sort(function(t,e){return t.getDuration().from-e.getDuration().from}),this.$endscreenBottomTable.empty(),this.answered.forEach(function(e){var n=e.getDuration().from,o=t.getDescription(e),i=e.getInstance(),r=i.getScore?i.getScore():void 0,s=i.getMaxScore?i.getMaxScore():void 0;t.$endscreenBottomTable.append(t.buildTableRow(n,o,r,s))});var n=this.answered.length;this.$endscreenIntroductionTitleText.html(this.l10n.title.replace("@answered",n)),0===n?this.$endscreenIntroductionText.html('
'+this.l10n.informationNoAnswers+"
"+this.l10n.informationMustHaveAnswer+"
"):this.$endscreenIntroductionText.html(this.l10n.information.replace("@answered",n)),n>0&&this.$submitButtonContainer.removeClass(u)}},{key:"getDescription",value:function(t){return"function"==typeof t.getInstance&&"function"==typeof t.getInstance().getTitle?t.getInstance().getTitle():t.getTitle()}},{key:"focus",value:function(){this.$submitButtonContainer.hasClass(u)?this.$closeButton.focus():this.$submitButton.focus()}}]),e}(H5P.EventDispatcher);e.default=h},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.onClick=void 0;var o=n(3);e.onClick=function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=n.ignoreKeyboard,r=void 0!==i&&i,s=n.preventDefaultForKeys,a=void 0===s||s,l=function(t){"click"!==t.type&&a&&t.preventDefault(),e(t)};t.click(l),!0!==r&&(0,o.onKey)(t,[{key:o.Keys.SPACE},{key:o.Keys.ENTER}],l)}}]);;