2014-10-01 10:05:04 +02:00
/*jshint multistr: true */
2014-06-19 16:09:23 +02:00
// TODO: Should we split up the generic parts needed by the editor(and others), and the parts needed to "run" H5Ps?
2015-05-05 13:13:57 +02:00
/** @namespace */
2018-05-03 10:27:06 +02:00
var H5P = window . H5P = window . H5P || { } ;
2013-01-17 09:01:43 +01:00
2015-05-05 13:13:57 +02:00
/ * *
* Tells us if we ' re inside of an iframe .
* @ member { boolean }
* /
2015-09-10 14:27:12 +02:00
H5P . isFramed = ( window . self !== window . parent ) ;
2014-03-26 08:43:29 +01:00
2015-05-05 13:13:57 +02:00
/ * *
* jQuery instance of current window .
* @ member { H5P . jQuery }
* /
2014-03-26 08:43:29 +01:00
H5P . $window = H5P . jQuery ( window ) ;
2015-05-05 13:13:57 +02:00
/ * *
* List over H5P instances on the current page .
* @ member { Array }
* /
2014-10-13 22:19:59 +02:00
H5P . instances = [ ] ;
2014-03-26 08:43:29 +01:00
// Detect if we support fullscreen, and what prefix to use.
2021-05-12 12:10:51 +02:00
if ( document . documentElement . requestFullscreen ) {
2015-05-05 13:13:57 +02:00
/ * *
* Browser prefix to use when entering fullscreen mode .
* undefined means no fullscreen support .
* @ member { string }
* /
2014-03-26 08:43:29 +01:00
H5P . fullScreenBrowserPrefix = '' ;
}
2014-10-23 15:00:37 +02:00
else if ( document . documentElement . webkitRequestFullScreen ) {
2016-12-12 11:22:34 +01:00
H5P . safariBrowser = navigator . userAgent . match ( /version\/([.\d]+)/i ) ;
2014-05-22 11:17:30 +02:00
H5P . safariBrowser = ( H5P . safariBrowser === null ? 0 : parseInt ( H5P . safariBrowser [ 1 ] ) ) ;
2014-10-01 10:05:04 +02:00
2014-05-22 11:17:30 +02:00
// Do not allow fullscreen for safari < 7.
if ( H5P . safariBrowser === 0 || H5P . safariBrowser > 6 ) {
H5P . fullScreenBrowserPrefix = 'webkit' ;
}
2014-03-26 08:43:29 +01:00
}
else if ( document . documentElement . mozRequestFullScreen ) {
H5P . fullScreenBrowserPrefix = 'moz' ;
}
else if ( document . documentElement . msRequestFullscreen ) {
H5P . fullScreenBrowserPrefix = 'ms' ;
}
2013-11-08 18:16:41 +01:00
2014-10-23 14:02:34 +02:00
/ * *
* Keep track of when the H5Ps where started .
*
2015-05-05 13:13:57 +02:00
* @ type { Object [ ] }
2014-10-23 14:02:34 +02:00
* /
2014-10-15 11:38:29 +02:00
H5P . opened = { } ;
2013-11-26 14:54:29 +01:00
/ * *
* Initialize H5P content .
* Scans for ".h5p-content" in the document and initializes H5P instances where found .
2015-05-05 13:13:57 +02:00
*
* @ param { Object } target DOM Element
2013-11-26 14:54:29 +01:00
* /
2015-03-10 10:09:31 +01:00
H5P . init = function ( target ) {
2014-03-26 08:43:29 +01:00
// Useful jQuery object.
2015-03-10 10:09:31 +01:00
if ( H5P . $body === undefined ) {
H5P . $body = H5P . jQuery ( document . body ) ;
}
2013-04-26 17:27:35 +02:00
2015-03-13 12:51:31 +01:00
// Determine if we can use full screen
2016-11-11 11:57:54 +01:00
if ( H5P . fullscreenSupported === undefined ) {
2015-05-05 13:13:57 +02:00
/ * *
* Use this variable to check if fullscreen is supported . Fullscreen can be
* restricted when embedding since not all browsers support the native
* fullscreen , and the semi - fullscreen solution doesn ' t work when embedded .
* @ type { boolean }
* /
2019-03-07 12:57:45 +01:00
H5P . fullscreenSupported = ! H5PIntegration . fullscreenDisabled && ! H5P . fullscreenDisabled && ( ! ( H5P . isFramed && H5P . externalEmbed !== false ) || ! ! ( document . fullscreenEnabled || document . webkitFullscreenEnabled || document . mozFullScreenEnabled ) ) ;
2019-01-14 15:26:27 +01:00
// -We should consider document.msFullscreenEnabled when they get their
// -element sizing corrected. Ref. https://connect.microsoft.com/IE/feedback/details/838286/ie-11-incorrectly-reports-dom-element-sizes-in-fullscreen-mode-when-fullscreened-element-is-within-an-iframe
// Update: Seems to be no need as they've moved on to Webkit
2015-03-13 12:51:31 +01:00
}
2013-11-08 18:16:41 +01:00
2016-12-21 11:34:51 +01:00
// Deprecated variable, kept to maintain backwards compatability
2016-12-12 11:28:23 +01:00
if ( H5P . canHasFullScreen === undefined ) {
/ * *
* @ deprecated since version 1.11
* @ type { boolean }
* /
2016-12-14 16:21:42 +01:00
H5P . canHasFullScreen = H5P . fullscreenSupported ;
2016-12-12 11:28:23 +01:00
}
2014-03-26 08:43:29 +01:00
// H5Ps added in normal DIV.
2018-10-23 11:25:46 +02:00
H5P . jQuery ( '.h5p-content:not(.h5p-initialized)' , target ) . each ( function ( ) {
2015-03-10 10:09:31 +01:00
var $element = H5P . jQuery ( this ) . addClass ( 'h5p-initialized' ) ;
2014-03-26 08:43:29 +01:00
var $container = H5P . jQuery ( '<div class="h5p-container"></div>' ) . appendTo ( $element ) ;
var contentId = $element . data ( 'content-id' ) ;
2015-02-27 13:59:42 +01:00
var contentData = H5PIntegration . contents [ 'cid-' + contentId ] ;
2014-04-14 10:50:37 +02:00
if ( contentData === undefined ) {
return H5P . error ( 'No data for content id ' + contentId + '. Perhaps the library is gone?' ) ;
}
2014-03-26 08:43:29 +01:00
var library = {
library : contentData . library ,
2018-03-16 20:11:08 +01:00
params : JSON . parse ( contentData . jsonContent ) ,
metadata : contentData . metadata
2014-03-26 08:43:29 +01:00
} ;
2015-08-26 15:58:49 +02:00
H5P . getUserData ( contentId , 'state' , function ( err , previousState ) {
2015-04-07 19:32:44 +02:00
if ( previousState ) {
library . userDatas = {
state : previousState
} ;
}
2015-04-13 20:30:40 +02:00
else if ( previousState === null && H5PIntegration . saveFreq ) {
2015-04-07 19:32:44 +02:00
// Content has been reset. Display dialog.
delete contentData . contentUserData ;
var dialog = new H5P . Dialog ( 'content-user-data-reset' , 'Data Reset' , '<p>' + H5P . t ( 'contentChanged' ) + '</p><p>' + H5P . t ( 'startingOver' ) + '</p><div class="h5p-dialog-ok-button" tabIndex="0" role="button">OK</div>' , $container ) ;
H5P . jQuery ( dialog ) . on ( 'dialog-opened' , function ( event , $dialog ) {
2016-06-07 11:09:42 +02:00
var closeDialog = function ( event ) {
if ( event . type === 'click' || event . which === 32 ) {
2015-04-07 19:32:44 +02:00
dialog . close ( ) ;
2016-06-07 11:09:42 +02:00
H5P . deleteUserData ( contentId , 'state' , 0 ) ;
2015-04-07 19:32:44 +02:00
}
2016-06-07 11:09:42 +02:00
} ;
$dialog . find ( '.h5p-dialog-ok-button' ) . click ( closeDialog ) . keypress ( closeDialog ) ;
2019-02-27 16:04:37 +01:00
H5P . trigger ( instance , 'resize' ) ;
} ) . on ( 'dialog-closed' , function ( ) {
H5P . trigger ( instance , 'resize' ) ;
2015-04-07 19:32:44 +02:00
} ) ;
dialog . open ( ) ;
}
2016-07-15 16:14:32 +02:00
// If previousState is false we don't have a previous state
2015-04-07 19:32:44 +02:00
} ) ;
2014-03-26 08:43:29 +01:00
// Create new instance.
2015-07-14 14:37:39 +02:00
var instance = H5P . newRunnable ( library , contentId , $container , true , { standalone : true } ) ;
2014-10-01 10:05:04 +02:00
2019-04-03 16:09:53 +02:00
H5P . offlineRequestQueue = new H5P . OfflineRequestQueue ( { instance : instance } ) ;
2014-03-26 08:43:29 +01:00
// Check if we should add and display a fullscreen button for this H5P.
2016-11-11 11:57:54 +01:00
if ( contentData . fullScreen == 1 && H5P . fullscreenSupported ) {
2017-06-22 10:56:15 +02:00
H5P . jQuery (
'<div class="h5p-content-controls">' +
'<div role="button" ' +
'tabindex="0" ' +
'class="h5p-enable-fullscreen" ' +
2019-02-11 16:18:57 +01:00
'aria-label="' + H5P . t ( 'fullscreen' ) + '" ' +
2017-06-22 10:56:15 +02:00
'title="' + H5P . t ( 'fullscreen' ) + '">' +
'</div>' +
'</div>' )
. prependTo ( $container )
2019-01-09 11:51:17 +01:00
. children ( )
. click ( function ( ) {
H5P . fullScreen ( $container , instance ) ;
} )
2017-06-22 10:56:15 +02:00
. keydown ( function ( e ) {
if ( e . which === 32 || e . which === 13 ) {
H5P . fullScreen ( $container , instance ) ;
return false ;
}
} )
;
2014-10-01 10:05:04 +02:00
}
2016-04-08 13:18:35 +02:00
/ * *
2016-12-13 10:29:33 +01:00
* Create action bar
2016-04-08 13:18:35 +02:00
* /
2016-12-13 10:29:33 +01:00
var displayOptions = contentData . displayOptions ;
2016-12-20 14:37:31 +01:00
var displayFrame = false ;
2016-12-16 14:22:03 +01:00
if ( displayOptions . frame ) {
2016-12-13 10:29:33 +01:00
// Special handling of copyrights
2016-12-16 14:22:03 +01:00
if ( displayOptions . copyright ) {
2018-04-05 17:17:56 +02:00
var copyrights = H5P . getCopyrights ( instance , library . params , contentId , library . metadata ) ;
2016-12-13 10:29:33 +01:00
if ( ! copyrights ) {
2016-12-16 14:22:03 +01:00
displayOptions . copyright = false ;
2016-12-13 10:29:33 +01:00
}
}
2016-04-08 13:18:35 +02:00
2016-12-13 10:29:33 +01:00
// Create action bar
var actionBar = new H5P . ActionBar ( displayOptions ) ;
var $actions = actionBar . getDOMElement ( ) ;
2015-05-11 16:00:55 +02:00
2019-02-27 15:03:58 +01:00
actionBar . on ( 'reuse' , function ( ) {
H5P . openReuseDialog ( $actions , contentData , library , instance , contentId ) ;
instance . triggerXAPI ( 'accessed-reuse' ) ;
2016-12-13 10:29:33 +01:00
} ) ;
actionBar . on ( 'copyrights' , function ( ) {
var dialog = new H5P . Dialog ( 'copyrights' , H5P . t ( 'copyrightInformation' ) , copyrights , $container ) ;
2019-02-27 16:04:37 +01:00
dialog . open ( true ) ;
2017-01-31 13:35:23 +01:00
instance . triggerXAPI ( 'accessed-copyright' ) ;
2016-12-13 10:29:33 +01:00
} ) ;
actionBar . on ( 'embed' , function ( ) {
2015-02-25 12:10:07 +01:00
H5P . openEmbedDialog ( $actions , contentData . embedCode , contentData . resizeCode , {
2015-05-07 15:16:35 +02:00
width : $element . width ( ) ,
height : $element . height ( )
2019-02-27 16:04:37 +01:00
} , instance ) ;
2017-01-31 13:35:23 +01:00
instance . triggerXAPI ( 'accessed-embed' ) ;
2015-02-12 11:52:55 +01:00
} ) ;
2015-04-13 18:41:53 +02:00
2016-12-20 14:37:31 +01:00
if ( actionBar . hasActions ( ) ) {
displayFrame = true ;
$actions . insertAfter ( $container ) ;
}
2015-02-12 11:52:55 +01:00
}
2016-12-20 14:37:31 +01:00
$element . addClass ( displayFrame ? 'h5p-frame' : 'h5p-no-frame' ) ;
2014-10-01 10:05:04 +02:00
2014-10-15 11:38:29 +02:00
// Keep track of when we started
H5P . opened [ contentId ] = new Date ( ) ;
// Handle events when the user finishes the content. Useful for logging exercise results.
2015-02-20 10:15:36 +01:00
H5P . on ( instance , 'finish' , function ( event ) {
2014-10-15 11:38:29 +02:00
if ( event . data !== undefined ) {
H5P . setFinished ( contentId , event . data . score , event . data . maxScore , event . data . time ) ;
}
} ) ;
2014-04-30 15:59:19 +02:00
2015-02-20 10:15:36 +01:00
// Listen for xAPI events.
2015-02-16 16:47:04 +01:00
H5P . on ( instance , 'xAPI' , H5P . xAPICompletedListener ) ;
2014-04-30 15:59:19 +02:00
2015-04-07 19:32:44 +02:00
// Auto save current state if supported
if ( H5PIntegration . saveFreq !== false && (
2019-01-09 11:51:17 +01:00
instance . getCurrentState instanceof Function ||
2015-04-07 19:32:44 +02:00
typeof instance . getCurrentState === 'function' ) ) {
2014-04-30 15:59:19 +02:00
2015-08-26 15:58:49 +02:00
var saveTimer , save = function ( ) {
2015-04-07 19:32:44 +02:00
var state = instance . getCurrentState ( ) ;
if ( state !== undefined ) {
2015-04-09 14:00:00 +02:00
H5P . setUserData ( contentId , 'state' , state , { deleteOnChange : true } ) ;
2015-04-07 19:32:44 +02:00
}
if ( H5PIntegration . saveFreq ) {
// Continue autosave
saveTimer = setTimeout ( save , H5PIntegration . saveFreq * 1000 ) ;
}
2014-03-26 08:43:29 +01:00
} ;
2014-10-01 10:05:04 +02:00
2015-04-07 19:32:44 +02:00
if ( H5PIntegration . saveFreq ) {
// Start autosave
saveTimer = setTimeout ( save , H5PIntegration . saveFreq * 1000 ) ;
}
// xAPI events will schedule a save in three seconds.
H5P . on ( instance , 'xAPI' , function ( event ) {
var verb = event . getVerb ( ) ;
if ( verb === 'completed' || verb === 'progressed' ) {
clearTimeout ( saveTimer ) ;
saveTimer = setTimeout ( save , 3000 ) ;
}
2014-04-30 15:59:19 +02:00
} ) ;
2013-04-11 14:29:29 +02:00
}
2014-10-01 10:05:04 +02:00
2015-03-03 11:03:10 +01:00
if ( H5P . isFramed ) {
var resizeDelay ;
2015-02-09 10:22:14 +01:00
if ( H5P . externalEmbed === false ) {
// Internal embed
// Make it possible to resize the iframe when the content changes size. This way we get no scrollbars.
2017-08-23 12:44:15 +02:00
var iframe = window . frameElement ;
2015-02-09 10:22:14 +01:00
var resizeIframe = function ( ) {
if ( window . parent . H5P . isFullscreen ) {
return ; // Skip if full screen.
}
// Retain parent size to avoid jumping/scrolling
var parentHeight = iframe . parentElement . style . height ;
iframe . parentElement . style . height = iframe . parentElement . clientHeight + 'px' ;
2019-03-07 15:48:21 +01:00
// Note: Force layout reflow
// This fixes a flickering bug for embedded content on iPads
// @see https://github.com/h5p/h5p-moodle-plugin/issues/237
iframe . getBoundingClientRect ( ) ;
2015-02-09 10:22:14 +01:00
// Reset iframe height, in case content has shrinked.
iframe . style . height = '1px' ;
// Resize iframe so all content is visible.
iframe . style . height = ( iframe . contentDocument . body . scrollHeight ) + 'px' ;
// Free parent
iframe . parentElement . style . height = parentHeight ;
} ;
2015-02-20 10:15:36 +01:00
H5P . on ( instance , 'resize' , function ( ) {
2015-02-09 10:22:14 +01:00
// Use a delay to make sure iframe is resized to the correct size.
clearTimeout ( resizeDelay ) ;
resizeDelay = setTimeout ( function ( ) {
resizeIframe ( ) ;
} , 1 ) ;
} ) ;
2014-04-30 15:59:19 +02:00
}
2015-02-09 10:22:14 +01:00
else if ( H5P . communicator ) {
// External embed
var parentIsFriendly = false ;
2015-03-02 15:53:29 +01:00
// Handle that the resizer is loaded after the iframe
H5P . communicator . on ( 'ready' , function ( ) {
H5P . communicator . send ( 'hello' ) ;
} ) ;
2015-02-09 10:22:14 +01:00
// Handle hello message from our parent window
H5P . communicator . on ( 'hello' , function ( ) {
// Initial setup/handshake is done
parentIsFriendly = true ;
2016-01-05 13:57:16 +01:00
// Make iframe responsive
document . body . style . height = 'auto' ;
2015-02-09 10:22:14 +01:00
// Hide scrollbars for correct size
document . body . style . overflow = 'hidden' ;
2015-03-02 15:53:29 +01:00
// Content need to be resized to fit the new iframe size
H5P . trigger ( instance , 'resize' ) ;
2015-02-09 10:22:14 +01:00
} ) ;
// When resize has been prepared tell parent window to resize
2018-10-23 11:25:46 +02:00
H5P . communicator . on ( 'resizePrepared' , function ( ) {
2015-02-09 10:22:14 +01:00
H5P . communicator . send ( 'resize' , {
2016-01-05 13:57:16 +01:00
scrollHeight : document . body . scrollHeight
2015-02-09 10:22:14 +01:00
} ) ;
} ) ;
H5P . communicator . on ( 'resize' , function ( ) {
2015-02-20 10:15:36 +01:00
H5P . trigger ( instance , 'resize' ) ;
2015-02-09 10:22:14 +01:00
} ) ;
2015-02-20 10:15:36 +01:00
H5P . on ( instance , 'resize' , function ( ) {
2015-02-09 10:22:14 +01:00
if ( H5P . isFullscreen ) {
return ; // Skip iframe resize
}
// Use a delay to make sure iframe is resized to the correct size.
clearTimeout ( resizeDelay ) ;
resizeDelay = setTimeout ( function ( ) {
// Only resize if the iframe can be resized
if ( parentIsFriendly ) {
2016-01-05 13:57:16 +01:00
H5P . communicator . send ( 'prepareResize' , {
scrollHeight : document . body . scrollHeight ,
clientHeight : document . body . clientHeight
} ) ;
2015-02-09 10:22:14 +01:00
}
else {
H5P . communicator . send ( 'hello' ) ;
}
} , 0 ) ;
} ) ;
2014-03-26 08:43:29 +01:00
}
2015-02-09 10:22:14 +01:00
}
if ( ! H5P . isFramed || H5P . externalEmbed === false ) {
// Resize everything when window is resized.
2015-09-10 14:27:12 +02:00
H5P . jQuery ( window . parent ) . resize ( function ( ) {
2014-06-24 15:31:02 +02:00
if ( window . parent . H5P . isFullscreen ) {
2015-02-09 10:22:14 +01:00
// Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event.
2015-03-02 15:53:29 +01:00
H5P . trigger ( instance , 'resize' ) ;
2015-02-09 10:22:14 +01:00
}
else {
2015-02-20 10:15:36 +01:00
H5P . trigger ( instance , 'resize' ) ;
2014-04-30 15:59:19 +02:00
}
} ) ;
2013-04-11 14:29:29 +02:00
}
2015-03-02 15:53:29 +01:00
2015-03-02 15:01:05 +01:00
H5P . instances . push ( instance ) ;
2014-10-01 10:05:04 +02:00
2014-04-30 15:59:19 +02:00
// Resize content.
2015-02-04 18:43:06 +01:00
H5P . trigger ( instance , 'resize' ) ;
2019-09-18 13:45:37 +02:00
// Logic for hiding focus effects when using mouse
2019-09-26 12:58:15 +02:00
$element . addClass ( 'using-mouse' ) ;
$element . on ( 'mousedown keydown keyup' , function ( event ) {
$element . toggleClass ( 'using-mouse' , event . type === 'mousedown' ) ;
2019-09-18 13:45:37 +02:00
} ) ;
2019-10-10 10:34:57 +02:00
if ( H5P . externalDispatcher ) {
H5P . externalDispatcher . trigger ( 'initialized' ) ;
}
2013-01-17 09:01:43 +01:00
} ) ;
2013-11-08 18:16:41 +01:00
2014-03-26 08:43:29 +01:00
// Insert H5Ps that should be in iframes.
2015-03-10 10:09:31 +01:00
H5P . jQuery ( 'iframe.h5p-iframe:not(.h5p-initialized)' , target ) . each ( function ( ) {
2021-04-29 22:00:15 +02:00
const iframe = this ;
2021-05-12 12:27:57 +02:00
const $iframe = H5P . jQuery ( iframe ) ;
2021-04-29 22:00:15 +02:00
const contentId = $iframe . data ( 'content-id' ) ;
2020-08-28 16:54:16 +02:00
const contentData = H5PIntegration . contents [ 'cid-' + contentId ] ;
2021-04-29 22:00:15 +02:00
const contentLanguage = contentData && contentData . metadata && contentData . metadata . defaultLanguage
2020-08-28 16:54:16 +02:00
? contentData . metadata . defaultLanguage : 'en' ;
2021-04-29 22:00:15 +02:00
const writeDocument = function ( ) {
iframe . contentDocument . open ( ) ;
iframe . contentDocument . write ( '<!doctype html><html class="h5p-iframe" lang="' + contentLanguage + '"><head>' + H5P . getHeadTags ( contentId ) + '</head><body><div class="h5p-content" data-content-id="' + contentId + '"/></body></html>' ) ;
iframe . contentDocument . close ( ) ;
} ;
$iframe . addClass ( 'h5p-initialized' )
if ( iframe . contentDocument === null ) {
// In some Edge cases the iframe isn't always loaded when the page is ready.
$iframe . on ( 'load' , writeDocument ) ;
$iframe . attr ( 'src' , 'about:blank' ) ;
}
else {
writeDocument ( ) ;
}
2014-03-26 08:43:29 +01:00
} ) ;
2013-01-17 09:01:43 +01:00
} ;
2015-02-27 13:59:42 +01:00
/ * *
* Loop through assets for iframe content and create a set of tags for head .
*
* @ private
* @ param { number } contentId
* @ returns { string } HTML
* /
H5P . getHeadTags = function ( contentId ) {
var createStyleTags = function ( styles ) {
var tags = '' ;
for ( var i = 0 ; i < styles . length ; i ++ ) {
2015-03-23 09:45:02 +01:00
tags += '<link rel="stylesheet" href="' + styles [ i ] + '">' ;
2015-02-27 13:59:42 +01:00
}
return tags ;
} ;
var createScriptTags = function ( scripts ) {
var tags = '' ;
for ( var i = 0 ; i < scripts . length ; i ++ ) {
2015-03-23 09:45:02 +01:00
tags += '<script src="' + scripts [ i ] + '"></script>' ;
2015-02-27 13:59:42 +01:00
}
return tags ;
} ;
2016-01-27 12:42:55 +01:00
return '<base target="_parent">' +
createStyleTags ( H5PIntegration . core . styles ) +
2015-02-27 13:59:42 +01:00
createStyleTags ( H5PIntegration . contents [ 'cid-' + contentId ] . styles ) +
createScriptTags ( H5PIntegration . core . scripts ) +
createScriptTags ( H5PIntegration . contents [ 'cid-' + contentId ] . scripts ) +
2015-09-10 14:27:12 +02:00
'<script>H5PIntegration = window.parent.H5PIntegration; var H5P = H5P || {}; H5P.externalEmbed = false;</script>' ;
2015-02-27 13:59:42 +01:00
} ;
2015-05-05 13:13:57 +02:00
/ * *
* When embedded the communicator helps talk to the parent page .
*
* @ type { Communicator }
* /
2015-08-26 15:58:49 +02:00
H5P . communicator = ( function ( ) {
2015-02-09 10:22:14 +01:00
/ * *
* @ class
2015-05-05 13:13:57 +02:00
* @ private
2015-02-09 10:22:14 +01:00
* /
function Communicator ( ) {
var self = this ;
// Maps actions to functions
var actionHandlers = { } ;
// Register message listener
window . addEventListener ( 'message' , function receiveMessage ( event ) {
if ( window . parent !== event . source || event . data . context !== 'h5p' ) {
return ; // Only handle messages from parent and in the correct context
}
if ( actionHandlers [ event . data . action ] !== undefined ) {
actionHandlers [ event . data . action ] ( event . data ) ;
}
} , false ) ;
/ * *
* Register action listener .
*
2015-05-05 13:13:57 +02:00
* @ param { string } action What you are waiting for
* @ param { function } handler What you want done
2015-02-09 10:22:14 +01:00
* /
self . on = function ( action , handler ) {
actionHandlers [ action ] = handler ;
} ;
/ * *
* Send a message to the all mighty father .
*
2015-05-05 13:13:57 +02:00
* @ param { string } action
2015-02-09 10:22:14 +01:00
* @ param { Object } [ data ] payload
* /
2015-08-26 15:58:49 +02:00
self . send = function ( action , data ) {
2015-02-09 10:22:14 +01:00
if ( data === undefined ) {
data = { } ;
}
data . context = 'h5p' ;
data . action = action ;
// Parent origin can be anything
window . parent . postMessage ( data , '*' ) ;
} ;
}
return ( window . postMessage && window . addEventListener ? new Communicator ( ) : undefined ) ;
} ) ( ) ;
2016-11-11 11:45:59 +01:00
/ * *
* Enter semi fullscreen for the given H5P instance
*
* @ param { H5P . jQuery } $element Content container .
* @ param { Object } instance
* @ param { function } exitCallback Callback function called when user exits fullscreen .
* @ param { H5P . jQuery } $body For internal use . Gives the body of the iframe .
* /
H5P . semiFullScreen = function ( $element , instance , exitCallback , body ) {
H5P . fullScreen ( $element , instance , exitCallback , body , true ) ;
} ;
2013-04-11 14:29:29 +02:00
/ * *
2015-05-05 13:13:57 +02:00
* Enter fullscreen for the given H5P instance .
2013-04-26 17:27:35 +02:00
*
2015-05-05 13:13:57 +02:00
* @ param { H5P . jQuery } $element Content container .
* @ param { Object } instance
2013-11-26 14:54:29 +01:00
* @ param { function } exitCallback Callback function called when user exits fullscreen .
2015-05-05 13:13:57 +02:00
* @ param { H5P . jQuery } $body For internal use . Gives the body of the iframe .
2016-12-12 11:22:34 +01:00
* @ param { Boolean } forceSemiFullScreen
2013-04-11 14:29:29 +02:00
* /
2016-11-11 11:45:59 +01:00
H5P . fullScreen = function ( $element , instance , exitCallback , body , forceSemiFullScreen ) {
2015-02-09 10:22:14 +01:00
if ( H5P . exitFullScreen !== undefined ) {
return ; // Cannot enter new fullscreen until previous is over
}
if ( H5P . isFramed && H5P . externalEmbed === false ) {
2014-03-26 08:43:29 +01:00
// Trigger resize on wrapper in parent window.
2016-11-11 11:45:59 +01:00
window . parent . H5P . fullScreen ( $element , instance , exitCallback , H5P . $body . get ( ) , forceSemiFullScreen ) ;
2015-02-09 10:22:14 +01:00
H5P . isFullscreen = true ;
H5P . exitFullScreen = function ( ) {
2015-09-10 14:27:12 +02:00
window . parent . H5P . exitFullScreen ( ) ;
2015-02-24 16:02:14 +01:00
} ;
H5P . on ( instance , 'exitFullScreen' , function ( ) {
2015-02-09 10:22:14 +01:00
H5P . isFullscreen = false ;
H5P . exitFullScreen = undefined ;
2015-02-24 16:02:14 +01:00
} ) ;
2014-03-26 08:43:29 +01:00
return ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
var $container = $element ;
2018-10-23 11:25:46 +02:00
var $classes , $iframe , $body ;
2014-03-26 08:43:29 +01:00
if ( body === undefined ) {
2013-12-04 15:21:34 +01:00
$body = H5P . $body ;
}
2014-03-26 08:43:29 +01:00
else {
// We're called from an iframe.
$body = H5P . jQuery ( body ) ;
$classes = $body . add ( $element . get ( ) ) ;
var iframeSelector = '#h5p-iframe-' + $element . parent ( ) . data ( 'content-id' ) ;
$iframe = H5P . jQuery ( iframeSelector ) ;
$element = $iframe . parent ( ) ; // Put iframe wrapper in fullscreen, not container.
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
$classes = $element . add ( H5P . $body ) . add ( $classes ) ;
2014-10-01 10:05:04 +02:00
2014-05-22 11:17:30 +02:00
/ * *
* Prepare for resize by setting the correct styles .
2014-10-01 10:05:04 +02:00
*
2015-05-05 13:13:57 +02:00
* @ private
* @ param { string } classes CSS
2014-05-22 11:17:30 +02:00
* /
var before = function ( classes ) {
$classes . addClass ( classes ) ;
2014-10-01 10:05:04 +02:00
2014-05-22 11:17:30 +02:00
if ( $iframe !== undefined ) {
// Set iframe to its default size(100%).
$iframe . css ( 'height' , '' ) ;
2014-03-26 08:43:29 +01:00
}
2014-05-22 11:17:30 +02:00
} ;
2014-10-01 10:05:04 +02:00
2014-05-22 11:17:30 +02:00
/ * *
* Gets called when fullscreen mode has been entered .
* Resizes and sets focus on content .
2015-05-05 13:13:57 +02:00
*
* @ private
2014-05-22 11:17:30 +02:00
* /
var entered = function ( ) {
// Do not rely on window resize events.
2015-02-04 18:43:06 +01:00
H5P . trigger ( instance , 'resize' ) ;
H5P . trigger ( instance , 'focus' ) ;
2015-02-20 10:15:36 +01:00
H5P . trigger ( instance , 'enterFullScreen' ) ;
2014-05-22 11:17:30 +02:00
} ;
2014-10-01 10:05:04 +02:00
2014-05-22 11:17:30 +02:00
/ * *
* Gets called when fullscreen mode has been exited .
* Resizes and sets focus on content .
2014-10-01 10:05:04 +02:00
*
2015-05-05 13:13:57 +02:00
* @ private
* @ param { string } classes CSS
2014-05-22 11:17:30 +02:00
* /
var done = function ( classes ) {
H5P . isFullscreen = false ;
$classes . removeClass ( classes ) ;
2014-10-01 10:05:04 +02:00
2014-05-22 11:17:30 +02:00
// Do not rely on window resize events.
2015-02-04 18:43:06 +01:00
H5P . trigger ( instance , 'resize' ) ;
H5P . trigger ( instance , 'focus' ) ;
2013-11-08 18:16:41 +01:00
2015-02-09 10:22:14 +01:00
H5P . exitFullScreen = undefined ;
2014-03-26 08:43:29 +01:00
if ( exitCallback !== undefined ) {
exitCallback ( ) ;
}
2015-02-09 11:47:33 +01:00
2015-02-20 10:15:36 +01:00
H5P . trigger ( instance , 'exitFullScreen' ) ;
2014-03-26 08:43:29 +01:00
} ;
2013-11-08 18:16:41 +01:00
2014-04-30 15:59:19 +02:00
H5P . isFullscreen = true ;
2016-11-11 11:45:59 +01:00
if ( H5P . fullScreenBrowserPrefix === undefined || forceSemiFullScreen === true ) {
2013-04-11 14:29:29 +02:00
// Create semi fullscreen.
2014-10-01 10:05:04 +02:00
2015-02-09 10:22:14 +01:00
if ( H5P . isFramed ) {
2015-02-09 11:51:39 +01:00
return ; // TODO: Should we support semi-fullscreen for IE9 & 10 ?
2015-02-09 10:22:14 +01:00
}
2014-05-22 11:17:30 +02:00
before ( 'h5p-semi-fullscreen' ) ;
2018-11-23 16:40:18 +01:00
var $disable = H5P . jQuery ( '<div role="button" tabindex="0" class="h5p-disable-fullscreen" title="' + H5P . t ( 'disableFullscreen' ) + '" aria-label="' + H5P . t ( 'disableFullscreen' ) + '"></div>' ) . appendTo ( $container . find ( '.h5p-content-controls' ) ) ;
2015-10-27 16:57:28 +01:00
var keyup , disableSemiFullscreen = H5P . exitFullScreen = function ( ) {
2015-11-13 11:18:53 +01:00
if ( prevViewportContent ) {
// Use content from the previous viewport tag
h5pViewport . content = prevViewportContent ;
2015-10-27 16:57:28 +01:00
}
else {
2015-11-13 11:18:53 +01:00
// Remove viewport tag
head . removeChild ( h5pViewport ) ;
2015-10-27 16:57:28 +01:00
}
2014-10-01 10:05:04 +02:00
$disable . remove ( ) ;
2013-12-04 15:21:34 +01:00
$body . unbind ( 'keyup' , keyup ) ;
2014-03-26 08:43:29 +01:00
done ( 'h5p-semi-fullscreen' ) ;
2013-04-11 14:29:29 +02:00
} ;
keyup = function ( event ) {
if ( event . keyCode === 27 ) {
disableSemiFullscreen ( ) ;
}
} ;
$disable . click ( disableSemiFullscreen ) ;
2013-12-04 15:21:34 +01:00
$body . keyup ( keyup ) ;
2015-10-27 16:57:28 +01:00
// Disable zoom
2015-11-13 11:18:53 +01:00
var prevViewportContent , h5pViewport ;
2015-10-27 16:57:28 +01:00
var metaTags = document . getElementsByTagName ( 'meta' ) ;
for ( var i = 0 ; i < metaTags . length ; i ++ ) {
if ( metaTags [ i ] . name === 'viewport' ) {
2015-11-13 11:18:53 +01:00
// Use the existing viewport tag
h5pViewport = metaTags [ i ] ;
prevViewportContent = h5pViewport . content ;
2015-10-27 16:57:28 +01:00
break ;
}
}
2015-11-13 11:18:53 +01:00
if ( ! prevViewportContent ) {
// Create a new viewport tag
h5pViewport = document . createElement ( 'meta' ) ;
h5pViewport . name = 'viewport' ;
2015-10-27 16:57:28 +01:00
}
2015-11-13 11:18:53 +01:00
h5pViewport . content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' ;
if ( ! prevViewportContent ) {
// Insert the new viewport tag
2015-10-27 16:57:28 +01:00
var head = document . getElementsByTagName ( 'head' ) [ 0 ] ;
2015-11-13 11:18:53 +01:00
head . appendChild ( h5pViewport ) ;
2015-10-27 16:57:28 +01:00
}
2014-05-22 11:17:30 +02:00
entered ( ) ;
2013-04-10 17:08:57 +02:00
}
2013-04-11 14:29:29 +02:00
else {
2014-03-26 08:43:29 +01:00
// Create real fullscreen.
2014-10-01 10:05:04 +02:00
2014-05-22 11:17:30 +02:00
before ( 'h5p-fullscreen' ) ;
2014-01-29 16:03:51 +01:00
var first , eventName = ( H5P . fullScreenBrowserPrefix === 'ms' ? 'MSFullscreenChange' : H5P . fullScreenBrowserPrefix + 'fullscreenchange' ) ;
2013-04-11 14:29:29 +02:00
document . addEventListener ( eventName , function ( ) {
if ( first === undefined ) {
2014-05-22 11:17:30 +02:00
// We are entering fullscreen mode
2013-04-11 14:29:29 +02:00
first = false ;
2014-05-22 11:17:30 +02:00
entered ( ) ;
2013-04-11 14:29:29 +02:00
return ;
}
2014-10-01 10:05:04 +02:00
2014-05-22 11:17:30 +02:00
// We are exiting fullscreen
2014-03-26 08:43:29 +01:00
done ( 'h5p-fullscreen' ) ;
2013-04-11 14:29:29 +02:00
document . removeEventListener ( eventName , arguments . callee , false ) ;
} ) ;
2013-04-26 17:27:35 +02:00
2013-04-11 14:29:29 +02:00
if ( H5P . fullScreenBrowserPrefix === '' ) {
2021-05-12 12:10:51 +02:00
$element [ 0 ] . requestFullscreen ( ) ;
2013-04-11 14:29:29 +02:00
}
else {
2014-01-29 16:03:51 +01:00
var method = ( H5P . fullScreenBrowserPrefix === 'ms' ? 'msRequestFullscreen' : H5P . fullScreenBrowserPrefix + 'RequestFullScreen' ) ;
2014-05-22 11:17:30 +02:00
var params = ( H5P . fullScreenBrowserPrefix === 'webkit' && H5P . safariBrowser === 0 ? Element . ALLOW _KEYBOARD _INPUT : undefined ) ;
2014-03-26 08:43:29 +01:00
$element [ 0 ] [ method ] ( params ) ;
2013-04-11 14:29:29 +02:00
}
2015-02-09 10:22:14 +01:00
// Allows everone to exit
H5P . exitFullScreen = function ( ) {
if ( H5P . fullScreenBrowserPrefix === '' ) {
document . exitFullscreen ( ) ;
}
else if ( H5P . fullScreenBrowserPrefix === 'moz' ) {
document . mozCancelFullScreen ( ) ;
}
else {
document [ H5P . fullScreenBrowserPrefix + 'ExitFullscreen' ] ( ) ;
}
} ;
2013-04-10 17:08:57 +02:00
}
} ;
2019-06-18 14:43:34 +02:00
( function ( ) {
2019-06-19 10:50:37 +02:00
/ * *
* Helper for adding a query parameter to an existing path that may already
* contain one or a hash .
*
* @ param { string } path
* @ param { string } parameter
* @ return { string }
* /
H5P . addQueryParameter = function ( path , parameter ) {
let newPath , secondSplit ;
const firstSplit = path . split ( '?' ) ;
if ( firstSplit [ 1 ] ) {
// There is already an existing query
secondSplit = firstSplit [ 1 ] . split ( '#' ) ;
newPath = firstSplit [ 0 ] + '?' + secondSplit [ 0 ] + '&' ;
}
else {
// No existing query, just need to take care of the hash
secondSplit = firstSplit [ 0 ] . split ( '#' ) ;
newPath = secondSplit [ 0 ] + '?' ;
}
newPath += parameter ;
if ( secondSplit [ 1 ] ) {
// Add back the hash
newPath += '#' + secondSplit [ 1 ] ;
}
return newPath ;
} ;
2019-06-18 14:43:34 +02:00
/ * *
* Helper for setting the crossOrigin attribute + the complete correct source .
* Note : This will start loading the resource .
*
* @ param { Element } element DOM element , typically img , video or audio
* @ param { Object } source File object from parameters / json _content ( created by H5PEditor )
* @ param { number } contentId Needed to determine the complete correct file path
* /
H5P . setSource = function ( element , source , contentId ) {
2019-06-19 10:50:37 +02:00
let path = source . path ;
2019-06-18 14:43:34 +02:00
const crossOrigin = H5P . getCrossOrigin ( source ) ;
if ( crossOrigin ) {
element . crossOrigin = crossOrigin ;
2019-06-19 10:50:37 +02:00
if ( H5PIntegration . crossoriginCacheBuster ) {
// Some sites may want to add a cache buster in case the same resource
// is used elsewhere without the crossOrigin attribute
path = H5P . addQueryParameter ( path , H5PIntegration . crossoriginCacheBuster ) ;
}
2019-06-18 14:43:34 +02:00
}
else {
// In case this element has been used before.
element . removeAttribute ( 'crossorigin' ) ;
}
2019-06-19 10:50:37 +02:00
element . src = H5P . getPath ( path , contentId ) ;
2019-06-18 14:43:34 +02:00
} ;
/ * *
* Check if the given path has a protocol .
*
* @ private
* @ param { string } path
* @ return { string }
* /
2014-06-24 14:41:35 +02:00
var hasProtocol = function ( path ) {
return path . match ( /^[a-z0-9]+:\/\//i ) ;
} ;
2014-10-01 10:05:04 +02:00
2019-06-18 14:43:34 +02:00
/ * *
* Get the crossOrigin policy to use for img , video and audio tags on the current site .
*
* @ param { Object | string } source File object from parameters / json _content - Can also be URL ( deprecated usage )
* @ returns { string | null } crossOrigin attribute value required by the source
* /
H5P . getCrossOrigin = function ( source ) {
if ( typeof source !== 'object' ) {
// Deprecated usage.
return H5PIntegration . crossorigin && H5PIntegration . crossoriginRegex && source . match ( H5PIntegration . crossoriginRegex ) ? H5PIntegration . crossorigin : null ;
}
2014-10-01 10:05:04 +02:00
2019-06-18 14:43:34 +02:00
if ( H5PIntegration . crossorigin && ! hasProtocol ( source . path ) ) {
// This is a local file, use the local crossOrigin policy.
return H5PIntegration . crossorigin ;
// Note: We cannot use this for all external sources since we do not know
// each server's individual policy. We could add support for a list of
// external sources and their policy later on.
2016-09-09 09:19:55 +02:00
}
2019-06-18 14:43:34 +02:00
} ;
/ * *
* Find the path to the content files based on the id of the content .
* Also identifies and returns absolute paths .
*
* @ param { string } path
* Relative to content folder or absolute .
* @ param { number } contentId
* ID of the content requesting the path .
* @ returns { string }
* Complete URL to path .
* /
H5P . getPath = function ( path , contentId ) {
if ( hasProtocol ( path ) ) {
return path ;
}
var prefix ;
var isTmpFile = ( path . substr ( - 4 , 4 ) === '#tmp' ) ;
if ( contentId !== undefined && ! isTmpFile ) {
// Check for custom override URL
if ( H5PIntegration . contents !== undefined &&
H5PIntegration . contents [ 'cid-' + contentId ] ) {
prefix = H5PIntegration . contents [ 'cid-' + contentId ] . contentUrl ;
}
if ( ! prefix ) {
prefix = H5PIntegration . url + '/content/' + contentId ;
}
}
else if ( window . H5PEditor !== undefined ) {
prefix = H5PEditor . filesPath ;
}
else {
return ;
2016-09-08 16:34:53 +02:00
}
2014-10-01 10:05:04 +02:00
2019-06-18 14:43:34 +02:00
if ( ! hasProtocol ( prefix ) ) {
// Use absolute urls
prefix = window . location . protocol + "//" + window . location . host + prefix ;
}
2014-10-01 10:05:04 +02:00
2019-06-18 14:43:34 +02:00
return prefix + '/' + path ;
} ;
} ) ( ) ;
2013-01-17 22:20:41 +01:00
2013-07-03 14:22:00 +02:00
/ * *
2013-07-03 14:24:09 +02:00
* THIS FUNCTION IS DEPRECATED , USE getPath INSTEAD
2015-03-23 10:53:21 +01:00
* Will be remove march 2016.
2013-07-03 14:24:09 +02:00
*
2015-05-05 13:13:57 +02:00
* Find the path to the content files folder based on the id of the content
2013-07-03 14:22:00 +02:00
*
2015-05-05 13:13:57 +02:00
* @ deprecated
* Will be removed march 2016.
* @ param contentId
* Id of the content requesting the path
* @ returns { string }
* URL
2013-07-03 14:22:00 +02:00
* /
H5P . getContentPath = function ( contentId ) {
2015-03-23 10:53:21 +01:00
return H5PIntegration . url + '/content/' + contentId ;
2013-07-03 14:22:00 +02:00
} ;
2013-11-26 14:54:29 +01:00
/ * *
* Get library class constructor from H5P by classname .
2014-10-01 10:05:04 +02:00
* Note that this class will only work for resolve "H5P.NameWithoutDot" .
2015-05-05 13:13:57 +02:00
* Also check out { @ link H5P . newRunnable }
2013-11-26 14:54:29 +01:00
*
* Used from libraries to construct instances of other libraries ' objects by name .
*
* @ param { string } name Name of library
2015-05-05 13:13:57 +02:00
* @ returns { Object } Class constructor
2013-11-26 14:54:29 +01:00
* /
H5P . classFromName = function ( name ) {
2013-01-17 09:01:43 +01:00
var arr = name . split ( "." ) ;
2015-05-05 13:13:57 +02:00
return this [ arr [ arr . length - 1 ] ] ;
2013-01-17 09:01:43 +01:00
} ;
2014-03-26 08:43:29 +01:00
/ * *
* A safe way of creating a new instance of a runnable H5P .
*
2015-05-05 13:13:57 +02:00
* @ param { Object } library
* Library / action object form params .
* @ param { number } contentId
* Identifies the content .
* @ param { H5P . jQuery } [ $attachTo ]
* Element to attach the instance to .
* @ param { boolean } [ skipResize ]
* Skip triggering of the resize event after attaching .
* @ param { Object } [ extras ]
* Extra parameters for the H5P content constructor
* @ returns { Object }
* Instance .
2014-03-26 08:43:29 +01:00
* /
2015-03-22 20:39:16 +01:00
H5P . newRunnable = function ( library , contentId , $attachTo , skipResize , extras ) {
2015-04-07 19:32:44 +02:00
var nameSplit , versionSplit , machineName ;
2014-03-26 08:43:29 +01:00
try {
2014-10-01 10:05:04 +02:00
nameSplit = library . library . split ( ' ' , 2 ) ;
2015-04-07 19:32:44 +02:00
machineName = nameSplit [ 0 ] ;
2014-10-01 10:05:04 +02:00
versionSplit = nameSplit [ 1 ] . split ( '.' , 2 ) ;
2014-03-26 08:43:29 +01:00
}
catch ( err ) {
return H5P . error ( 'Invalid library string: ' + library . library ) ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
if ( ( library . params instanceof Object ) !== true || ( library . params instanceof Array ) === true ) {
H5P . error ( 'Invalid library params for: ' + library . library ) ;
return H5P . error ( library . params ) ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
// Find constructor function
2014-10-01 10:05:04 +02:00
var constructor ;
2014-03-26 08:43:29 +01:00
try {
nameSplit = nameSplit [ 0 ] . split ( '.' ) ;
2014-10-01 10:05:04 +02:00
constructor = window ;
for ( var i = 0 ; i < nameSplit . length ; i ++ ) {
2014-03-26 08:43:29 +01:00
constructor = constructor [ nameSplit [ i ] ] ;
2014-10-01 10:05:04 +02:00
}
2014-03-26 08:43:29 +01:00
if ( typeof constructor !== 'function' ) {
throw null ;
}
}
catch ( err ) {
return H5P . error ( 'Unable to find constructor for: ' + library . library ) ;
}
2014-10-01 10:05:04 +02:00
2015-03-22 20:39:16 +01:00
if ( extras === undefined ) {
extras = { } ;
}
2015-04-07 19:32:44 +02:00
if ( library . subContentId ) {
extras . subContentId = library . subContentId ;
2015-03-21 16:45:38 +01:00
}
2015-04-07 19:32:44 +02:00
2015-04-13 20:31:21 +02:00
if ( library . userDatas && library . userDatas . state && H5PIntegration . saveFreq ) {
2015-04-07 19:32:44 +02:00
extras . previousState = library . userDatas . state ;
}
2018-03-16 20:11:08 +01:00
if ( library . metadata ) {
extras . metadata = library . metadata ;
}
2015-07-17 13:32:35 +02:00
// Makes all H5P libraries extend H5P.ContentType:
2015-07-14 14:37:39 +02:00
var standalone = extras . standalone || false ;
2015-07-17 13:32:35 +02:00
// This order makes it possible for an H5P library to override H5P.ContentType functions!
2015-07-20 13:40:59 +02:00
constructor . prototype = H5P . jQuery . extend ( { } , H5P . ContentType ( standalone ) . prototype , constructor . prototype ) ;
2015-07-14 14:37:39 +02:00
2015-04-07 19:32:44 +02:00
var instance ;
// Some old library versions have their own custom third parameter.
// Make sure we don't send them the extras.
// (they will interpret it as something else)
2015-03-22 20:39:16 +01:00
if ( H5P . jQuery . inArray ( library . library , [ 'H5P.CoursePresentation 1.0' , 'H5P.CoursePresentation 1.1' , 'H5P.CoursePresentation 1.2' , 'H5P.CoursePresentation 1.3' ] ) > - 1 ) {
2015-04-07 19:32:44 +02:00
instance = new constructor ( library . params , contentId ) ;
2015-03-22 20:39:16 +01:00
}
else {
2015-04-07 19:32:44 +02:00
instance = new constructor ( library . params , contentId , extras ) ;
2015-03-21 16:45:38 +01:00
}
2014-10-01 10:05:04 +02:00
2014-04-30 15:59:19 +02:00
if ( instance . $ === undefined ) {
instance . $ = H5P . jQuery ( instance ) ;
}
2014-10-01 10:05:04 +02:00
2015-02-18 09:07:57 +01:00
if ( instance . contentId === undefined ) {
2015-02-18 15:20:20 +01:00
instance . contentId = contentId ;
2015-02-18 09:07:57 +01:00
}
2015-04-07 19:32:44 +02:00
if ( instance . subContentId === undefined && library . subContentId ) {
instance . subContentId = library . subContentId ;
2015-03-21 16:45:38 +01:00
}
2015-03-22 20:39:16 +01:00
if ( instance . parent === undefined && extras && extras . parent ) {
instance . parent = extras . parent ;
2015-03-21 16:45:38 +01:00
}
2015-07-10 12:58:02 +02:00
if ( instance . libraryInfo === undefined ) {
instance . libraryInfo = {
versionedName : library . library ,
versionedNameNoSpaces : machineName + '-' + versionSplit [ 0 ] + '.' + versionSplit [ 1 ] ,
machineName : machineName ,
majorVersion : versionSplit [ 0 ] ,
minorVersion : versionSplit [ 1 ]
} ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
if ( $attachTo !== undefined ) {
2015-07-14 14:37:39 +02:00
$attachTo . toggleClass ( 'h5p-standalone' , standalone ) ;
2014-03-26 08:43:29 +01:00
instance . attach ( $attachTo ) ;
2015-04-07 19:32:44 +02:00
H5P . trigger ( instance , 'domChanged' , {
'$target' : $attachTo ,
'library' : machineName ,
'key' : 'newLibrary'
} , { 'bubbles' : true , 'external' : true } ) ;
2014-10-01 10:05:04 +02:00
2014-04-30 15:59:19 +02:00
if ( skipResize === undefined || ! skipResize ) {
2014-03-26 08:43:29 +01:00
// Resize content.
2015-02-04 18:43:06 +01:00
H5P . trigger ( instance , 'resize' ) ;
2014-03-26 08:43:29 +01:00
}
}
return instance ;
} ;
/ * *
2015-05-05 13:13:57 +02:00
* Used to print useful error messages . ( to JavaScript error console )
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ param { * } err Error to print .
2014-03-26 08:43:29 +01:00
* /
H5P . error = function ( err ) {
2015-02-20 10:26:33 +01:00
if ( window . console !== undefined && console . error !== undefined ) {
2015-05-15 12:21:23 +02:00
console . error ( err . stack ? err . stack : err ) ;
2014-03-26 08:43:29 +01:00
}
2014-05-02 15:45:45 +02:00
} ;
2014-03-26 08:43:29 +01:00
/ * *
* Translate text strings .
*
2015-05-05 13:13:57 +02:00
* @ param { string } key
* Translation identifier , may only contain a - zA - Z0 - 9. No spaces or special chars .
2016-04-07 14:51:56 +02:00
* @ param { Object } [ vars ]
2015-05-05 13:13:57 +02:00
* Data for placeholders .
2016-04-07 14:51:56 +02:00
* @ param { string } [ ns ]
2015-05-05 13:13:57 +02:00
* Translation namespace . Defaults to H5P .
* @ returns { string }
* Translated text
2014-03-26 08:43:29 +01:00
* /
H5P . t = function ( key , vars , ns ) {
if ( ns === undefined ) {
ns = 'H5P' ;
}
2015-02-27 13:59:42 +01:00
if ( H5PIntegration . l10n [ ns ] === undefined ) {
2014-03-26 08:43:29 +01:00
return '[Missing translation namespace "' + ns + '"]' ;
}
2014-10-01 10:05:04 +02:00
2015-02-27 13:59:42 +01:00
if ( H5PIntegration . l10n [ ns ] [ key ] === undefined ) {
2014-03-26 08:43:29 +01:00
return '[Missing translation "' + key + '" in "' + ns + '"]' ;
}
2015-02-27 13:59:42 +01:00
var translation = H5PIntegration . l10n [ ns ] [ key ] ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
if ( vars !== undefined ) {
// Replace placeholder with variables.
for ( var placeholder in vars ) {
translation = translation . replace ( placeholder , vars [ placeholder ] ) ;
2014-10-01 10:05:04 +02:00
}
2014-03-26 08:43:29 +01:00
}
return translation ;
} ;
2015-05-05 13:13:57 +02:00
/ * *
* Creates a new popup dialog over the H5P content .
*
* @ class
* @ param { string } name
* Used for html class .
* @ param { string } title
* Used for header .
* @ param { string } content
* Displayed inside the dialog .
* @ param { H5P . jQuery } $element
* Which DOM element the dialog should be inserted after .
* /
2014-03-26 08:43:29 +01:00
H5P . Dialog = function ( name , title , content , $element ) {
2017-02-07 13:48:42 +01:00
/** @alias H5P.Dialog# */
2014-03-26 08:43:29 +01:00
var self = this ;
2019-10-17 15:01:22 +02:00
var $dialog = H5P . jQuery ( '<div class="h5p-popup-dialog h5p-' + name + ' - dialog " role=" dialog " tabindex=" - 1 " > \
2014-03-26 08:43:29 +01:00
< div class = "h5p-inner" > \
< h2 > ' + title + ' < / h 2 > \
< div class = "h5p-scroll-content" > ' + content + ' < / d i v > \
2019-01-11 14:52:10 +01:00
< div class = "h5p-close" role = "button" tabindex = "0" aria - label = "' + H5P.t('close') + '" title = "' + H5P.t('close') + '" > < / d i v > \
2014-03-26 08:43:29 +01:00
< / d i v > \
< / d i v > ' )
. insertAfter ( $element )
2019-09-18 13:45:37 +02:00
. click ( function ( e ) {
if ( e && e . originalEvent && e . originalEvent . preventClosing ) {
return ;
}
2014-03-26 08:43:29 +01:00
self . close ( ) ;
} )
2019-01-09 11:51:17 +01:00
. children ( '.h5p-inner' )
2019-09-18 13:45:37 +02:00
. click ( function ( e ) {
e . originalEvent . preventClosing = true ;
2019-01-09 11:51:17 +01:00
} )
. find ( '.h5p-close' )
. click ( function ( ) {
self . close ( ) ;
} )
2019-10-17 15:01:22 +02:00
. keypress ( function ( e ) {
if ( e . which === 13 || e . which === 32 ) {
self . close ( ) ;
return false ;
}
} )
2019-01-09 11:51:17 +01:00
. end ( )
. find ( 'a' )
. click ( function ( e ) {
e . stopPropagation ( ) ;
} )
. end ( )
2016-03-31 16:07:45 +02:00
. end ( ) ;
2014-10-01 10:05:04 +02:00
2017-02-07 13:48:42 +01:00
/ * *
* Opens the dialog .
* /
2019-02-27 16:04:37 +01:00
self . open = function ( scrollbar ) {
if ( scrollbar ) {
$dialog . css ( 'height' , '100%' ) ;
}
2014-03-26 08:43:29 +01:00
setTimeout ( function ( ) {
$dialog . addClass ( 'h5p-open' ) ; // Fade in
// Triggering an event, in case something has to be done after dialog has been opened.
H5P . jQuery ( self ) . trigger ( 'dialog-opened' , [ $dialog ] ) ;
2019-10-17 15:01:22 +02:00
$dialog . focus ( ) ;
2014-03-26 08:43:29 +01:00
} , 1 ) ;
} ;
2014-10-01 10:05:04 +02:00
2017-02-07 13:48:42 +01:00
/ * *
* Closes the dialog .
* /
self . close = function ( ) {
2014-03-26 08:43:29 +01:00
$dialog . removeClass ( 'h5p-open' ) ; // Fade out
setTimeout ( function ( ) {
$dialog . remove ( ) ;
2019-02-27 16:04:37 +01:00
H5P . jQuery ( self ) . trigger ( 'dialog-closed' , [ $dialog ] ) ;
2019-10-17 15:01:22 +02:00
$element . attr ( 'tabindex' , '-1' ) ;
$element . focus ( ) ;
2014-03-26 08:43:29 +01:00
} , 200 ) ;
} ;
} ;
/ * *
2015-05-11 16:00:55 +02:00
* Gather copyright information for the given content .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ param { Object } instance
* H5P instance to get copyright information for .
* @ param { Object } parameters
* Parameters of the content instance .
* @ param { number } contentId
* Identifies the H5P content
2018-06-28 18:21:30 +02:00
* @ param { Object } metadata
* Metadata of the content instance .
2015-05-11 16:00:55 +02:00
* @ returns { string } Copyright information .
2014-03-26 08:43:29 +01:00
* /
2018-04-05 17:17:56 +02:00
H5P . getCopyrights = function ( instance , parameters , contentId , metadata ) {
2015-05-12 10:04:21 +02:00
var copyrights ;
2014-10-30 15:51:20 +01:00
if ( instance . getCopyrights !== undefined ) {
2015-05-15 12:21:23 +02:00
try {
// Use the instance's own copyright generator
copyrights = instance . getCopyrights ( ) ;
}
catch ( err ) {
// Failed, prevent crashing page.
}
2014-10-30 15:51:20 +01:00
}
2015-05-15 12:21:23 +02:00
if ( copyrights === undefined ) {
2014-10-30 15:51:20 +01:00
// Create a generic flat copyright list
copyrights = new H5P . ContentCopyrights ( ) ;
H5P . findCopyrights ( copyrights , parameters , contentId ) ;
}
2018-04-04 15:46:30 +02:00
var metadataCopyrights = H5P . buildMetadataCopyrights ( metadata , instance . libraryInfo . machineName ) ;
if ( metadataCopyrights !== undefined ) {
copyrights . addMediaInFront ( metadataCopyrights ) ;
}
2014-03-26 08:43:29 +01:00
if ( copyrights !== undefined ) {
2014-10-30 15:51:20 +01:00
// Convert to string
2014-03-26 08:43:29 +01:00
copyrights = copyrights . toString ( ) ;
}
2015-05-11 16:00:55 +02:00
return copyrights ;
2014-03-26 08:43:29 +01:00
} ;
2014-10-30 15:51:20 +01:00
/ * *
* Gather a flat list of copyright information from the given parameters .
*
2015-05-05 13:13:57 +02:00
* @ param { H5P . ContentCopyrights } info
* Used to collect all information in .
* @ param { ( Object | Array ) } parameters
* To search for file objects in .
* @ param { number } contentId
* Used to insert thumbnails for images .
2018-06-28 18:21:30 +02:00
* @ param { Object } extras - Extras .
* @ param { object } extras . metadata - Metadata
* @ param { object } extras . machineName - Library name of some kind .
* Metadata of the content instance .
2014-10-30 15:51:20 +01:00
* /
2018-06-28 18:21:30 +02:00
H5P . findCopyrights = function ( info , parameters , contentId , extras ) {
// If extras are
if ( extras ) {
extras . params = parameters ;
buildFromMetadata ( extras , extras . machineName , contentId ) ;
}
2018-06-01 15:45:09 +02:00
var lastContentTypeName ;
2014-10-30 15:51:20 +01:00
// Cycle through parameters
for ( var field in parameters ) {
if ( ! parameters . hasOwnProperty ( field ) ) {
continue ; // Do not check
}
2016-12-21 11:34:51 +01:00
2017-05-08 14:17:29 +02:00
/ * *
* @ deprecated This hack should be removed after 2017 - 11 - 01
* The code that was using this was removed by HFP - 574
2018-04-04 15:46:30 +02:00
* This note was seen on 2018 - 04 - 04 , and consultation with
* higher authorities lead to keeping the code for now ; - )
2016-12-21 11:34:51 +01:00
* /
if ( field === 'overrideSettings' ) {
2017-05-08 14:17:29 +02:00
console . warn ( "The semantics field 'overrideSettings' is DEPRECATED and should not be used." ) ;
console . warn ( parameters ) ;
2016-12-21 11:34:51 +01:00
continue ;
}
2014-10-30 15:51:20 +01:00
var value = parameters [ field ] ;
2018-06-01 17:44:21 +02:00
if ( value && value . library && typeof value . library === 'string' ) {
2018-06-01 15:45:09 +02:00
lastContentTypeName = value . library . split ( ' ' ) [ 0 ] ;
}
2018-06-01 17:44:21 +02:00
else if ( value && value . library && typeof value . library === 'object' ) {
2018-06-01 15:45:09 +02:00
lastContentTypeName = ( value . library . library && typeof value . library . library === 'string' ) ? value . library . library . split ( ' ' ) [ 0 ] : lastContentTypeName ;
}
2018-04-04 15:46:30 +02:00
2014-10-30 15:51:20 +01:00
if ( value instanceof Array ) {
// Cycle through array
H5P . findCopyrights ( info , value , contentId ) ;
}
else if ( value instanceof Object ) {
2018-06-28 18:21:30 +02:00
buildFromMetadata ( value , lastContentTypeName , contentId ) ;
2018-04-04 15:46:30 +02:00
2018-04-05 17:17:56 +02:00
// Check if object is a file with copyrights (old core)
2014-10-30 15:51:20 +01:00
if ( value . copyright === undefined ||
value . copyright . license === undefined ||
value . path === undefined ||
value . mime === undefined ) {
// Nope, cycle throught object
H5P . findCopyrights ( info , value , contentId ) ;
}
else {
// Found file, add copyrights
var copyrights = new H5P . MediaCopyright ( value . copyright ) ;
if ( value . width !== undefined && value . height !== undefined ) {
copyrights . setThumbnail ( new H5P . Thumbnail ( H5P . getPath ( value . path , contentId ) , value . width , value . height ) ) ;
}
info . addMedia ( copyrights ) ;
}
}
}
2018-06-28 18:21:30 +02:00
2018-10-23 11:25:46 +02:00
function buildFromMetadata ( data , name , contentId ) {
2018-06-28 18:21:30 +02:00
if ( data . metadata ) {
const metadataCopyrights = H5P . buildMetadataCopyrights ( data . metadata , name ) ;
if ( metadataCopyrights !== undefined ) {
if ( data . params && data . params . contentName === 'Image' && data . params . file ) {
const path = data . params . file . path ;
const width = data . params . file . width ;
const height = data . params . file . height ;
metadataCopyrights . setThumbnail ( new H5P . Thumbnail ( H5P . getPath ( path , contentId ) , width , height ) ) ;
}
info . addMedia ( metadataCopyrights ) ;
}
}
}
2014-10-30 15:51:20 +01:00
} ;
2018-10-23 11:25:46 +02:00
H5P . buildMetadataCopyrights = function ( metadata ) {
2018-04-04 15:46:30 +02:00
if ( metadata && metadata . license !== undefined && metadata . license !== 'U' ) {
var dataset = {
2018-09-24 11:03:10 +02:00
contentType : metadata . contentType ,
2018-04-04 15:46:30 +02:00
title : metadata . title ,
2018-10-23 11:25:46 +02:00
author : ( metadata . authors && metadata . authors . length > 0 ) ? metadata . authors . map ( function ( author ) {
2018-06-25 14:14:13 +02:00
return ( author . role ) ? author . name + ' (' + author . role + ')' : author . name ;
} ) . join ( ', ' ) : undefined ,
source : metadata . source ,
2018-04-04 15:46:30 +02:00
year : ( metadata . yearFrom ) ? ( metadata . yearFrom + ( ( metadata . yearTo ) ? '-' + metadata . yearTo : '' ) ) : undefined ,
license : metadata . license ,
version : metadata . licenseVersion ,
licenseExtras : metadata . licenseExtras ,
2018-10-23 11:25:46 +02:00
changes : ( metadata . changes && metadata . changes . length > 0 ) ? metadata . changes . map ( function ( change ) {
2018-04-04 15:46:30 +02:00
return change . log + ( change . author ? ', ' + change . author : '' ) + ( change . date ? ', ' + change . date : '' ) ;
} ) . join ( ' / ' ) : undefined
} ;
2018-09-24 11:03:10 +02:00
return new H5P . MediaCopyright ( dataset ) ;
2018-04-04 15:46:30 +02:00
}
} ;
2019-02-27 15:03:58 +01:00
/ * *
* Display a dialog containing the download button and copy button .
*
* @ param { H5P . jQuery } $element
* @ param { Object } contentData
* @ param { Object } library
* @ param { Object } instance
* @ param { number } contentId
* /
H5P . openReuseDialog = function ( $element , contentData , library , instance , contentId ) {
let html = '' ;
if ( contentData . displayOptions . export ) {
html += '<button type="button" class="h5p-big-button h5p-download-button"><div class="h5p-button-title">Download as an .h5p file</div><div class="h5p-button-description">.h5p files may be uploaded to any web-site where H5P content may be created.</div></button>' ;
}
if ( contentData . displayOptions . export && contentData . displayOptions . copy ) {
html += '<div class="h5p-horizontal-line-text"><span>or</span></div>' ;
}
if ( contentData . displayOptions . copy ) {
html += '<button type="button" class="h5p-big-button h5p-copy-button"><div class="h5p-button-title">Copy content</div><div class="h5p-button-description">Copied content may be pasted anywhere this content type is supported on this website.</div></button>' ;
}
const dialog = new H5P . Dialog ( 'reuse' , H5P . t ( 'reuseContent' ) , html , $element ) ;
// Selecting embed code when dialog is opened
H5P . jQuery ( dialog ) . on ( 'dialog-opened' , function ( e , $dialog ) {
2019-03-01 15:24:46 +01:00
H5P . jQuery ( '<a href="https://h5p.org/node/442225" target="_blank">More Info</a>' ) . click ( function ( e ) {
2019-02-27 15:03:58 +01:00
e . stopPropagation ( ) ;
} ) . appendTo ( $dialog . find ( 'h2' ) ) ;
$dialog . find ( '.h5p-download-button' ) . click ( function ( ) {
window . location . href = contentData . exportUrl ;
instance . triggerXAPI ( 'downloaded' ) ;
2019-03-01 15:24:46 +01:00
dialog . close ( ) ;
2019-02-27 15:03:58 +01:00
} ) ;
$dialog . find ( '.h5p-copy-button' ) . click ( function ( ) {
const item = new H5P . ClipboardItem ( library ) ;
item . contentId = contentId ;
H5P . setClipboard ( item ) ;
instance . triggerXAPI ( 'copied' ) ;
2019-03-01 15:24:46 +01:00
dialog . close ( ) ;
H5P . attachToastTo (
H5P . jQuery ( '.h5p-content:first' ) [ 0 ] ,
H5P . t ( 'contentCopied' ) ,
{
position : {
horizontal : 'centered' ,
vertical : 'centered' ,
noOverflowX : true
}
}
) ;
2019-02-27 15:03:58 +01:00
} ) ;
2019-02-27 16:04:37 +01:00
H5P . trigger ( instance , 'resize' ) ;
} ) . on ( 'dialog-closed' , function ( ) {
H5P . trigger ( instance , 'resize' ) ;
2019-02-27 15:03:58 +01:00
} ) ;
dialog . open ( ) ;
} ;
2014-03-26 08:43:29 +01:00
/ * *
* Display a dialog containing the embed code .
*
2015-05-05 13:13:57 +02:00
* @ param { H5P . jQuery } $element
* Element to insert dialog after .
* @ param { string } embedCode
* The embed code .
* @ param { string } resizeCode
* The advanced resize code
* @ param { Object } size
* The content ' s size .
* @ param { number } size . width
* @ param { number } size . height
2014-03-26 08:43:29 +01:00
* /
2019-02-27 16:04:37 +01:00
H5P . openEmbedDialog = function ( $element , embedCode , resizeCode , size , instance ) {
2015-03-02 15:01:05 +01:00
var fullEmbedCode = embedCode + resizeCode ;
2015-06-25 15:14:55 +02:00
var dialog = new H5P . Dialog ( 'embed' , H5P . t ( 'embed' ) , '<textarea class="h5p-embed-code-container" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>' + H5P . t ( 'size' ) + ': <input type="text" value="' + Math . ceil ( size . width ) + '" class="h5p-embed-size"/> × <input type="text" value="' + Math . ceil ( size . height ) + '" class="h5p-embed-size"/> px<br/><div role="button" tabindex="0" class="h5p-expander">' + H5P . t ( 'showAdvanced' ) + '</div><div class="h5p-expander-content"><p>' + H5P . t ( 'advancedHelp' ) + '</p><textarea class="h5p-embed-code-container" autocorrect="off" autocapitalize="off" spellcheck="false">' + resizeCode + '</textarea></div>' , $element ) ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
// Selecting embed code when dialog is opened
H5P . jQuery ( dialog ) . on ( 'dialog-opened' , function ( event , $dialog ) {
2015-02-25 13:05:45 +01:00
var $inner = $dialog . find ( '.h5p-inner' ) ;
2015-02-27 10:26:57 +01:00
var $scroll = $inner . find ( '.h5p-scroll-content' ) ;
var diff = $scroll . outerHeight ( ) - $scroll . innerHeight ( ) ;
2015-02-25 13:05:45 +01:00
var positionInner = function ( ) {
2019-02-27 16:04:37 +01:00
H5P . trigger ( instance , 'resize' ) ;
2015-02-25 13:05:45 +01:00
} ;
2015-02-25 12:10:07 +01:00
// Handle changing of width/height
var $w = $dialog . find ( '.h5p-embed-size:eq(0)' ) ;
var $h = $dialog . find ( '.h5p-embed-size:eq(1)' ) ;
var getNum = function ( $e , d ) {
var num = parseFloat ( $e . val ( ) ) ;
if ( isNaN ( num ) ) {
return d ;
}
return Math . ceil ( num ) ;
} ;
2015-08-26 15:58:49 +02:00
var updateEmbed = function ( ) {
2015-03-02 15:01:05 +01:00
$dialog . find ( '.h5p-embed-code-container:first' ) . val ( fullEmbedCode . replace ( ':w' , getNum ( $w , size . width ) ) . replace ( ':h' , getNum ( $h , size . height ) ) ) ;
2015-02-25 12:10:07 +01:00
} ;
2015-03-02 15:01:05 +01:00
$w . change ( updateEmbed ) ;
2015-02-25 12:10:07 +01:00
$h . change ( updateEmbed ) ;
updateEmbed ( ) ;
// Select text and expand textareas
2018-10-23 11:25:46 +02:00
$dialog . find ( '.h5p-embed-code-container' ) . each ( function ( ) {
H5P . jQuery ( this ) . css ( 'height' , this . scrollHeight + 'px' ) . focus ( function ( ) {
H5P . jQuery ( this ) . select ( ) ;
} ) ;
2015-03-02 15:01:05 +01:00
} ) ;
$dialog . find ( '.h5p-embed-code-container' ) . eq ( 0 ) . select ( ) ;
positionInner ( ) ;
2015-02-25 12:10:07 +01:00
// Expand advanced embed
2015-08-26 15:58:49 +02:00
var expand = function ( ) {
2015-02-25 12:10:07 +01:00
var $expander = H5P . jQuery ( this ) ;
var $content = $expander . next ( ) ;
if ( $content . is ( ':visible' ) ) {
2019-10-17 15:01:22 +02:00
$expander . removeClass ( 'h5p-open' ) . text ( H5P . t ( 'showAdvanced' ) ) . attr ( 'aria-expanded' , 'true' ) ;
2015-02-25 12:10:07 +01:00
$content . hide ( ) ;
}
else {
2019-10-17 15:01:22 +02:00
$expander . addClass ( 'h5p-open' ) . text ( H5P . t ( 'hideAdvanced' ) ) . attr ( 'aria-expanded' , 'false' ) ;
2015-02-25 12:10:07 +01:00
$content . show ( ) ;
}
2018-10-23 11:25:46 +02:00
$dialog . find ( '.h5p-embed-code-container' ) . each ( function ( ) {
2015-03-02 15:01:05 +01:00
H5P . jQuery ( this ) . css ( 'height' , this . scrollHeight + 'px' ) ;
} ) ;
2015-02-25 13:05:45 +01:00
positionInner ( ) ;
2015-02-27 08:57:02 +01:00
} ;
$dialog . find ( '.h5p-expander' ) . click ( expand ) . keypress ( function ( event ) {
if ( event . keyCode === 32 ) {
2015-02-27 10:26:57 +01:00
expand . apply ( this ) ;
2019-10-17 13:01:55 +02:00
return false ;
2015-02-27 08:57:02 +01:00
}
2015-02-25 12:10:07 +01:00
} ) ;
2019-02-27 16:04:37 +01:00
} ) . on ( 'dialog-closed' , function ( ) {
H5P . trigger ( instance , 'resize' ) ;
2014-03-26 08:43:29 +01:00
} ) ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
dialog . open ( ) ;
} ;
2019-03-01 15:24:46 +01:00
/ * *
* Show a toast message .
*
* The reference element could be dom elements the toast should be attached to ,
* or e . g . the document body for general toast messages .
*
* @ param { DOM } element Reference element to show toast message for .
* @ param { string } message Message to show .
* @ param { object } [ config ] Configuration .
* @ param { string } [ config . style = h5p - toast ] Style name for the tooltip .
* @ param { number } [ config . duration = 3000 ] Toast message length in ms .
* @ param { object } [ config . position ] Relative positioning of the toast .
* @ param { string } [ config . position . horizontal = centered ] [ before | left | centered | right | after ] .
* @ param { string } [ config . position . vertical = below ] [ above | top | centered | bottom | below ] .
* @ param { number } [ config . position . offsetHorizontal = 0 ] Extra horizontal offset .
* @ param { number } [ config . position . offsetVertical = 0 ] Extra vetical offset .
* @ param { boolean } [ config . position . noOverflowLeft = false ] True to prevent overflow left .
* @ param { boolean } [ config . position . noOverflowRight = false ] True to prevent overflow right .
* @ param { boolean } [ config . position . noOverflowTop = false ] True to prevent overflow top .
* @ param { boolean } [ config . position . noOverflowBottom = false ] True to prevent overflow bottom .
* @ param { boolean } [ config . position . noOverflowX = false ] True to prevent overflow left and right .
* @ param { boolean } [ config . position . noOverflowY = false ] True to prevent overflow top and bottom .
* @ param { object } [ config . position . overflowReference = document . body ] DOM reference for overflow .
* /
H5P . attachToastTo = function ( element , message , config ) {
if ( element === undefined || message === undefined ) {
return ;
}
const eventPath = function ( evt ) {
var path = ( evt . composedPath && evt . composedPath ( ) ) || evt . path ;
var target = evt . target ;
if ( path != null ) {
// Safari doesn't include Window, but it should.
return ( path . indexOf ( window ) < 0 ) ? path . concat ( window ) : path ;
}
if ( target === window ) {
return [ window ] ;
}
function getParents ( node , memo ) {
memo = memo || [ ] ;
var parentNode = node . parentNode ;
if ( ! parentNode ) {
return memo ;
}
else {
return getParents ( parentNode , memo . concat ( parentNode ) ) ;
}
}
return [ target ] . concat ( getParents ( target ) , window ) ;
} ;
/ * *
* Handle click while toast is showing .
* /
const clickHandler = function ( event ) {
/ *
* A common use case will be to attach toasts to buttons that are clicked .
* The click would remove the toast message instantly without this check .
* Children of the clicked element are also ignored .
* /
var path = eventPath ( event ) ;
if ( path . indexOf ( element ) !== - 1 ) {
return ;
}
clearTimeout ( timer ) ;
removeToast ( ) ;
} ;
/ * *
* Remove the toast message .
* /
const removeToast = function ( ) {
document . removeEventListener ( 'click' , clickHandler ) ;
if ( toast . parentNode ) {
toast . parentNode . removeChild ( toast ) ;
}
} ;
/ * *
* Get absolute coordinates for the toast .
*
* @ param { DOM } element Reference element to show toast message for .
* @ param { DOM } toast Toast element .
* @ param { object } [ position = { } ] Relative positioning of the toast message .
* @ param { string } [ position . horizontal = centered ] [ before | left | centered | right | after ] .
* @ param { string } [ position . vertical = below ] [ above | top | centered | bottom | below ] .
* @ param { number } [ position . offsetHorizontal = 0 ] Extra horizontal offset .
* @ param { number } [ position . offsetVertical = 0 ] Extra vetical offset .
* @ param { boolean } [ position . noOverflowLeft = false ] True to prevent overflow left .
* @ param { boolean } [ position . noOverflowRight = false ] True to prevent overflow right .
* @ param { boolean } [ position . noOverflowTop = false ] True to prevent overflow top .
* @ param { boolean } [ position . noOverflowBottom = false ] True to prevent overflow bottom .
* @ param { boolean } [ position . noOverflowX = false ] True to prevent overflow left and right .
* @ param { boolean } [ position . noOverflowY = false ] True to prevent overflow top and bottom .
* @ return { object }
* /
const getToastCoordinates = function ( element , toast , position ) {
position = position || { } ;
position . offsetHorizontal = position . offsetHorizontal || 0 ;
position . offsetVertical = position . offsetVertical || 0 ;
const toastRect = toast . getBoundingClientRect ( ) ;
const elementRect = element . getBoundingClientRect ( ) ;
let left = 0 ;
let top = 0 ;
// Compute horizontal position
switch ( position . horizontal ) {
case 'before' :
left = elementRect . left - toastRect . width - position . offsetHorizontal ;
break ;
case 'after' :
left = elementRect . left + elementRect . width + position . offsetHorizontal ;
break ;
case 'left' :
left = elementRect . left + position . offsetHorizontal ;
break ;
case 'right' :
left = elementRect . left + elementRect . width - toastRect . width - position . offsetHorizontal ;
break ;
case 'centered' :
left = elementRect . left + elementRect . width / 2 - toastRect . width / 2 + position . offsetHorizontal ;
break ;
default :
left = elementRect . left + elementRect . width / 2 - toastRect . width / 2 + position . offsetHorizontal ;
}
// Compute vertical position
switch ( position . vertical ) {
case 'above' :
top = elementRect . top - toastRect . height - position . offsetVertical ;
break ;
case 'below' :
top = elementRect . top + elementRect . height + position . offsetVertical ;
break ;
case 'top' :
top = elementRect . top + position . offsetVertical ;
break ;
case 'bottom' :
top = elementRect . top + elementRect . height - toastRect . height - position . offsetVertical ;
break ;
case 'centered' :
top = elementRect . top + elementRect . height / 2 - toastRect . height / 2 + position . offsetVertical ;
break ;
default :
top = elementRect . top + elementRect . height + position . offsetVertical ;
}
// Prevent overflow
const overflowElement = document . body ;
const bounds = overflowElement . getBoundingClientRect ( ) ;
if ( ( position . noOverflowLeft || position . noOverflowX ) && ( left < bounds . x ) ) {
left = bounds . x ;
}
if ( ( position . noOverflowRight || position . noOverflowX ) && ( ( left + toastRect . width ) > ( bounds . x + bounds . width ) ) ) {
left = bounds . x + bounds . width - toastRect . width ;
}
if ( ( position . noOverflowTop || position . noOverflowY ) && ( top < bounds . y ) ) {
top = bounds . y ;
}
if ( ( position . noOverflowBottom || position . noOverflowY ) && ( ( top + toastRect . height ) > ( bounds . y + bounds . height ) ) ) {
left = bounds . y + bounds . height - toastRect . height ;
}
return { left : left , top : top } ;
} ;
// Sanitization
config = config || { } ;
config . style = config . style || 'h5p-toast' ;
config . duration = config . duration || 3000 ;
// Build toast
const toast = document . createElement ( 'div' ) ;
toast . setAttribute ( 'id' , config . style ) ;
toast . classList . add ( 'h5p-toast-disabled' ) ;
toast . classList . add ( config . style ) ;
const msg = document . createElement ( 'span' ) ;
msg . innerHTML = message ;
toast . appendChild ( msg ) ;
document . body . appendChild ( toast ) ;
// The message has to be set before getting the coordinates
const coordinates = getToastCoordinates ( element , toast , config . position ) ;
toast . style . left = Math . round ( coordinates . left ) + 'px' ;
toast . style . top = Math . round ( coordinates . top ) + 'px' ;
toast . classList . remove ( 'h5p-toast-disabled' ) ;
const timer = setTimeout ( removeToast , config . duration ) ;
// The toast can also be removed by clicking somewhere
document . addEventListener ( 'click' , clickHandler ) ;
} ;
2014-03-26 08:43:29 +01:00
/ * *
* Copyrights for a H5P Content Library .
2015-05-05 13:13:57 +02:00
*
* @ class
2014-03-26 08:43:29 +01:00
* /
H5P . ContentCopyrights = function ( ) {
var label ;
var media = [ ] ;
var content = [ ] ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Set label .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ param { string } newLabel
2014-03-26 08:43:29 +01:00
* /
this . setLabel = function ( newLabel ) {
label = newLabel ;
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Add sub content .
2014-03-26 08:43:29 +01:00
*
* @ param { H5P . MediaCopyright } newMedia
* /
this . addMedia = function ( newMedia ) {
if ( newMedia !== undefined ) {
media . push ( newMedia ) ;
}
} ;
2014-10-01 10:05:04 +02:00
2018-04-04 15:46:30 +02:00
/ * *
* Add sub content in front .
*
* @ param { H5P . MediaCopyright } newMedia
* /
this . addMediaInFront = function ( newMedia ) {
if ( newMedia !== undefined ) {
media . unshift ( newMedia ) ;
}
} ;
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Add sub content .
2014-03-26 08:43:29 +01:00
*
* @ param { H5P . ContentCopyrights } newContent
* /
this . addContent = function ( newContent ) {
if ( newContent !== undefined ) {
content . push ( newContent ) ;
}
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Print content copyright .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ returns { string } HTML .
2014-03-26 08:43:29 +01:00
* /
this . toString = function ( ) {
var html = '' ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
// Add media rights
for ( var i = 0 ; i < media . length ; i ++ ) {
html += media [ i ] ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
// Add sub content rights
2014-10-01 10:05:04 +02:00
for ( i = 0 ; i < content . length ; i ++ ) {
2014-03-26 08:43:29 +01:00
html += content [ i ] ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
if ( html !== '' ) {
// Add a label to this info
if ( label !== undefined ) {
html = '<h3>' + label + '</h3>' + html ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
// Add wrapper
html = '<div class="h5p-content-copyrights">' + html + '</div>' ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
return html ;
} ;
2014-05-02 15:45:45 +02:00
} ;
2014-03-26 08:43:29 +01:00
/ * *
* A ordered list of copyright fields for media .
*
2015-05-05 13:13:57 +02:00
* @ class
* @ param { Object } copyright
* Copyright information fields .
* @ param { Object } [ labels ]
* Translation of labels .
* @ param { Array } [ order ]
* Order of the fields .
* @ param { Object } [ extraFields ]
* Add extra copyright fields .
2014-03-26 08:43:29 +01:00
* /
H5P . MediaCopyright = function ( copyright , labels , order , extraFields ) {
var thumbnail ;
var list = new H5P . DefinitionList ( ) ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Get translated label for field .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ private
* @ param { string } fieldName
* @ returns { string }
2014-03-26 08:43:29 +01:00
* /
var getLabel = function ( fieldName ) {
if ( labels === undefined || labels [ fieldName ] === undefined ) {
return H5P . t ( fieldName ) ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
return labels [ fieldName ] ;
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2017-05-30 14:12:22 +02:00
* Get humanized value for the license field .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ private
2017-05-30 14:12:22 +02:00
* @ param { string } license
* @ param { string } [ version ]
2015-05-05 13:13:57 +02:00
* @ returns { string }
2014-03-26 08:43:29 +01:00
* /
2017-05-30 14:12:22 +02:00
var humanizeLicense = function ( license , version ) {
var copyrightLicense = H5P . copyrightLicenses [ license ] ;
// Build license string
var value = '' ;
if ( ! ( license === 'PD' && version ) ) {
// Add license label
2017-05-31 09:33:10 +02:00
value += ( copyrightLicense . hasOwnProperty ( 'label' ) ? copyrightLicense . label : copyrightLicense ) ;
2017-05-30 14:12:22 +02:00
}
// Check for version info
var versionInfo ;
2017-06-06 13:51:51 +02:00
if ( copyrightLicense . versions ) {
2017-06-06 14:21:18 +02:00
if ( copyrightLicense . versions . default && ( ! version || ! copyrightLicense . versions [ version ] ) ) {
2017-06-06 13:51:51 +02:00
version = copyrightLicense . versions . default ;
}
if ( version && copyrightLicense . versions [ version ] ) {
versionInfo = copyrightLicense . versions [ version ] ;
}
2017-05-30 14:12:22 +02:00
}
if ( versionInfo ) {
// Add license version
if ( value ) {
value += ' ' ;
}
2017-05-31 09:33:10 +02:00
value += ( versionInfo . hasOwnProperty ( 'label' ) ? versionInfo . label : versionInfo ) ;
2017-05-30 14:12:22 +02:00
}
// Add link if specified
var link ;
2017-05-31 09:33:10 +02:00
if ( copyrightLicense . hasOwnProperty ( 'link' ) ) {
2017-05-30 14:12:22 +02:00
link = copyrightLicense . link . replace ( ':version' , copyrightLicense . linkVersions ? copyrightLicense . linkVersions [ version ] : version ) ;
}
2017-05-31 09:33:10 +02:00
else if ( versionInfo && copyrightLicense . hasOwnProperty ( 'link' ) ) {
2018-05-25 18:38:42 +02:00
link = versionInfo . link ;
2017-05-30 14:12:22 +02:00
}
if ( link ) {
value = '<a href="' + link + '" target="_blank">' + value + '</a>' ;
}
// Generate parenthesis
var parenthesis = '' ;
if ( license !== 'PD' && license !== 'C' ) {
parenthesis += license ;
}
if ( version && version !== 'CC0 1.0' ) {
if ( parenthesis && license !== 'GNU GPL' ) {
parenthesis += ' ' ;
}
parenthesis += version ;
}
if ( parenthesis ) {
value += ' (' + parenthesis + ')' ;
}
if ( license === 'C' ) {
value += ' ©' ;
2014-03-26 08:43:29 +01:00
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
return value ;
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
if ( copyright !== undefined ) {
// Add the extra fields
for ( var field in extraFields ) {
if ( extraFields . hasOwnProperty ( field ) ) {
copyright [ field ] = extraFields [ field ] ;
}
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
if ( order === undefined ) {
// Set default order
2018-09-24 11:03:10 +02:00
order = [ 'contentType' , 'title' , 'license' , 'author' , 'year' , 'source' , 'licenseExtras' , 'changes' ] ;
2014-03-26 08:43:29 +01:00
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
for ( var i = 0 ; i < order . length ; i ++ ) {
var fieldName = order [ i ] ;
2018-10-23 11:10:06 +02:00
if ( copyright [ fieldName ] !== undefined && copyright [ fieldName ] !== '' ) {
2017-05-30 14:12:22 +02:00
var humanValue = copyright [ fieldName ] ;
if ( fieldName === 'license' ) {
humanValue = humanizeLicense ( copyright . license , copyright . version ) ;
}
2018-06-25 14:14:13 +02:00
if ( fieldName === 'source' ) {
humanValue = ( humanValue ) ? '<a href="' + humanValue + '" target="_blank">' + humanValue + '</a>' : undefined ;
}
2017-05-30 14:12:22 +02:00
list . add ( new H5P . Field ( getLabel ( fieldName ) , humanValue ) ) ;
2014-03-26 08:43:29 +01:00
}
}
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Set thumbnail .
2014-03-26 08:43:29 +01:00
*
* @ param { H5P . Thumbnail } newThumbnail
* /
this . setThumbnail = function ( newThumbnail ) {
thumbnail = newThumbnail ;
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Checks if this copyright is undisclosed .
2014-03-26 08:43:29 +01:00
* I . e . only has the license attribute set , and it ' s undisclosed .
*
2015-05-05 13:13:57 +02:00
* @ returns { boolean }
2014-03-26 08:43:29 +01:00
* /
this . undisclosed = function ( ) {
if ( list . size ( ) === 1 ) {
var field = list . get ( 0 ) ;
2017-05-30 14:12:22 +02:00
if ( field . getLabel ( ) === getLabel ( 'license' ) && field . getValue ( ) === humanizeLicense ( 'U' ) ) {
2014-03-26 08:43:29 +01:00
return true ;
}
}
return false ;
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Print media copyright .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ returns { string } HTML .
2014-03-26 08:43:29 +01:00
* /
this . toString = function ( ) {
var html = '' ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
if ( this . undisclosed ( ) ) {
return html ; // No need to print a copyright with a single undisclosed license.
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
if ( thumbnail !== undefined ) {
html += thumbnail ;
}
html += list ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
if ( html !== '' ) {
html = '<div class="h5p-media-copyright">' + html + '</div>' ;
}
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
return html ;
} ;
2014-05-02 15:45:45 +02:00
} ;
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* A simple and elegant class for creating thumbnails of images .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ class
* @ param { string } source
* @ param { number } width
* @ param { number } height
2014-03-26 08:43:29 +01:00
* /
H5P . Thumbnail = function ( source , width , height ) {
var thumbWidth , thumbHeight = 100 ;
if ( width !== undefined ) {
thumbWidth = Math . round ( thumbHeight * ( width / height ) ) ;
}
/ * *
2015-05-05 13:13:57 +02:00
* Print thumbnail .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ returns { string } HTML .
2014-03-26 08:43:29 +01:00
* /
this . toString = function ( ) {
return '<img src="' + source + '" alt="' + H5P . t ( 'thumbnail' ) + '" class="h5p-thumbnail" height="' + thumbHeight + '"' + ( thumbWidth === undefined ? '' : ' width="' + thumbWidth + '"' ) + '/>' ;
} ;
2014-05-02 15:45:45 +02:00
} ;
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Simple data structure class for storing a single field .
*
* @ class
* @ param { string } label
* @ param { string } value
2014-03-26 08:43:29 +01:00
* /
H5P . Field = function ( label , value ) {
/ * *
* Public . Get field label .
*
* @ returns { String }
2014-10-01 10:05:04 +02:00
* /
2014-03-26 08:43:29 +01:00
this . getLabel = function ( ) {
return label ;
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
* Public . Get field value .
*
* @ returns { String }
2014-10-01 10:05:04 +02:00
* /
2014-03-26 08:43:29 +01:00
this . getValue = function ( ) {
return value ;
} ;
2014-05-02 15:45:45 +02:00
} ;
2014-03-26 08:43:29 +01:00
/ * *
* Simple class for creating a definition list .
2015-05-05 13:13:57 +02:00
*
* @ class
2014-03-26 08:43:29 +01:00
* /
H5P . DefinitionList = function ( ) {
var fields = [ ] ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Add field to list .
2014-03-26 08:43:29 +01:00
*
* @ param { H5P . Field } field
* /
this . add = function ( field ) {
fields . push ( field ) ;
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Get Number of fields .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ returns { number }
2014-03-26 08:43:29 +01:00
* /
this . size = function ( ) {
return fields . length ;
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Get field at given index .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ param { number } index
* @ returns { H5P . Field }
2014-03-26 08:43:29 +01:00
* /
this . get = function ( index ) {
return fields [ index ] ;
} ;
2014-10-01 10:05:04 +02:00
2014-03-26 08:43:29 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Print definition list .
2014-03-26 08:43:29 +01:00
*
2015-05-05 13:13:57 +02:00
* @ returns { string } HTML .
2014-03-26 08:43:29 +01:00
* /
this . toString = function ( ) {
var html = '' ;
for ( var i = 0 ; i < fields . length ; i ++ ) {
var field = fields [ i ] ;
html += '<dt>' + field . getLabel ( ) + '</dt><dd>' + field . getValue ( ) + '</dd>' ;
}
return ( html === '' ? html : '<dl class="h5p-definition-list">' + html + '</dl>' ) ;
} ;
2014-05-02 15:45:45 +02:00
} ;
2014-03-26 08:43:29 +01:00
/ * *
* THIS FUNCTION / CLASS IS DEPRECATED AND WILL BE REMOVED .
*
* Helper object for keeping coordinates in the same format all over .
2015-05-05 13:13:57 +02:00
*
* @ deprecated
* Will be removed march 2016.
* @ class
* @ param { number } x
* @ param { number } y
* @ param { number } w
* @ param { number } h
2014-03-26 08:43:29 +01:00
* /
2013-11-26 14:54:29 +01:00
H5P . Coords = function ( x , y , w , h ) {
2013-01-17 09:01:43 +01:00
if ( ! ( this instanceof H5P . Coords ) )
return new H5P . Coords ( x , y , w , h ) ;
2015-05-05 13:13:57 +02:00
/** @member {number} */
2013-01-17 09:01:43 +01:00
this . x = 0 ;
2015-05-05 13:13:57 +02:00
/** @member {number} */
2013-01-17 09:01:43 +01:00
this . y = 0 ;
2015-05-05 13:13:57 +02:00
/** @member {number} */
2013-01-17 09:01:43 +01:00
this . w = 1 ;
2015-05-05 13:13:57 +02:00
/** @member {number} */
2013-01-17 09:01:43 +01:00
this . h = 1 ;
2013-04-02 17:05:16 +02:00
if ( typeof ( x ) === 'object' ) {
2013-01-17 09:01:43 +01:00
this . x = x . x ;
this . y = x . y ;
this . w = x . w ;
this . h = x . h ;
2018-10-23 11:25:46 +02:00
}
else {
2013-01-17 09:01:43 +01:00
if ( x !== undefined ) {
this . x = x ;
}
if ( y !== undefined ) {
this . y = y ;
}
if ( w !== undefined ) {
this . w = w ;
}
if ( h !== undefined ) {
this . h = h ;
2013-01-25 14:38:12 +01:00
}
2013-01-17 09:01:43 +01:00
}
return this ;
} ;
2013-03-06 15:59:02 +01:00
/ * *
2013-11-26 14:54:29 +01:00
* Parse library string into values .
*
* @ param { string } library
2015-05-05 13:13:57 +02:00
* library in the format "machineName majorVersion.minorVersion"
* @ returns { Object }
* library as an object with machineName , majorVersion and minorVersion properties
* return false if the library parameter is invalid
2013-03-06 15:59:02 +01:00
* /
H5P . libraryFromString = function ( library ) {
2016-04-21 13:22:01 +02:00
var regExp = /(.+)\s(\d+)\.(\d+)$/g ;
2013-03-06 15:59:02 +01:00
var res = regExp . exec ( library ) ;
if ( res !== null ) {
return {
'machineName' : res [ 1 ] ,
2018-09-10 11:30:57 +02:00
'majorVersion' : parseInt ( res [ 2 ] ) ,
'minorVersion' : parseInt ( res [ 3 ] )
2013-03-06 15:59:02 +01:00
} ;
}
else {
return false ;
}
} ;
2013-02-07 17:50:17 +01:00
2013-07-08 08:59:14 +02:00
/ * *
* Get the path to the library
2013-07-18 15:58:52 +02:00
*
2015-05-05 13:13:57 +02:00
* @ param { string } library
2014-03-26 08:43:29 +01:00
* The library identifier in the format "machineName-majorVersion.minorVersion" .
2015-05-05 13:13:57 +02:00
* @ returns { string }
* The full path to the library .
2013-07-08 08:59:14 +02:00
* /
2013-11-26 14:54:29 +01:00
H5P . getLibraryPath = function ( library ) {
2019-03-12 12:47:31 +01:00
if ( H5PIntegration . urlLibraries !== undefined ) {
// This is an override for those implementations that has a different libraries URL, e.g. Moodle
return H5PIntegration . urlLibraries + '/' + library ;
}
else {
return H5PIntegration . url + '/libraries/' + library ;
}
2013-07-08 08:59:14 +02:00
} ;
2013-04-04 14:58:17 +02:00
/ * *
* Recursivly clone the given object .
2013-04-26 17:27:35 +02:00
*
2015-05-05 13:13:57 +02:00
* @ param { Object | Array } object
* Object to clone .
* @ param { boolean } [ recursive ]
* @ returns { Object | Array }
* A clone of object .
2013-04-04 14:58:17 +02:00
* /
2013-05-23 13:00:58 +02:00
H5P . cloneObject = function ( object , recursive ) {
2015-05-05 13:13:57 +02:00
// TODO: Consider if this needs to be in core. Doesn't $.extend do the same?
2013-05-23 13:00:58 +02:00
var clone = object instanceof Array ? [ ] : { } ;
2013-04-26 17:27:35 +02:00
2013-04-04 14:58:17 +02:00
for ( var i in object ) {
if ( object . hasOwnProperty ( i ) ) {
if ( recursive !== undefined && recursive && typeof object [ i ] === 'object' ) {
2013-05-23 13:00:58 +02:00
clone [ i ] = H5P . cloneObject ( object [ i ] , recursive ) ;
2013-04-04 14:58:17 +02:00
}
else {
clone [ i ] = object [ i ] ;
}
}
2013-04-18 09:51:58 +02:00
}
2013-04-26 17:27:35 +02:00
2013-04-18 09:51:58 +02:00
return clone ;
2013-04-04 14:58:17 +02:00
} ;
2013-04-30 17:07:14 +02:00
/ * *
* Remove all empty spaces before and after the value .
*
2015-05-05 13:13:57 +02:00
* @ param { string } value
* @ returns { string }
2013-04-30 17:07:14 +02:00
* /
H5P . trim = function ( value ) {
return value . replace ( /^\s+|\s+$/g , '' ) ;
2015-05-05 13:13:57 +02:00
// TODO: Only include this or String.trim(). What is best?
// I'm leaning towards implementing the missing ones: http://kangax.github.io/compat-table/es5/
// So should we make this function deprecated?
2013-04-30 17:07:14 +02:00
} ;
2013-05-24 13:24:01 +02:00
/ * *
2015-05-05 13:13:57 +02:00
* Check if JavaScript path / key is loaded .
2013-05-24 13:24:01 +02:00
*
2015-05-05 13:13:57 +02:00
* @ param { string } path
* @ returns { boolean }
2013-05-24 13:24:01 +02:00
* /
H5P . jsLoaded = function ( path ) {
2015-02-27 13:59:42 +01:00
H5PIntegration . loadedJs = H5PIntegration . loadedJs || [ ] ;
return H5P . jQuery . inArray ( path , H5PIntegration . loadedJs ) !== - 1 ;
2013-05-24 13:24:01 +02:00
} ;
/ * *
* Check if styles path / key is loaded .
*
2015-05-05 13:13:57 +02:00
* @ param { string } path
* @ returns { boolean }
2013-05-24 13:24:01 +02:00
* /
H5P . cssLoaded = function ( path ) {
2015-02-27 13:59:42 +01:00
H5PIntegration . loadedCss = H5PIntegration . loadedCss || [ ] ;
return H5P . jQuery . inArray ( path , H5PIntegration . loadedCss ) !== - 1 ;
2013-05-24 13:24:01 +02:00
} ;
2013-11-26 14:54:29 +01:00
/ * *
* Shuffle an array in place .
*
2015-05-05 13:13:57 +02:00
* @ param { Array } array
* Array to shuffle
* @ returns { Array }
* The passed array is returned for chaining .
2013-11-26 14:54:29 +01:00
* /
H5P . shuffleArray = function ( array ) {
2015-05-05 13:13:57 +02:00
// TODO: Consider if this should be a part of core. I'm guessing very few libraries are going to use it.
2015-04-22 09:51:36 +02:00
if ( ! ( array instanceof Array ) ) {
2013-11-26 14:54:29 +01:00
return ;
}
2013-07-25 01:50:16 +02:00
var i = array . length , j , tempi , tempj ;
2013-01-17 09:01:43 +01:00
if ( i === 0 ) return false ;
while ( -- i ) {
2013-01-25 14:38:12 +01:00
j = Math . floor ( Math . random ( ) * ( i + 1 ) ) ;
2013-07-25 01:50:16 +02:00
tempi = array [ i ] ;
tempj = array [ j ] ;
array [ i ] = tempj ;
array [ j ] = tempi ;
2013-01-17 09:01:43 +01:00
}
2013-07-25 01:50:16 +02:00
return array ;
2013-01-17 09:01:43 +01:00
} ;
2013-10-21 13:22:13 +02:00
/ * *
* Post finished results for user .
*
2015-05-05 13:13:57 +02:00
* @ deprecated
* Do not use this function directly , trigger the finish event instead .
* Will be removed march 2016
* @ param { number } contentId
* Identifies the content
* @ param { number } score
* Achieved score / points
* @ param { number } maxScore
* The maximum score / points that can be achieved
* @ param { number } [ time ]
* Reported time consumption / usage
2013-10-21 13:22:13 +02:00
* /
2014-10-15 11:38:29 +02:00
H5P . setFinished = function ( contentId , score , maxScore , time ) {
2016-11-19 12:29:38 +01:00
var validScore = typeof score === 'number' || score instanceof Number ;
if ( validScore && H5PIntegration . postUserStatistics === true ) {
2014-10-15 11:38:29 +02:00
/ * *
* Return unix timestamp for the given JS Date .
*
2015-05-05 13:13:57 +02:00
* @ private
2014-10-15 11:38:29 +02:00
* @ param { Date } date
* @ returns { Number }
* /
var toUnix = function ( date ) {
return Math . round ( date . getTime ( ) / 1000 ) ;
} ;
// Post the results
2019-03-18 10:58:13 +01:00
const data = {
2014-10-15 11:38:29 +02:00
contentId : contentId ,
score : score ,
maxScore : maxScore ,
opened : toUnix ( H5P . opened [ contentId ] ) ,
finished : toUnix ( new Date ( ) ) ,
2016-06-13 10:31:09 +02:00
time : time
2019-03-18 10:58:13 +01:00
} ;
H5P . jQuery . post ( H5PIntegration . ajax . setFinished , data )
. fail ( function ( ) {
2019-04-08 10:23:20 +02:00
H5P . offlineRequestQueue . add ( H5PIntegration . ajax . setFinished , data ) ;
2019-03-18 10:58:13 +01:00
} ) ;
2013-10-21 13:22:13 +02:00
}
} ;
2013-01-17 09:01:43 +01:00
// Add indexOf to browsers that lack them. (IEs)
2013-11-08 18:16:41 +01:00
if ( ! Array . prototype . indexOf ) {
Array . prototype . indexOf = function ( needle ) {
for ( var i = 0 ; i < this . length ; i ++ ) {
if ( this [ i ] === needle ) {
2013-01-25 14:38:12 +01:00
return i ;
}
}
return - 1 ;
} ;
2013-01-17 09:01:43 +01:00
}
2013-07-09 10:36:27 +02:00
// Need to define trim() since this is not available on older IEs,
// and trim is used in several libs
2013-11-08 18:16:41 +01:00
if ( String . prototype . trim === undefined ) {
2013-07-09 10:36:27 +02:00
String . prototype . trim = function ( ) {
return H5P . trim ( this ) ;
} ;
}
2015-02-04 18:43:06 +01:00
/ * *
* Trigger an event on an instance
2015-02-20 10:15:36 +01:00
*
2015-02-04 18:43:06 +01:00
* Helper function that triggers an event if the instance supports event handling
2015-02-20 10:15:36 +01:00
*
2015-05-05 13:13:57 +02:00
* @ param { Object } instance
* Instance of H5P content
2015-02-04 18:43:06 +01:00
* @ param { string } eventType
2015-05-05 13:13:57 +02:00
* Type of event to trigger
* @ param { * } data
* @ param { Object } extras
2015-02-04 18:43:06 +01:00
* /
2015-05-05 13:13:57 +02:00
H5P . trigger = function ( instance , eventType , data , extras ) {
2015-02-04 18:43:06 +01:00
// Try new event system first
2015-01-14 18:08:36 +01:00
if ( instance . trigger !== undefined ) {
2015-04-07 19:32:44 +02:00
instance . trigger ( eventType , data , extras ) ;
2015-01-14 18:08:36 +01:00
}
2015-02-04 18:43:06 +01:00
// Try deprecated event system
else if ( instance . $ !== undefined && instance . $ . trigger !== undefined ) {
2015-02-20 10:26:33 +01:00
instance . $ . trigger ( eventType ) ;
2015-01-14 18:08:36 +01:00
}
2015-02-04 18:43:06 +01:00
} ;
2015-01-14 18:08:36 +01:00
2015-02-04 18:43:06 +01:00
/ * *
* Register an event handler
2015-02-20 10:15:36 +01:00
*
2015-02-04 18:43:06 +01:00
* Helper function that registers an event handler for an event type if
* the instance supports event handling
2015-02-20 10:15:36 +01:00
*
2015-05-05 13:13:57 +02:00
* @ param { Object } instance
* Instance of H5P content
2015-02-04 18:43:06 +01:00
* @ param { string } eventType
2015-05-05 13:13:57 +02:00
* Type of event to listen for
* @ param { H5P . EventCallback } handler
* Callback that gets triggered for events of the specified type
2015-02-04 18:43:06 +01:00
* /
2015-05-05 13:13:57 +02:00
H5P . on = function ( instance , eventType , handler ) {
2015-02-04 18:43:06 +01:00
// Try new event system first
2015-01-14 18:08:36 +01:00
if ( instance . on !== undefined ) {
2015-02-04 18:43:06 +01:00
instance . on ( eventType , handler ) ;
2015-01-14 18:08:36 +01:00
}
2015-02-04 18:43:06 +01:00
// Try deprecated event system
else if ( instance . $ !== undefined && instance . $ . on !== undefined ) {
2015-02-20 10:26:33 +01:00
instance . $ . on ( eventType , handler ) ;
2015-01-14 18:08:36 +01:00
}
2015-02-19 15:50:04 +01:00
} ;
2015-03-10 09:53:33 +01:00
2015-03-21 14:16:31 +01:00
/ * *
2015-05-05 13:13:57 +02:00
* Generate random UUID
2015-04-07 19:32:44 +02:00
*
2015-05-05 13:13:57 +02:00
* @ returns { string } UUID
2015-03-21 14:16:31 +01:00
* /
2015-05-05 13:13:57 +02:00
H5P . createUUID = function ( ) {
2018-10-23 11:25:46 +02:00
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' . replace ( /[xy]/g , function ( char ) {
2015-03-21 14:16:31 +01:00
var random = Math . random ( ) * 16 | 0 , newChar = char === 'x' ? random : ( random & 0x3 | 0x8 ) ;
return newChar . toString ( 16 ) ;
} ) ;
} ;
2015-05-05 13:13:57 +02:00
/ * *
* Create title
*
* @ param { string } rawTitle
* @ param { number } maxLength
* @ returns { string }
* /
H5P . createTitle = function ( rawTitle , maxLength ) {
2015-04-15 13:54:46 +02:00
if ( ! rawTitle ) {
return '' ;
}
2015-03-22 12:45:03 +01:00
if ( maxLength === undefined ) {
maxLength = 60 ;
}
var title = H5P . jQuery ( '<div></div>' )
. text (
// Strip tags
rawTitle . replace ( /(<([^>]+)>)/ig , "" )
// Escape
) . text ( ) ;
if ( title . length > maxLength ) {
title = title . substr ( 0 , maxLength - 3 ) + '...' ;
}
return title ;
2015-03-21 16:45:38 +01:00
} ;
2015-03-10 09:53:33 +01:00
2015-04-07 19:32:44 +02:00
// Wrap in privates
( function ( $ ) {
/ * *
* Creates ajax requests for inserting , updateing and deleteing
* content user data .
*
* @ private
* @ param { number } contentId What content to store the data for .
* @ param { string } dataType Identifies the set of data for this content .
* @ param { string } subContentId Identifies sub content
* @ param { function } [ done ] Callback when ajax is done .
* @ param { object } [ data ] To be stored for future use .
* @ param { boolean } [ preload = false ] Data is loaded when content is loaded .
* @ param { boolean } [ invalidate = false ] Data is invalidated when content changes .
* @ param { boolean } [ async = true ]
* /
function contentUserDataAjax ( contentId , dataType , subContentId , done , data , preload , invalidate , async ) {
2015-06-23 11:12:29 +02:00
if ( H5PIntegration . user === undefined ) {
// Not logged in, no use in saving.
done ( 'Not signed in.' ) ;
return ;
}
2015-04-07 19:32:44 +02:00
var options = {
2015-04-20 10:30:52 +02:00
url : H5PIntegration . ajax . contentUserData . replace ( ':contentId' , contentId ) . replace ( ':dataType' , dataType ) . replace ( ':subContentId' , subContentId ? subContentId : 0 ) ,
2015-04-07 19:32:44 +02:00
dataType : 'json' ,
async : async === undefined ? true : async
} ;
if ( data !== undefined ) {
options . type = 'POST' ;
options . data = {
data : ( data === null ? 0 : data ) ,
preload : ( preload ? 1 : 0 ) ,
2016-06-13 10:31:09 +02:00
invalidate : ( invalidate ? 1 : 0 )
2015-04-07 19:32:44 +02:00
} ;
}
else {
options . type = 'GET' ;
}
if ( done !== undefined ) {
2015-08-26 15:58:49 +02:00
options . error = function ( xhr , error ) {
2015-04-07 19:32:44 +02:00
done ( error ) ;
} ;
options . success = function ( response ) {
if ( ! response . success ) {
2016-02-22 12:01:18 +01:00
done ( response . message ) ;
2015-04-07 19:32:44 +02:00
return ;
}
if ( response . data === false || response . data === undefined ) {
done ( ) ;
return ;
}
done ( undefined , response . data ) ;
} ;
}
$ . ajax ( options ) ;
2015-03-10 09:53:33 +01:00
}
2015-04-07 19:32:44 +02:00
/ * *
* Get user data for given content .
*
2015-05-05 13:13:57 +02:00
* @ param { number } contentId
* What content to get data for .
* @ param { string } dataId
* Identifies the set of data for this content .
* @ param { function } done
* Callback with error and data parameters .
* @ param { string } [ subContentId ]
* Identifies which data belongs to sub content .
2015-04-07 19:32:44 +02:00
* /
H5P . getUserData = function ( contentId , dataId , done , subContentId ) {
if ( ! subContentId ) {
subContentId = 0 ; // Default
}
2015-11-04 17:30:58 +01:00
H5PIntegration . contents = H5PIntegration . contents || { } ;
var content = H5PIntegration . contents [ 'cid-' + contentId ] || { } ;
2015-04-07 19:32:44 +02:00
var preloadedData = content . contentUserData ;
2016-07-15 16:14:32 +02:00
if ( preloadedData && preloadedData [ subContentId ] && preloadedData [ subContentId ] [ dataId ] !== undefined ) {
2015-04-07 19:32:44 +02:00
if ( preloadedData [ subContentId ] [ dataId ] === 'RESET' ) {
done ( undefined , null ) ;
return ;
}
try {
done ( undefined , JSON . parse ( preloadedData [ subContentId ] [ dataId ] ) ) ;
}
catch ( err ) {
done ( err ) ;
}
}
else {
2015-08-26 15:58:49 +02:00
contentUserDataAjax ( contentId , dataId , subContentId , function ( err , data ) {
2015-04-07 19:32:44 +02:00
if ( err || data === undefined ) {
done ( err , data ) ;
return ; // Error or no data
}
// Cache in preloaded
if ( content . contentUserData === undefined ) {
2015-04-22 10:58:59 +02:00
content . contentUserData = preloadedData = { } ;
2015-04-07 19:32:44 +02:00
}
if ( preloadedData [ subContentId ] === undefined ) {
preloadedData [ subContentId ] = { } ;
}
preloadedData [ subContentId ] [ dataId ] = data ;
// Done. Try to decode JSON
try {
done ( undefined , JSON . parse ( data ) ) ;
}
catch ( e ) {
done ( e ) ;
}
} ) ;
}
} ;
2015-05-05 13:13:57 +02:00
/ * *
* Async error handling .
*
* @ callback H5P . ErrorCallback
* @ param { * } error
* /
2015-04-07 19:32:44 +02:00
/ * *
* Set user data for given content .
*
2015-05-05 13:13:57 +02:00
* @ param { number } contentId
* What content to get data for .
* @ param { string } dataId
* Identifies the set of data for this content .
* @ param { Object } data
* The data that is to be stored .
* @ param { Object } [ extras ]
* Extra properties
* @ param { string } [ extras . subContentId ]
* Identifies which data belongs to sub content .
* @ param { boolean } [ extras . preloaded = true ]
* If the data should be loaded when content is loaded .
* @ param { boolean } [ extras . deleteOnChange = false ]
* If the data should be invalidated when the content changes .
* @ param { H5P . ErrorCallback } [ extras . errorCallback ]
* Callback with error as parameters .
* @ param { boolean } [ extras . async = true ]
2015-04-07 19:32:44 +02:00
* /
2015-04-09 14:00:00 +02:00
H5P . setUserData = function ( contentId , dataId , data , extras ) {
var options = H5P . jQuery . extend ( true , { } , {
subContentId : 0 ,
preloaded : true ,
deleteOnChange : false ,
async : true
} , extras ) ;
2015-04-07 19:32:44 +02:00
try {
data = JSON . stringify ( data ) ;
}
catch ( err ) {
2015-04-20 10:30:52 +02:00
if ( options . errorCallback ) {
2015-04-09 14:00:00 +02:00
options . errorCallback ( err ) ;
}
2015-04-07 19:32:44 +02:00
return ; // Failed to serialize.
}
var content = H5PIntegration . contents [ 'cid-' + contentId ] ;
2015-11-04 17:30:58 +01:00
if ( content === undefined ) {
content = H5PIntegration . contents [ 'cid-' + contentId ] = { } ;
}
2015-04-07 19:32:44 +02:00
if ( ! content . contentUserData ) {
content . contentUserData = { } ;
}
var preloadedData = content . contentUserData ;
2015-04-09 14:00:00 +02:00
if ( preloadedData [ options . subContentId ] === undefined ) {
preloadedData [ options . subContentId ] = { } ;
2015-04-07 19:32:44 +02:00
}
2015-04-09 14:00:00 +02:00
if ( data === preloadedData [ options . subContentId ] [ dataId ] ) {
2015-04-07 19:32:44 +02:00
return ; // No need to save this twice.
}
2015-04-09 14:00:00 +02:00
preloadedData [ options . subContentId ] [ dataId ] = data ;
2018-10-23 11:25:46 +02:00
contentUserDataAjax ( contentId , dataId , options . subContentId , function ( error ) {
2015-04-09 14:00:00 +02:00
if ( options . errorCallback && error ) {
options . errorCallback ( error ) ;
2015-04-07 19:32:44 +02:00
}
2015-04-09 14:00:00 +02:00
} , data , options . preloaded , options . deleteOnChange , options . async ) ;
2015-04-07 19:32:44 +02:00
} ;
/ * *
* Delete user data for given content .
*
2015-05-05 13:13:57 +02:00
* @ param { number } contentId
* What content to remove data for .
* @ param { string } dataId
* Identifies the set of data for this content .
* @ param { string } [ subContentId ]
* Identifies which data belongs to sub content .
2015-04-07 19:32:44 +02:00
* /
H5P . deleteUserData = function ( contentId , dataId , subContentId ) {
if ( ! subContentId ) {
subContentId = 0 ; // Default
}
// Remove from preloaded/cache
var preloadedData = H5PIntegration . contents [ 'cid-' + contentId ] . contentUserData ;
if ( preloadedData && preloadedData [ subContentId ] && preloadedData [ subContentId ] [ dataId ] ) {
delete preloadedData [ subContentId ] [ dataId ] ;
}
contentUserDataAjax ( contentId , dataId , subContentId , undefined , null ) ;
} ;
2018-08-30 12:35:46 +02:00
/ * *
* Function for getting content for a certain ID
*
* @ param { number } contentId
* @ return { Object }
* /
H5P . getContentForInstance = function ( contentId ) {
var key = 'cid-' + contentId ;
var exists = H5PIntegration && H5PIntegration . contents &&
H5PIntegration . contents [ key ] ;
return exists ? H5PIntegration . contents [ key ] : undefined ;
} ;
2018-06-25 15:59:17 +02:00
/ * *
* Prepares the content parameters for storing in the clipboard .
*
* @ class
* @ param { Object } parameters The parameters for the content to store
* @ param { string } [ genericProperty ] If only part of the parameters are generic , which part
* @ param { string } [ specificKey ] If the parameters are specific , what content type does it fit
* @ returns { Object } Ready for the clipboard
* /
H5P . ClipboardItem = function ( parameters , genericProperty , specificKey ) {
2018-06-26 11:52:57 +02:00
var self = this ;
2018-06-25 15:59:17 +02:00
2018-06-26 11:52:57 +02:00
/ * *
* Set relative dimensions when params contains a file with a width and a height .
* Very useful to be compatible with wysiwyg editors .
*
* @ private
* /
var setDimensionsFromFile = function ( ) {
if ( ! self . generic ) {
return ;
}
var params = self . specific [ self . generic ] ;
if ( ! params . params . file || ! params . params . file . width || ! params . params . file . height ) {
return ;
2018-06-25 15:59:17 +02:00
}
2018-06-26 11:52:57 +02:00
self . width = 20 ; // %
self . height = ( params . params . file . height / params . params . file . width ) * self . width ;
2018-07-18 10:45:14 +02:00
} ;
2018-06-26 11:52:57 +02:00
if ( ! genericProperty ) {
genericProperty = 'action' ;
parameters = {
action : parameters
} ;
}
self . specific = parameters ;
if ( genericProperty && parameters [ genericProperty ] ) {
self . generic = genericProperty ;
}
if ( specificKey ) {
self . from = specificKey ;
2018-06-25 15:59:17 +02:00
}
if ( window . H5PEditor && H5PEditor . contentId ) {
2018-06-26 11:52:57 +02:00
self . contentId = H5PEditor . contentId ;
}
if ( ! self . specific . width && ! self . specific . height ) {
setDimensionsFromFile ( ) ;
2018-06-25 15:59:17 +02:00
}
} ;
/ * *
* Store item in the H5P Clipboard .
*
* @ param { H5P . ClipboardItem | * } clipboardItem
* /
H5P . clipboardify = function ( clipboardItem ) {
if ( ! ( clipboardItem instanceof H5P . ClipboardItem ) ) {
clipboardItem = new H5P . ClipboardItem ( clipboardItem ) ;
}
2018-07-18 10:45:14 +02:00
H5P . setClipboard ( clipboardItem ) ;
2018-06-25 15:59:17 +02:00
} ;
2018-06-26 11:52:57 +02:00
/ * *
* Retrieve parsed clipboard data .
2018-06-25 15:59:17 +02:00
*
* @ return { Object }
* /
H5P . getClipboard = function ( ) {
2019-01-03 12:50:44 +01:00
return parseClipboard ( ) ;
2018-07-18 10:45:14 +02:00
} ;
/ * *
* Set item in the H5P Clipboard .
*
* @ param { H5P . ClipboardItem | object } clipboardItem - Data to be set .
* /
H5P . setClipboard = function ( clipboardItem ) {
localStorage . setItem ( 'h5pClipboard' , JSON . stringify ( clipboardItem ) ) ;
// Trigger an event so all 'Paste' buttons may be enabled.
H5P . externalDispatcher . trigger ( 'datainclipboard' , { reset : false } ) ;
} ;
2018-06-26 11:52:57 +02:00
2018-08-27 14:53:17 +02:00
/ * *
* Get config for a library
*
* @ param string machineName
* @ return Object
* /
H5P . getLibraryConfig = function ( machineName ) {
var hasConfig = H5PIntegration . libraryConfig && H5PIntegration . libraryConfig [ machineName ] ;
return hasConfig ? H5PIntegration . libraryConfig [ machineName ] : { } ;
} ;
2018-06-26 11:52:57 +02:00
/ * *
* Get item from the H5P Clipboard .
*
* @ private
* @ return { Object }
* /
var parseClipboard = function ( ) {
2018-06-25 15:59:17 +02:00
var clipboardData = localStorage . getItem ( 'h5pClipboard' ) ;
if ( ! clipboardData ) {
return ;
}
// Try to parse clipboard dat
try {
clipboardData = JSON . parse ( clipboardData ) ;
}
catch ( err ) {
console . error ( 'Unable to parse JSON from clipboard.' , err ) ;
return ;
}
2019-01-03 12:46:16 +01:00
// Update file URLs and reset content Ids
recursiveUpdate ( clipboardData . specific , function ( path ) {
2018-06-25 15:59:17 +02:00
var isTmpFile = ( path . substr ( - 4 , 4 ) === '#tmp' ) ;
2019-05-08 14:16:22 +02:00
if ( ! isTmpFile && clipboardData . contentId && ! path . match ( /^https?:\/\//i ) ) {
2018-06-25 15:59:17 +02:00
// Comes from existing content
if ( H5PEditor . contentId ) {
// .. to existing content
return '../' + clipboardData . contentId + '/' + path ;
}
else {
// .. to new content
return ( H5PEditor . contentRelUrl ? H5PEditor . contentRelUrl : '../content/' ) + clipboardData . contentId + '/' + path ;
}
}
return path ; // Will automatically be looked for in tmp folder
} ) ;
if ( clipboardData . generic ) {
2018-06-26 11:52:57 +02:00
// Use reference instead of key
clipboardData . generic = clipboardData . specific [ clipboardData . generic ] ;
2018-06-25 15:59:17 +02:00
}
return clipboardData ;
} ;
/ * *
2019-01-03 12:46:16 +01:00
* Update file URLs and reset content IDs .
* Useful when copying content .
2018-06-25 15:59:17 +02:00
*
2018-06-26 11:52:57 +02:00
* @ private
2018-06-25 15:59:17 +02:00
* @ param { object } params Reference
* @ param { function } handler Modifies the path to work when pasted
* /
2019-01-03 12:46:16 +01:00
var recursiveUpdate = function ( params , handler ) {
2018-06-25 15:59:17 +02:00
for ( var prop in params ) {
if ( params . hasOwnProperty ( prop ) && params [ prop ] instanceof Object ) {
var obj = params [ prop ] ;
if ( obj . path !== undefined && obj . mime !== undefined ) {
obj . path = handler ( obj . path ) ;
}
else {
2019-01-03 12:46:16 +01:00
if ( obj . library !== undefined && obj . subContentId !== undefined ) {
// Avoid multiple content with same ID
delete obj . subContentId ;
}
recursiveUpdate ( obj , handler ) ;
2018-06-25 15:59:17 +02:00
}
}
}
} ;
2015-04-07 19:32:44 +02:00
// Init H5P when page is fully loadded
$ ( document ) . ready ( function ( ) {
2016-11-17 12:56:15 +01:00
2018-06-25 15:59:17 +02:00
window . addEventListener ( 'storage' , function ( event ) {
// Pick up clipboard changes from other tabs
if ( event . key === 'h5pClipboard' ) {
2018-06-26 11:52:57 +02:00
// Trigger an event so all 'Paste' buttons may be enabled.
2018-06-25 15:59:17 +02:00
H5P . externalDispatcher . trigger ( 'datainclipboard' , { reset : event . newValue === null } ) ;
}
} ) ;
2017-05-30 15:43:26 +02:00
var ccVersions = {
2017-06-06 13:51:51 +02:00
'default' : '4.0' ,
2017-05-30 15:43:26 +02:00
'4.0' : H5P . t ( 'licenseCC40' ) ,
'3.0' : H5P . t ( 'licenseCC30' ) ,
'2.5' : H5P . t ( 'licenseCC25' ) ,
'2.0' : H5P . t ( 'licenseCC20' ) ,
'1.0' : H5P . t ( 'licenseCC10' ) ,
} ;
/ * *
* Maps copyright license codes to their human readable counterpart .
*
* @ type { Object }
* /
H5P . copyrightLicenses = {
'U' : H5P . t ( 'licenseU' ) ,
'CC BY' : {
label : H5P . t ( 'licenseCCBY' ) ,
2018-06-13 16:41:49 +02:00
link : 'http://creativecommons.org/licenses/by/:version' ,
2017-05-30 15:43:26 +02:00
versions : ccVersions
} ,
'CC BY-SA' : {
label : H5P . t ( 'licenseCCBYSA' ) ,
2018-06-13 16:41:49 +02:00
link : 'http://creativecommons.org/licenses/by-sa/:version' ,
2017-05-30 15:43:26 +02:00
versions : ccVersions
} ,
'CC BY-ND' : {
label : H5P . t ( 'licenseCCBYND' ) ,
2018-06-13 16:41:49 +02:00
link : 'http://creativecommons.org/licenses/by-nd/:version' ,
2017-05-30 15:43:26 +02:00
versions : ccVersions
} ,
'CC BY-NC' : {
label : H5P . t ( 'licenseCCBYNC' ) ,
2018-06-13 16:41:49 +02:00
link : 'http://creativecommons.org/licenses/by-nc/:version' ,
2017-05-30 15:43:26 +02:00
versions : ccVersions
} ,
'CC BY-NC-SA' : {
label : H5P . t ( 'licenseCCBYNCSA' ) ,
2018-06-13 16:41:49 +02:00
link : 'http://creativecommons.org/licenses/by-nc-sa/:version' ,
2017-05-30 15:43:26 +02:00
versions : ccVersions
} ,
'CC BY-NC-ND' : {
label : H5P . t ( 'licenseCCBYNCND' ) ,
2018-06-13 16:41:49 +02:00
link : 'http://creativecommons.org/licenses/by-nc-nd/:version' ,
2017-05-30 15:43:26 +02:00
versions : ccVersions
} ,
2018-05-25 18:38:42 +02:00
'CC0 1.0' : {
label : H5P . t ( 'licenseCC010' ) ,
link : 'https://creativecommons.org/publicdomain/zero/1.0/'
} ,
2017-05-30 15:43:26 +02:00
'GNU GPL' : {
label : H5P . t ( 'licenseGPL' ) ,
link : 'http://www.gnu.org/licenses/gpl-:version-standalone.html' ,
linkVersions : {
'v3' : '3.0' ,
'v2' : '2.0' ,
'v1' : '1.0'
} ,
versions : {
2017-06-06 13:51:51 +02:00
'default' : 'v3' ,
2017-05-30 15:43:26 +02:00
'v3' : H5P . t ( 'licenseV3' ) ,
'v2' : H5P . t ( 'licenseV2' ) ,
'v1' : H5P . t ( 'licenseV1' )
}
} ,
'PD' : {
label : H5P . t ( 'licensePD' ) ,
versions : {
'CC0 1.0' : {
label : H5P . t ( 'licenseCC010' ) ,
link : 'https://creativecommons.org/publicdomain/zero/1.0/'
} ,
'CC PDM' : {
label : H5P . t ( 'licensePDM' ) ,
link : 'https://creativecommons.org/publicdomain/mark/1.0/'
}
}
} ,
'ODC PDDL' : '<a href="http://opendatacommons.org/licenses/pddl/1.0/" target="_blank">Public Domain Dedication and Licence</a>' ,
2018-05-25 18:38:42 +02:00
'CC PDM' : {
label : H5P . t ( 'licensePDM' ) ,
link : 'https://creativecommons.org/publicdomain/mark/1.0/'
} ,
2017-05-30 15:43:26 +02:00
'C' : H5P . t ( 'licenseC' ) ,
} ;
2016-11-17 12:56:15 +01:00
/ * *
* Indicates if H5P is embedded on an external page using iframe .
* @ member { boolean } H5P . externalEmbed
* /
// Relay events to top window. This must be done before H5P.init
// since events may be fired on initialization.
if ( H5P . isFramed && H5P . externalEmbed === false ) {
H5P . externalDispatcher . on ( '*' , function ( event ) {
window . parent . H5P . externalDispatcher . trigger . call ( this , event ) ;
} ) ;
}
2015-05-05 13:13:57 +02:00
/ * *
* Prevent H5P Core from initializing . Must be overriden before document ready .
* @ member { boolean } H5P . preventInit
* /
2015-04-07 19:32:44 +02:00
if ( ! H5P . preventInit ) {
// Note that this start script has to be an external resource for it to
// load in correct order in IE9.
H5P . init ( document . body ) ;
}
if ( H5PIntegration . saveFreq !== false ) {
2015-12-18 10:06:33 +01:00
// When was the last state stored
var lastStoredOn = 0 ;
2015-04-07 19:32:44 +02:00
// Store the current state of the H5P when leaving the page.
2015-12-16 09:50:58 +01:00
var storeCurrentState = function ( ) {
2015-12-18 10:06:33 +01:00
// Make sure at least 250 ms has passed since last save
var currentTime = new Date ( ) . getTime ( ) ;
if ( currentTime - lastStoredOn > 250 ) {
lastStoredOn = currentTime ;
for ( var i = 0 ; i < H5P . instances . length ; i ++ ) {
var instance = H5P . instances [ i ] ;
if ( instance . getCurrentState instanceof Function ||
typeof instance . getCurrentState === 'function' ) {
var state = instance . getCurrentState ( ) ;
if ( state !== undefined ) {
// Async is not used to prevent the request from being cancelled.
H5P . setUserData ( instance . contentId , 'state' , state , { deleteOnChange : true , async : false } ) ;
}
2015-04-07 19:32:44 +02:00
}
}
}
2015-12-16 09:50:58 +01:00
} ;
2015-12-18 10:06:33 +01:00
// iPad does not support beforeunload, therefore using unload
H5P . $window . one ( 'beforeunload unload' , function ( ) {
// Only want to do this once
H5P . $window . off ( 'pagehide beforeunload unload' ) ;
storeCurrentState ( ) ;
2015-04-07 19:32:44 +02:00
} ) ;
2015-12-18 10:06:33 +01:00
// pagehide is used on iPad when tabs are switched
2015-12-16 09:50:58 +01:00
H5P . $window . on ( 'pagehide' , storeCurrentState ) ;
2015-04-07 19:32:44 +02:00
}
} ) ;
} ) ( H5P . jQuery ) ;