1 |
efrain |
1 |
/*jshint multistr: true */
|
|
|
2 |
// TODO: Should we split up the generic parts needed by the editor(and others), and the parts needed to "run" H5Ps?
|
|
|
3 |
|
|
|
4 |
/** @namespace */
|
|
|
5 |
var H5P = window.H5P = window.H5P || {};
|
|
|
6 |
|
|
|
7 |
/**
|
|
|
8 |
* Tells us if we're inside of an iframe.
|
|
|
9 |
* @member {boolean}
|
|
|
10 |
*/
|
|
|
11 |
H5P.isFramed = (window.self !== window.parent);
|
|
|
12 |
|
|
|
13 |
/**
|
|
|
14 |
* jQuery instance of current window.
|
|
|
15 |
* @member {H5P.jQuery}
|
|
|
16 |
*/
|
|
|
17 |
H5P.$window = H5P.jQuery(window);
|
|
|
18 |
|
|
|
19 |
/**
|
|
|
20 |
* List over H5P instances on the current page.
|
|
|
21 |
* @member {Array}
|
|
|
22 |
*/
|
|
|
23 |
H5P.instances = [];
|
|
|
24 |
|
|
|
25 |
// Detect if we support fullscreen, and what prefix to use.
|
|
|
26 |
if (document.documentElement.requestFullscreen) {
|
|
|
27 |
/**
|
|
|
28 |
* Browser prefix to use when entering fullscreen mode.
|
|
|
29 |
* undefined means no fullscreen support.
|
|
|
30 |
* @member {string}
|
|
|
31 |
*/
|
|
|
32 |
H5P.fullScreenBrowserPrefix = '';
|
|
|
33 |
}
|
|
|
34 |
else if (document.documentElement.webkitRequestFullScreen) {
|
|
|
35 |
H5P.safariBrowser = navigator.userAgent.match(/version\/([.\d]+)/i);
|
|
|
36 |
H5P.safariBrowser = (H5P.safariBrowser === null ? 0 : parseInt(H5P.safariBrowser[1]));
|
|
|
37 |
|
|
|
38 |
// Do not allow fullscreen for safari < 7.
|
|
|
39 |
if (H5P.safariBrowser === 0 || H5P.safariBrowser > 6) {
|
|
|
40 |
H5P.fullScreenBrowserPrefix = 'webkit';
|
|
|
41 |
}
|
|
|
42 |
}
|
|
|
43 |
else if (document.documentElement.mozRequestFullScreen) {
|
|
|
44 |
H5P.fullScreenBrowserPrefix = 'moz';
|
|
|
45 |
}
|
|
|
46 |
else if (document.documentElement.msRequestFullscreen) {
|
|
|
47 |
H5P.fullScreenBrowserPrefix = 'ms';
|
|
|
48 |
}
|
|
|
49 |
|
|
|
50 |
/**
|
|
|
51 |
* Keep track of when the H5Ps where started.
|
|
|
52 |
*
|
|
|
53 |
* @type {Object[]}
|
|
|
54 |
*/
|
|
|
55 |
H5P.opened = {};
|
|
|
56 |
|
|
|
57 |
/**
|
|
|
58 |
* Initialize H5P content.
|
|
|
59 |
* Scans for ".h5p-content" in the document and initializes H5P instances where found.
|
|
|
60 |
*
|
|
|
61 |
* @param {Object} target DOM Element
|
|
|
62 |
*/
|
|
|
63 |
H5P.init = function (target) {
|
|
|
64 |
// Useful jQuery object.
|
|
|
65 |
if (H5P.$body === undefined) {
|
|
|
66 |
H5P.$body = H5P.jQuery(document.body);
|
|
|
67 |
}
|
|
|
68 |
|
|
|
69 |
// Determine if we can use full screen
|
|
|
70 |
if (H5P.fullscreenSupported === undefined) {
|
|
|
71 |
/**
|
|
|
72 |
* Use this variable to check if fullscreen is supported. Fullscreen can be
|
|
|
73 |
* restricted when embedding since not all browsers support the native
|
|
|
74 |
* fullscreen, and the semi-fullscreen solution doesn't work when embedded.
|
|
|
75 |
* @type {boolean}
|
|
|
76 |
*/
|
|
|
77 |
H5P.fullscreenSupported = !H5PIntegration.fullscreenDisabled && !H5P.fullscreenDisabled && (!(H5P.isFramed && H5P.externalEmbed !== false) || !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled));
|
|
|
78 |
// -We should consider document.msFullscreenEnabled when they get their
|
|
|
79 |
// -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
|
|
|
80 |
// Update: Seems to be no need as they've moved on to Webkit
|
|
|
81 |
}
|
|
|
82 |
|
|
|
83 |
// Deprecated variable, kept to maintain backwards compatability
|
|
|
84 |
if (H5P.canHasFullScreen === undefined) {
|
|
|
85 |
/**
|
|
|
86 |
* @deprecated since version 1.11
|
|
|
87 |
* @type {boolean}
|
|
|
88 |
*/
|
|
|
89 |
H5P.canHasFullScreen = H5P.fullscreenSupported;
|
|
|
90 |
}
|
|
|
91 |
|
|
|
92 |
// H5Ps added in normal DIV.
|
|
|
93 |
H5P.jQuery('.h5p-content:not(.h5p-initialized)', target).each(function () {
|
|
|
94 |
var $element = H5P.jQuery(this).addClass('h5p-initialized');
|
|
|
95 |
var $container = H5P.jQuery('<div class="h5p-container"></div>').appendTo($element);
|
|
|
96 |
var contentId = $element.data('content-id');
|
|
|
97 |
var contentData = H5PIntegration.contents['cid-' + contentId];
|
|
|
98 |
if (contentData === undefined) {
|
|
|
99 |
return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?');
|
|
|
100 |
}
|
|
|
101 |
var library = {
|
|
|
102 |
library: contentData.library,
|
|
|
103 |
params: JSON.parse(contentData.jsonContent),
|
|
|
104 |
metadata: contentData.metadata
|
|
|
105 |
};
|
|
|
106 |
|
|
|
107 |
H5P.getUserData(contentId, 'state', function (err, previousState) {
|
|
|
108 |
if (previousState) {
|
|
|
109 |
library.userDatas = {
|
|
|
110 |
state: previousState
|
|
|
111 |
};
|
|
|
112 |
}
|
|
|
113 |
else if (previousState === null && H5PIntegration.saveFreq) {
|
|
|
114 |
// Content has been reset. Display dialog.
|
|
|
115 |
delete contentData.contentUserData;
|
|
|
116 |
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);
|
|
|
117 |
H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) {
|
|
|
118 |
|
|
|
119 |
var closeDialog = function (event) {
|
|
|
120 |
if (event.type === 'click' || event.which === 32) {
|
|
|
121 |
dialog.close();
|
|
|
122 |
H5P.deleteUserData(contentId, 'state', 0);
|
|
|
123 |
}
|
|
|
124 |
};
|
|
|
125 |
|
|
|
126 |
$dialog.find('.h5p-dialog-ok-button').click(closeDialog).keypress(closeDialog);
|
|
|
127 |
H5P.trigger(instance, 'resize');
|
|
|
128 |
}).on('dialog-closed', function () {
|
|
|
129 |
H5P.trigger(instance, 'resize');
|
|
|
130 |
});
|
|
|
131 |
dialog.open();
|
|
|
132 |
}
|
|
|
133 |
// If previousState is false we don't have a previous state
|
|
|
134 |
});
|
|
|
135 |
|
|
|
136 |
// Create new instance.
|
|
|
137 |
var instance = H5P.newRunnable(library, contentId, $container, true, {standalone: true});
|
|
|
138 |
|
|
|
139 |
H5P.offlineRequestQueue = new H5P.OfflineRequestQueue({instance: instance});
|
|
|
140 |
|
|
|
141 |
// Check if we should add and display a fullscreen button for this H5P.
|
|
|
142 |
if (contentData.fullScreen == 1 && H5P.fullscreenSupported) {
|
|
|
143 |
H5P.jQuery(
|
|
|
144 |
'<div class="h5p-content-controls">' +
|
|
|
145 |
'<div role="button" ' +
|
|
|
146 |
'tabindex="0" ' +
|
|
|
147 |
'class="h5p-enable-fullscreen" ' +
|
|
|
148 |
'aria-label="' + H5P.t('fullscreen') + '" ' +
|
|
|
149 |
'title="' + H5P.t('fullscreen') + '">' +
|
|
|
150 |
'</div>' +
|
|
|
151 |
'</div>')
|
|
|
152 |
.prependTo($container)
|
|
|
153 |
.children()
|
|
|
154 |
.click(function () {
|
|
|
155 |
H5P.fullScreen($container, instance);
|
|
|
156 |
})
|
|
|
157 |
.keydown(function (e) {
|
|
|
158 |
if (e.which === 32 || e.which === 13) {
|
|
|
159 |
H5P.fullScreen($container, instance);
|
|
|
160 |
return false;
|
|
|
161 |
}
|
|
|
162 |
})
|
|
|
163 |
;
|
|
|
164 |
}
|
|
|
165 |
|
|
|
166 |
/**
|
|
|
167 |
* Create action bar
|
|
|
168 |
*/
|
|
|
169 |
var displayOptions = contentData.displayOptions;
|
|
|
170 |
var displayFrame = false;
|
|
|
171 |
if (displayOptions.frame) {
|
|
|
172 |
// Special handling of copyrights
|
|
|
173 |
if (displayOptions.copyright) {
|
|
|
174 |
var copyrights = H5P.getCopyrights(instance, library.params, contentId, library.metadata);
|
|
|
175 |
if (!copyrights) {
|
|
|
176 |
displayOptions.copyright = false;
|
|
|
177 |
}
|
|
|
178 |
}
|
|
|
179 |
|
|
|
180 |
// Create action bar
|
|
|
181 |
var actionBar = new H5P.ActionBar(displayOptions);
|
|
|
182 |
var $actions = actionBar.getDOMElement();
|
|
|
183 |
|
|
|
184 |
actionBar.on('reuse', function () {
|
|
|
185 |
H5P.openReuseDialog($actions, contentData, library, instance, contentId);
|
|
|
186 |
instance.triggerXAPI('accessed-reuse');
|
|
|
187 |
});
|
|
|
188 |
actionBar.on('copyrights', function () {
|
|
|
189 |
var dialog = new H5P.Dialog('copyrights', H5P.t('copyrightInformation'), copyrights, $container, $actions.find('.h5p-copyrights')[0]);
|
|
|
190 |
dialog.open(true);
|
|
|
191 |
instance.triggerXAPI('accessed-copyright');
|
|
|
192 |
});
|
|
|
193 |
actionBar.on('embed', function () {
|
|
|
194 |
H5P.openEmbedDialog($actions, contentData.embedCode, contentData.resizeCode, {
|
|
|
195 |
width: $element.width(),
|
|
|
196 |
height: $element.height()
|
|
|
197 |
}, instance);
|
|
|
198 |
instance.triggerXAPI('accessed-embed');
|
|
|
199 |
});
|
|
|
200 |
|
|
|
201 |
if (actionBar.hasActions()) {
|
|
|
202 |
displayFrame = true;
|
|
|
203 |
$actions.insertAfter($container);
|
|
|
204 |
}
|
|
|
205 |
}
|
|
|
206 |
|
|
|
207 |
$element.addClass(displayFrame ? 'h5p-frame' : 'h5p-no-frame');
|
|
|
208 |
|
|
|
209 |
// Keep track of when we started
|
|
|
210 |
H5P.opened[contentId] = new Date();
|
|
|
211 |
|
|
|
212 |
// Handle events when the user finishes the content. Useful for logging exercise results.
|
|
|
213 |
H5P.on(instance, 'finish', function (event) {
|
|
|
214 |
if (event.data !== undefined) {
|
|
|
215 |
H5P.setFinished(contentId, event.data.score, event.data.maxScore, event.data.time);
|
|
|
216 |
}
|
|
|
217 |
});
|
|
|
218 |
|
|
|
219 |
// Listen for xAPI events.
|
|
|
220 |
H5P.on(instance, 'xAPI', H5P.xAPICompletedListener);
|
|
|
221 |
|
|
|
222 |
// Auto save current state if supported
|
|
|
223 |
if (H5PIntegration.saveFreq !== false && (
|
|
|
224 |
instance.getCurrentState instanceof Function ||
|
|
|
225 |
typeof instance.getCurrentState === 'function')) {
|
|
|
226 |
|
|
|
227 |
var saveTimer, save = function () {
|
|
|
228 |
var state = instance.getCurrentState();
|
|
|
229 |
if (state !== undefined) {
|
|
|
230 |
H5P.setUserData(contentId, 'state', state, {deleteOnChange: true});
|
|
|
231 |
}
|
|
|
232 |
if (H5PIntegration.saveFreq) {
|
|
|
233 |
// Continue autosave
|
|
|
234 |
saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000);
|
|
|
235 |
}
|
|
|
236 |
};
|
|
|
237 |
|
|
|
238 |
if (H5PIntegration.saveFreq) {
|
|
|
239 |
// Start autosave
|
|
|
240 |
saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000);
|
|
|
241 |
}
|
|
|
242 |
|
|
|
243 |
// xAPI events will schedule a save in three seconds.
|
|
|
244 |
H5P.on(instance, 'xAPI', function (event) {
|
|
|
245 |
var verb = event.getVerb();
|
|
|
246 |
if (verb === 'completed' || verb === 'progressed') {
|
|
|
247 |
clearTimeout(saveTimer);
|
|
|
248 |
saveTimer = setTimeout(save, 3000);
|
|
|
249 |
}
|
|
|
250 |
});
|
|
|
251 |
}
|
|
|
252 |
|
|
|
253 |
if (H5P.isFramed) {
|
|
|
254 |
var resizeDelay;
|
|
|
255 |
if (H5P.externalEmbed === false) {
|
|
|
256 |
// Internal embed
|
|
|
257 |
// Make it possible to resize the iframe when the content changes size. This way we get no scrollbars.
|
|
|
258 |
var iframe = window.frameElement;
|
|
|
259 |
var resizeIframe = function () {
|
|
|
260 |
if (window.parent.H5P.isFullscreen) {
|
|
|
261 |
return; // Skip if full screen.
|
|
|
262 |
}
|
|
|
263 |
|
|
|
264 |
// Retain parent size to avoid jumping/scrolling
|
|
|
265 |
var parentHeight = iframe.parentElement.style.height;
|
|
|
266 |
iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px';
|
|
|
267 |
|
|
|
268 |
// Note: Force layout reflow
|
|
|
269 |
// This fixes a flickering bug for embedded content on iPads
|
|
|
270 |
// @see https://github.com/h5p/h5p-moodle-plugin/issues/237
|
|
|
271 |
iframe.getBoundingClientRect();
|
|
|
272 |
|
|
|
273 |
// Reset iframe height, in case content has shrinked.
|
|
|
274 |
iframe.style.height = '1px';
|
|
|
275 |
|
|
|
276 |
// Resize iframe so all content is visible.
|
|
|
277 |
iframe.style.height = (iframe.contentDocument.body.scrollHeight) + 'px';
|
|
|
278 |
|
|
|
279 |
// Free parent
|
|
|
280 |
iframe.parentElement.style.height = parentHeight;
|
|
|
281 |
};
|
|
|
282 |
|
|
|
283 |
H5P.on(instance, 'resize', function () {
|
|
|
284 |
// Use a delay to make sure iframe is resized to the correct size.
|
|
|
285 |
clearTimeout(resizeDelay);
|
|
|
286 |
resizeDelay = setTimeout(function () {
|
|
|
287 |
resizeIframe();
|
|
|
288 |
}, 1);
|
|
|
289 |
});
|
|
|
290 |
}
|
|
|
291 |
else if (H5P.communicator) {
|
|
|
292 |
// External embed
|
|
|
293 |
var parentIsFriendly = false;
|
|
|
294 |
|
|
|
295 |
// Handle that the resizer is loaded after the iframe
|
|
|
296 |
H5P.communicator.on('ready', function () {
|
|
|
297 |
H5P.communicator.send('hello');
|
|
|
298 |
});
|
|
|
299 |
|
|
|
300 |
// Handle hello message from our parent window
|
|
|
301 |
H5P.communicator.on('hello', function () {
|
|
|
302 |
// Initial setup/handshake is done
|
|
|
303 |
parentIsFriendly = true;
|
|
|
304 |
|
|
|
305 |
// Make iframe responsive
|
|
|
306 |
document.body.style.height = 'auto';
|
|
|
307 |
|
|
|
308 |
// Hide scrollbars for correct size
|
|
|
309 |
document.body.style.overflow = 'hidden';
|
|
|
310 |
|
|
|
311 |
// Content need to be resized to fit the new iframe size
|
|
|
312 |
H5P.trigger(instance, 'resize');
|
|
|
313 |
});
|
|
|
314 |
|
|
|
315 |
// When resize has been prepared tell parent window to resize
|
|
|
316 |
H5P.communicator.on('resizePrepared', function () {
|
|
|
317 |
H5P.communicator.send('resize', {
|
|
|
318 |
scrollHeight: document.body.scrollHeight
|
|
|
319 |
});
|
|
|
320 |
});
|
|
|
321 |
|
|
|
322 |
H5P.communicator.on('resize', function () {
|
|
|
323 |
H5P.trigger(instance, 'resize');
|
|
|
324 |
});
|
|
|
325 |
|
|
|
326 |
H5P.on(instance, 'resize', function () {
|
|
|
327 |
if (H5P.isFullscreen) {
|
|
|
328 |
return; // Skip iframe resize
|
|
|
329 |
}
|
|
|
330 |
|
|
|
331 |
// Use a delay to make sure iframe is resized to the correct size.
|
|
|
332 |
clearTimeout(resizeDelay);
|
|
|
333 |
resizeDelay = setTimeout(function () {
|
|
|
334 |
// Only resize if the iframe can be resized
|
|
|
335 |
if (parentIsFriendly) {
|
|
|
336 |
H5P.communicator.send('prepareResize', {
|
|
|
337 |
scrollHeight: document.body.scrollHeight,
|
|
|
338 |
clientHeight: document.body.clientHeight
|
|
|
339 |
});
|
|
|
340 |
}
|
|
|
341 |
else {
|
|
|
342 |
H5P.communicator.send('hello');
|
|
|
343 |
}
|
|
|
344 |
}, 0);
|
|
|
345 |
});
|
|
|
346 |
}
|
|
|
347 |
}
|
|
|
348 |
|
|
|
349 |
if (!H5P.isFramed || H5P.externalEmbed === false) {
|
|
|
350 |
// Resize everything when window is resized.
|
|
|
351 |
H5P.jQuery(window.parent).resize(function () {
|
|
|
352 |
H5P.trigger(instance, 'resize');
|
|
|
353 |
});
|
|
|
354 |
}
|
|
|
355 |
|
|
|
356 |
H5P.instances.push(instance);
|
|
|
357 |
|
|
|
358 |
// Resize content.
|
|
|
359 |
H5P.trigger(instance, 'resize');
|
|
|
360 |
|
|
|
361 |
// Logic for hiding focus effects when using mouse
|
|
|
362 |
$element.addClass('using-mouse');
|
|
|
363 |
$element.on('mousedown keydown keyup', function (event) {
|
|
|
364 |
$element.toggleClass('using-mouse', event.type === 'mousedown');
|
|
|
365 |
});
|
|
|
366 |
|
|
|
367 |
if (H5P.externalDispatcher) {
|
|
|
368 |
H5P.externalDispatcher.trigger('initialized');
|
|
|
369 |
}
|
|
|
370 |
});
|
|
|
371 |
|
|
|
372 |
// Insert H5Ps that should be in iframes.
|
|
|
373 |
H5P.jQuery('iframe.h5p-iframe:not(.h5p-initialized)', target).each(function () {
|
|
|
374 |
const iframe = this;
|
|
|
375 |
const $iframe = H5P.jQuery(iframe);
|
|
|
376 |
|
|
|
377 |
const contentId = $iframe.data('content-id');
|
|
|
378 |
const contentData = H5PIntegration.contents['cid-' + contentId];
|
|
|
379 |
const contentLanguage = contentData && contentData.metadata && contentData.metadata.defaultLanguage
|
|
|
380 |
? contentData.metadata.defaultLanguage : 'en';
|
|
|
381 |
|
|
|
382 |
const writeDocument = function () {
|
|
|
383 |
iframe.contentDocument.open();
|
|
|
384 |
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>');
|
|
|
385 |
iframe.contentDocument.close();
|
|
|
386 |
};
|
|
|
387 |
|
|
|
388 |
$iframe.addClass('h5p-initialized')
|
|
|
389 |
if (iframe.contentDocument === null) {
|
|
|
390 |
// In some Edge cases the iframe isn't always loaded when the page is ready.
|
|
|
391 |
$iframe.on('load', writeDocument);
|
|
|
392 |
$iframe.attr('src', 'about:blank');
|
|
|
393 |
}
|
|
|
394 |
else {
|
|
|
395 |
writeDocument();
|
|
|
396 |
}
|
|
|
397 |
});
|
|
|
398 |
};
|
|
|
399 |
|
|
|
400 |
/**
|
|
|
401 |
* Loop through assets for iframe content and create a set of tags for head.
|
|
|
402 |
*
|
|
|
403 |
* @private
|
|
|
404 |
* @param {number} contentId
|
|
|
405 |
* @returns {string} HTML
|
|
|
406 |
*/
|
|
|
407 |
H5P.getHeadTags = function (contentId) {
|
|
|
408 |
var createStyleTags = function (styles) {
|
|
|
409 |
var tags = '';
|
|
|
410 |
for (var i = 0; i < styles.length; i++) {
|
|
|
411 |
tags += '<link rel="stylesheet" href="' + styles[i] + '">';
|
|
|
412 |
}
|
|
|
413 |
return tags;
|
|
|
414 |
};
|
|
|
415 |
|
|
|
416 |
var createScriptTags = function (scripts) {
|
|
|
417 |
var tags = '';
|
|
|
418 |
for (var i = 0; i < scripts.length; i++) {
|
|
|
419 |
tags += '<script src="' + scripts[i] + '"></script>';
|
|
|
420 |
}
|
|
|
421 |
return tags;
|
|
|
422 |
};
|
|
|
423 |
|
|
|
424 |
return '<base target="_parent">' +
|
|
|
425 |
createStyleTags(H5PIntegration.core.styles) +
|
|
|
426 |
createStyleTags(H5PIntegration.contents['cid-' + contentId].styles) +
|
|
|
427 |
createScriptTags(H5PIntegration.core.scripts) +
|
|
|
428 |
createScriptTags(H5PIntegration.contents['cid-' + contentId].scripts) +
|
|
|
429 |
'<script>H5PIntegration = window.parent.H5PIntegration; var H5P = H5P || {}; H5P.externalEmbed = false;</script>';
|
|
|
430 |
};
|
|
|
431 |
|
|
|
432 |
/**
|
|
|
433 |
* When embedded the communicator helps talk to the parent page.
|
|
|
434 |
*
|
|
|
435 |
* @type {Communicator}
|
|
|
436 |
*/
|
|
|
437 |
H5P.communicator = (function () {
|
|
|
438 |
/**
|
|
|
439 |
* @class
|
|
|
440 |
* @private
|
|
|
441 |
*/
|
|
|
442 |
function Communicator() {
|
|
|
443 |
var self = this;
|
|
|
444 |
|
|
|
445 |
// Maps actions to functions
|
|
|
446 |
var actionHandlers = {};
|
|
|
447 |
|
|
|
448 |
// Register message listener
|
|
|
449 |
window.addEventListener('message', function receiveMessage(event) {
|
|
|
450 |
if (window.parent !== event.source || event.data.context !== 'h5p') {
|
|
|
451 |
return; // Only handle messages from parent and in the correct context
|
|
|
452 |
}
|
|
|
453 |
|
|
|
454 |
if (actionHandlers[event.data.action] !== undefined) {
|
|
|
455 |
actionHandlers[event.data.action](event.data);
|
|
|
456 |
}
|
|
|
457 |
} , false);
|
|
|
458 |
|
|
|
459 |
|
|
|
460 |
/**
|
|
|
461 |
* Register action listener.
|
|
|
462 |
*
|
|
|
463 |
* @param {string} action What you are waiting for
|
|
|
464 |
* @param {function} handler What you want done
|
|
|
465 |
*/
|
|
|
466 |
self.on = function (action, handler) {
|
|
|
467 |
actionHandlers[action] = handler;
|
|
|
468 |
};
|
|
|
469 |
|
|
|
470 |
/**
|
|
|
471 |
* Send a message to the all mighty father.
|
|
|
472 |
*
|
|
|
473 |
* @param {string} action
|
|
|
474 |
* @param {Object} [data] payload
|
|
|
475 |
*/
|
|
|
476 |
self.send = function (action, data) {
|
|
|
477 |
if (data === undefined) {
|
|
|
478 |
data = {};
|
|
|
479 |
}
|
|
|
480 |
data.context = 'h5p';
|
|
|
481 |
data.action = action;
|
|
|
482 |
|
|
|
483 |
// Parent origin can be anything
|
|
|
484 |
window.parent.postMessage(data, '*');
|
|
|
485 |
};
|
|
|
486 |
}
|
|
|
487 |
|
|
|
488 |
return (window.postMessage && window.addEventListener ? new Communicator() : undefined);
|
|
|
489 |
})();
|
|
|
490 |
|
|
|
491 |
/**
|
|
|
492 |
* Enter semi fullscreen for the given H5P instance
|
|
|
493 |
*
|
|
|
494 |
* @param {H5P.jQuery} $element Content container.
|
|
|
495 |
* @param {Object} instance
|
|
|
496 |
* @param {function} exitCallback Callback function called when user exits fullscreen.
|
|
|
497 |
* @param {H5P.jQuery} $body For internal use. Gives the body of the iframe.
|
|
|
498 |
*/
|
|
|
499 |
H5P.semiFullScreen = function ($element, instance, exitCallback, body) {
|
|
|
500 |
H5P.fullScreen($element, instance, exitCallback, body, true);
|
|
|
501 |
};
|
|
|
502 |
|
|
|
503 |
/**
|
|
|
504 |
* Enter fullscreen for the given H5P instance.
|
|
|
505 |
*
|
|
|
506 |
* @param {H5P.jQuery} $element Content container.
|
|
|
507 |
* @param {Object} instance
|
|
|
508 |
* @param {function} exitCallback Callback function called when user exits fullscreen.
|
|
|
509 |
* @param {H5P.jQuery} $body For internal use. Gives the body of the iframe.
|
|
|
510 |
* @param {Boolean} forceSemiFullScreen
|
|
|
511 |
*/
|
|
|
512 |
H5P.fullScreen = function ($element, instance, exitCallback, body, forceSemiFullScreen) {
|
|
|
513 |
if (H5P.exitFullScreen !== undefined) {
|
|
|
514 |
return; // Cannot enter new fullscreen until previous is over
|
|
|
515 |
}
|
|
|
516 |
|
|
|
517 |
if (H5P.isFramed && H5P.externalEmbed === false) {
|
|
|
518 |
// Trigger resize on wrapper in parent window.
|
|
|
519 |
window.parent.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get(), forceSemiFullScreen);
|
|
|
520 |
H5P.isFullscreen = true;
|
|
|
521 |
H5P.exitFullScreen = function () {
|
|
|
522 |
window.parent.H5P.exitFullScreen();
|
|
|
523 |
};
|
|
|
524 |
H5P.on(instance, 'exitFullScreen', function () {
|
|
|
525 |
H5P.isFullscreen = false;
|
|
|
526 |
H5P.exitFullScreen = undefined;
|
|
|
527 |
});
|
|
|
528 |
return;
|
|
|
529 |
}
|
|
|
530 |
|
|
|
531 |
var $container = $element;
|
|
|
532 |
var $classes, $iframe, $body;
|
|
|
533 |
if (body === undefined) {
|
|
|
534 |
$body = H5P.$body;
|
|
|
535 |
}
|
|
|
536 |
else {
|
|
|
537 |
// We're called from an iframe.
|
|
|
538 |
$body = H5P.jQuery(body);
|
|
|
539 |
$classes = $body.add($element.get());
|
|
|
540 |
var iframeSelector = '#h5p-iframe-' + $element.parent().data('content-id');
|
|
|
541 |
$iframe = H5P.jQuery(iframeSelector);
|
|
|
542 |
$element = $iframe.parent(); // Put iframe wrapper in fullscreen, not container.
|
|
|
543 |
}
|
|
|
544 |
|
|
|
545 |
$classes = $element.add(H5P.$body).add($classes);
|
|
|
546 |
|
|
|
547 |
/**
|
|
|
548 |
* Prepare for resize by setting the correct styles.
|
|
|
549 |
*
|
|
|
550 |
* @private
|
|
|
551 |
* @param {string} classes CSS
|
|
|
552 |
*/
|
|
|
553 |
var before = function (classes) {
|
|
|
554 |
$classes.addClass(classes);
|
|
|
555 |
|
|
|
556 |
if ($iframe !== undefined) {
|
|
|
557 |
// Set iframe to its default size(100%).
|
|
|
558 |
$iframe.css('height', '');
|
|
|
559 |
}
|
|
|
560 |
};
|
|
|
561 |
|
|
|
562 |
/**
|
|
|
563 |
* Gets called when fullscreen mode has been entered.
|
|
|
564 |
* Resizes and sets focus on content.
|
|
|
565 |
*
|
|
|
566 |
* @private
|
|
|
567 |
*/
|
|
|
568 |
var entered = function () {
|
|
|
569 |
// Do not rely on window resize events.
|
|
|
570 |
H5P.trigger(instance, 'resize');
|
|
|
571 |
H5P.trigger(instance, 'focus');
|
|
|
572 |
H5P.trigger(instance, 'enterFullScreen');
|
|
|
573 |
};
|
|
|
574 |
|
|
|
575 |
/**
|
|
|
576 |
* Gets called when fullscreen mode has been exited.
|
|
|
577 |
* Resizes and sets focus on content.
|
|
|
578 |
*
|
|
|
579 |
* @private
|
|
|
580 |
* @param {string} classes CSS
|
|
|
581 |
*/
|
|
|
582 |
var done = function (classes) {
|
|
|
583 |
H5P.isFullscreen = false;
|
|
|
584 |
$classes.removeClass(classes);
|
|
|
585 |
|
|
|
586 |
// Do not rely on window resize events.
|
|
|
587 |
H5P.trigger(instance, 'resize');
|
|
|
588 |
H5P.trigger(instance, 'focus');
|
|
|
589 |
|
|
|
590 |
H5P.exitFullScreen = undefined;
|
|
|
591 |
if (exitCallback !== undefined) {
|
|
|
592 |
exitCallback();
|
|
|
593 |
}
|
|
|
594 |
|
|
|
595 |
H5P.trigger(instance, 'exitFullScreen');
|
|
|
596 |
};
|
|
|
597 |
|
|
|
598 |
H5P.isFullscreen = true;
|
|
|
599 |
if (H5P.fullScreenBrowserPrefix === undefined || forceSemiFullScreen === true) {
|
|
|
600 |
// Create semi fullscreen.
|
|
|
601 |
|
|
|
602 |
if (H5P.isFramed) {
|
|
|
603 |
return; // TODO: Should we support semi-fullscreen for IE9 & 10 ?
|
|
|
604 |
}
|
|
|
605 |
|
|
|
606 |
before('h5p-semi-fullscreen');
|
|
|
607 |
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'));
|
|
|
608 |
var keyup, disableSemiFullscreen = H5P.exitFullScreen = function () {
|
|
|
609 |
if (prevViewportContent) {
|
|
|
610 |
// Use content from the previous viewport tag
|
|
|
611 |
h5pViewport.content = prevViewportContent;
|
|
|
612 |
}
|
|
|
613 |
else {
|
|
|
614 |
// Remove viewport tag
|
|
|
615 |
head.removeChild(h5pViewport);
|
|
|
616 |
}
|
|
|
617 |
$disable.remove();
|
|
|
618 |
$body.unbind('keyup', keyup);
|
|
|
619 |
done('h5p-semi-fullscreen');
|
|
|
620 |
};
|
|
|
621 |
keyup = function (event) {
|
|
|
622 |
if (event.keyCode === 27) {
|
|
|
623 |
disableSemiFullscreen();
|
|
|
624 |
}
|
|
|
625 |
};
|
|
|
626 |
$disable.click(disableSemiFullscreen);
|
|
|
627 |
$body.keyup(keyup);
|
|
|
628 |
|
|
|
629 |
// Disable zoom
|
|
|
630 |
var prevViewportContent, h5pViewport;
|
|
|
631 |
var metaTags = document.getElementsByTagName('meta');
|
|
|
632 |
for (var i = 0; i < metaTags.length; i++) {
|
|
|
633 |
if (metaTags[i].name === 'viewport') {
|
|
|
634 |
// Use the existing viewport tag
|
|
|
635 |
h5pViewport = metaTags[i];
|
|
|
636 |
prevViewportContent = h5pViewport.content;
|
|
|
637 |
break;
|
|
|
638 |
}
|
|
|
639 |
}
|
|
|
640 |
if (!prevViewportContent) {
|
|
|
641 |
// Create a new viewport tag
|
|
|
642 |
h5pViewport = document.createElement('meta');
|
|
|
643 |
h5pViewport.name = 'viewport';
|
|
|
644 |
}
|
|
|
645 |
h5pViewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0';
|
|
|
646 |
if (!prevViewportContent) {
|
|
|
647 |
// Insert the new viewport tag
|
|
|
648 |
var head = document.getElementsByTagName('head')[0];
|
|
|
649 |
head.appendChild(h5pViewport);
|
|
|
650 |
}
|
|
|
651 |
|
|
|
652 |
entered();
|
|
|
653 |
}
|
|
|
654 |
else {
|
|
|
655 |
// Create real fullscreen.
|
|
|
656 |
|
|
|
657 |
before('h5p-fullscreen');
|
|
|
658 |
var first, eventName = (H5P.fullScreenBrowserPrefix === 'ms' ? 'MSFullscreenChange' : H5P.fullScreenBrowserPrefix + 'fullscreenchange');
|
|
|
659 |
document.addEventListener(eventName, function fullscreenCallback() {
|
|
|
660 |
if (first === undefined) {
|
|
|
661 |
// We are entering fullscreen mode
|
|
|
662 |
first = false;
|
|
|
663 |
entered();
|
|
|
664 |
return;
|
|
|
665 |
}
|
|
|
666 |
|
|
|
667 |
// We are exiting fullscreen
|
|
|
668 |
done('h5p-fullscreen');
|
|
|
669 |
document.removeEventListener(eventName, fullscreenCallback, false);
|
|
|
670 |
});
|
|
|
671 |
|
|
|
672 |
if (H5P.fullScreenBrowserPrefix === '') {
|
|
|
673 |
$element[0].requestFullscreen();
|
|
|
674 |
}
|
|
|
675 |
else {
|
|
|
676 |
var method = (H5P.fullScreenBrowserPrefix === 'ms' ? 'msRequestFullscreen' : H5P.fullScreenBrowserPrefix + 'RequestFullScreen');
|
|
|
677 |
var params = (H5P.fullScreenBrowserPrefix === 'webkit' && H5P.safariBrowser === 0 ? Element.ALLOW_KEYBOARD_INPUT : undefined);
|
|
|
678 |
$element[0][method](params);
|
|
|
679 |
}
|
|
|
680 |
|
|
|
681 |
// Allows everone to exit
|
|
|
682 |
H5P.exitFullScreen = function () {
|
|
|
683 |
if (H5P.fullScreenBrowserPrefix === '') {
|
|
|
684 |
document.exitFullscreen();
|
|
|
685 |
}
|
|
|
686 |
else if (H5P.fullScreenBrowserPrefix === 'moz') {
|
|
|
687 |
document.mozCancelFullScreen();
|
|
|
688 |
}
|
|
|
689 |
else {
|
|
|
690 |
document[H5P.fullScreenBrowserPrefix + 'ExitFullscreen']();
|
|
|
691 |
}
|
|
|
692 |
};
|
|
|
693 |
}
|
|
|
694 |
};
|
|
|
695 |
|
|
|
696 |
(function () {
|
|
|
697 |
/**
|
|
|
698 |
* Helper for adding a query parameter to an existing path that may already
|
|
|
699 |
* contain one or a hash.
|
|
|
700 |
*
|
|
|
701 |
* @param {string} path
|
|
|
702 |
* @param {string} parameter
|
|
|
703 |
* @return {string}
|
|
|
704 |
*/
|
|
|
705 |
H5P.addQueryParameter = function (path, parameter) {
|
|
|
706 |
let newPath, secondSplit;
|
|
|
707 |
const firstSplit = path.split('?');
|
|
|
708 |
if (firstSplit[1]) {
|
|
|
709 |
// There is already an existing query
|
|
|
710 |
secondSplit = firstSplit[1].split('#');
|
|
|
711 |
newPath = firstSplit[0] + '?' + secondSplit[0] + '&';
|
|
|
712 |
}
|
|
|
713 |
else {
|
|
|
714 |
// No existing query, just need to take care of the hash
|
|
|
715 |
secondSplit = firstSplit[0].split('#');
|
|
|
716 |
newPath = secondSplit[0] + '?';
|
|
|
717 |
}
|
|
|
718 |
newPath += parameter;
|
|
|
719 |
if (secondSplit[1]) {
|
|
|
720 |
// Add back the hash
|
|
|
721 |
newPath += '#' + secondSplit[1];
|
|
|
722 |
}
|
|
|
723 |
return newPath;
|
|
|
724 |
};
|
|
|
725 |
|
|
|
726 |
/**
|
|
|
727 |
* Helper for setting the crossOrigin attribute + the complete correct source.
|
|
|
728 |
* Note: This will start loading the resource.
|
|
|
729 |
*
|
|
|
730 |
* @param {Element} element DOM element, typically img, video or audio
|
|
|
731 |
* @param {Object} source File object from parameters/json_content (created by H5PEditor)
|
|
|
732 |
* @param {number} contentId Needed to determine the complete correct file path
|
|
|
733 |
*/
|
|
|
734 |
H5P.setSource = function (element, source, contentId) {
|
|
|
735 |
let path = source.path;
|
|
|
736 |
|
|
|
737 |
const crossOrigin = H5P.getCrossOrigin(source);
|
|
|
738 |
if (crossOrigin) {
|
|
|
739 |
element.crossOrigin = crossOrigin;
|
|
|
740 |
|
|
|
741 |
if (H5PIntegration.crossoriginCacheBuster) {
|
|
|
742 |
// Some sites may want to add a cache buster in case the same resource
|
|
|
743 |
// is used elsewhere without the crossOrigin attribute
|
|
|
744 |
path = H5P.addQueryParameter(path, H5PIntegration.crossoriginCacheBuster);
|
|
|
745 |
}
|
|
|
746 |
}
|
|
|
747 |
else {
|
|
|
748 |
// In case this element has been used before.
|
|
|
749 |
element.removeAttribute('crossorigin');
|
|
|
750 |
}
|
|
|
751 |
|
|
|
752 |
element.src = H5P.getPath(path, contentId);
|
|
|
753 |
};
|
|
|
754 |
|
|
|
755 |
/**
|
|
|
756 |
* Check if the given path has a protocol.
|
|
|
757 |
*
|
|
|
758 |
* @private
|
|
|
759 |
* @param {string} path
|
|
|
760 |
* @return {string}
|
|
|
761 |
*/
|
|
|
762 |
var hasProtocol = function (path) {
|
|
|
763 |
return path.match(/^[a-z0-9]+:\/\//i);
|
|
|
764 |
};
|
|
|
765 |
|
|
|
766 |
/**
|
|
|
767 |
* Get the crossOrigin policy to use for img, video and audio tags on the current site.
|
|
|
768 |
*
|
|
|
769 |
* @param {Object|string} source File object from parameters/json_content - Can also be URL(deprecated usage)
|
|
|
770 |
* @returns {string|null} crossOrigin attribute value required by the source
|
|
|
771 |
*/
|
|
|
772 |
H5P.getCrossOrigin = function (source) {
|
|
|
773 |
if (typeof source !== 'object') {
|
|
|
774 |
// Deprecated usage.
|
|
|
775 |
return H5PIntegration.crossorigin && H5PIntegration.crossoriginRegex && source.match(H5PIntegration.crossoriginRegex) ? H5PIntegration.crossorigin : null;
|
|
|
776 |
}
|
|
|
777 |
|
|
|
778 |
if (H5PIntegration.crossorigin && !hasProtocol(source.path)) {
|
|
|
779 |
// This is a local file, use the local crossOrigin policy.
|
|
|
780 |
return H5PIntegration.crossorigin;
|
|
|
781 |
// Note: We cannot use this for all external sources since we do not know
|
|
|
782 |
// each server's individual policy. We could add support for a list of
|
|
|
783 |
// external sources and their policy later on.
|
|
|
784 |
}
|
|
|
785 |
};
|
|
|
786 |
|
|
|
787 |
/**
|
|
|
788 |
* Find the path to the content files based on the id of the content.
|
|
|
789 |
* Also identifies and returns absolute paths.
|
|
|
790 |
*
|
|
|
791 |
* @param {string} path
|
|
|
792 |
* Relative to content folder or absolute.
|
|
|
793 |
* @param {number} contentId
|
|
|
794 |
* ID of the content requesting the path.
|
|
|
795 |
* @returns {string}
|
|
|
796 |
* Complete URL to path.
|
|
|
797 |
*/
|
|
|
798 |
H5P.getPath = function (path, contentId) {
|
|
|
799 |
if (hasProtocol(path)) {
|
|
|
800 |
return path;
|
|
|
801 |
}
|
|
|
802 |
|
|
|
803 |
var prefix;
|
|
|
804 |
var isTmpFile = (path.substr(-4,4) === '#tmp');
|
|
|
805 |
if (contentId !== undefined && !isTmpFile) {
|
|
|
806 |
// Check for custom override URL
|
|
|
807 |
if (H5PIntegration.contents !== undefined &&
|
|
|
808 |
H5PIntegration.contents['cid-' + contentId]) {
|
|
|
809 |
prefix = H5PIntegration.contents['cid-' + contentId].contentUrl;
|
|
|
810 |
}
|
|
|
811 |
if (!prefix) {
|
|
|
812 |
prefix = H5PIntegration.url + '/content/' + contentId;
|
|
|
813 |
}
|
|
|
814 |
}
|
|
|
815 |
else if (window.H5PEditor !== undefined) {
|
|
|
816 |
prefix = H5PEditor.filesPath;
|
|
|
817 |
}
|
|
|
818 |
else {
|
|
|
819 |
return;
|
|
|
820 |
}
|
|
|
821 |
|
|
|
822 |
if (!hasProtocol(prefix)) {
|
|
|
823 |
// Use absolute urls
|
|
|
824 |
prefix = window.location.protocol + "//" + window.location.host + prefix;
|
|
|
825 |
}
|
|
|
826 |
|
|
|
827 |
return prefix + '/' + path;
|
|
|
828 |
};
|
|
|
829 |
})();
|
|
|
830 |
|
|
|
831 |
/**
|
|
|
832 |
* THIS FUNCTION IS DEPRECATED, USE getPath INSTEAD
|
|
|
833 |
* Will be remove march 2016.
|
|
|
834 |
*
|
|
|
835 |
* Find the path to the content files folder based on the id of the content
|
|
|
836 |
*
|
|
|
837 |
* @deprecated
|
|
|
838 |
* Will be removed march 2016.
|
|
|
839 |
* @param contentId
|
|
|
840 |
* Id of the content requesting the path
|
|
|
841 |
* @returns {string}
|
|
|
842 |
* URL
|
|
|
843 |
*/
|
|
|
844 |
H5P.getContentPath = function (contentId) {
|
|
|
845 |
return H5PIntegration.url + '/content/' + contentId;
|
|
|
846 |
};
|
|
|
847 |
|
|
|
848 |
/**
|
|
|
849 |
* Get library class constructor from H5P by classname.
|
|
|
850 |
* Note that this class will only work for resolve "H5P.NameWithoutDot".
|
|
|
851 |
* Also check out {@link H5P.newRunnable}
|
|
|
852 |
*
|
|
|
853 |
* Used from libraries to construct instances of other libraries' objects by name.
|
|
|
854 |
*
|
|
|
855 |
* @param {string} name Name of library
|
|
|
856 |
* @returns {Object} Class constructor
|
|
|
857 |
*/
|
|
|
858 |
H5P.classFromName = function (name) {
|
|
|
859 |
var arr = name.split(".");
|
|
|
860 |
return this[arr[arr.length - 1]];
|
|
|
861 |
};
|
|
|
862 |
|
|
|
863 |
/**
|
|
|
864 |
* A safe way of creating a new instance of a runnable H5P.
|
|
|
865 |
*
|
|
|
866 |
* @param {Object} library
|
|
|
867 |
* Library/action object form params.
|
|
|
868 |
* @param {number} contentId
|
|
|
869 |
* Identifies the content.
|
|
|
870 |
* @param {H5P.jQuery} [$attachTo]
|
|
|
871 |
* Element to attach the instance to.
|
|
|
872 |
* @param {boolean} [skipResize]
|
|
|
873 |
* Skip triggering of the resize event after attaching.
|
|
|
874 |
* @param {Object} [extras]
|
|
|
875 |
* Extra parameters for the H5P content constructor
|
|
|
876 |
* @returns {Object}
|
|
|
877 |
* Instance.
|
|
|
878 |
*/
|
|
|
879 |
H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) {
|
|
|
880 |
var nameSplit, versionSplit, machineName;
|
|
|
881 |
try {
|
|
|
882 |
nameSplit = library.library.split(' ', 2);
|
|
|
883 |
machineName = nameSplit[0];
|
|
|
884 |
versionSplit = nameSplit[1].split('.', 2);
|
|
|
885 |
}
|
|
|
886 |
catch (err) {
|
|
|
887 |
return H5P.error('Invalid library string: ' + library.library);
|
|
|
888 |
}
|
|
|
889 |
|
|
|
890 |
if ((library.params instanceof Object) !== true || (library.params instanceof Array) === true) {
|
|
|
891 |
H5P.error('Invalid library params for: ' + library.library);
|
|
|
892 |
return H5P.error(library.params);
|
|
|
893 |
}
|
|
|
894 |
|
|
|
895 |
// Find constructor function
|
|
|
896 |
var constructor;
|
|
|
897 |
try {
|
|
|
898 |
nameSplit = nameSplit[0].split('.');
|
|
|
899 |
constructor = window;
|
|
|
900 |
for (var i = 0; i < nameSplit.length; i++) {
|
|
|
901 |
constructor = constructor[nameSplit[i]];
|
|
|
902 |
}
|
|
|
903 |
if (typeof constructor !== 'function') {
|
|
|
904 |
throw null;
|
|
|
905 |
}
|
|
|
906 |
}
|
|
|
907 |
catch (err) {
|
|
|
908 |
return H5P.error('Unable to find constructor for: ' + library.library);
|
|
|
909 |
}
|
|
|
910 |
|
|
|
911 |
if (extras === undefined) {
|
|
|
912 |
extras = {};
|
|
|
913 |
}
|
|
|
914 |
if (library.subContentId) {
|
|
|
915 |
extras.subContentId = library.subContentId;
|
|
|
916 |
}
|
|
|
917 |
|
|
|
918 |
if (library.userDatas && library.userDatas.state && H5PIntegration.saveFreq) {
|
|
|
919 |
extras.previousState = library.userDatas.state;
|
|
|
920 |
}
|
|
|
921 |
|
|
|
922 |
if (library.metadata) {
|
|
|
923 |
extras.metadata = library.metadata;
|
|
|
924 |
}
|
|
|
925 |
|
|
|
926 |
// Makes all H5P libraries extend H5P.ContentType:
|
|
|
927 |
var standalone = extras.standalone || false;
|
|
|
928 |
// This order makes it possible for an H5P library to override H5P.ContentType functions!
|
|
|
929 |
constructor.prototype = H5P.jQuery.extend({}, H5P.ContentType(standalone).prototype, constructor.prototype);
|
|
|
930 |
|
|
|
931 |
var instance;
|
|
|
932 |
// Some old library versions have their own custom third parameter.
|
|
|
933 |
// Make sure we don't send them the extras.
|
|
|
934 |
// (they will interpret it as something else)
|
|
|
935 |
if (H5P.jQuery.inArray(library.library, ['H5P.CoursePresentation 1.0', 'H5P.CoursePresentation 1.1', 'H5P.CoursePresentation 1.2', 'H5P.CoursePresentation 1.3']) > -1) {
|
|
|
936 |
instance = new constructor(library.params, contentId);
|
|
|
937 |
}
|
|
|
938 |
else {
|
|
|
939 |
instance = new constructor(library.params, contentId, extras);
|
|
|
940 |
}
|
|
|
941 |
|
|
|
942 |
if (instance.$ === undefined) {
|
|
|
943 |
instance.$ = H5P.jQuery(instance);
|
|
|
944 |
}
|
|
|
945 |
|
|
|
946 |
if (instance.contentId === undefined) {
|
|
|
947 |
instance.contentId = contentId;
|
|
|
948 |
}
|
|
|
949 |
if (instance.subContentId === undefined && library.subContentId) {
|
|
|
950 |
instance.subContentId = library.subContentId;
|
|
|
951 |
}
|
|
|
952 |
if (instance.parent === undefined && extras && extras.parent) {
|
|
|
953 |
instance.parent = extras.parent;
|
|
|
954 |
}
|
|
|
955 |
if (instance.libraryInfo === undefined) {
|
|
|
956 |
instance.libraryInfo = {
|
|
|
957 |
versionedName: library.library,
|
|
|
958 |
versionedNameNoSpaces: machineName + '-' + versionSplit[0] + '.' + versionSplit[1],
|
|
|
959 |
machineName: machineName,
|
|
|
960 |
majorVersion: versionSplit[0],
|
|
|
961 |
minorVersion: versionSplit[1]
|
|
|
962 |
};
|
|
|
963 |
}
|
|
|
964 |
|
|
|
965 |
if ($attachTo !== undefined) {
|
|
|
966 |
$attachTo.toggleClass('h5p-standalone', standalone);
|
|
|
967 |
instance.attach($attachTo);
|
|
|
968 |
H5P.trigger(instance, 'domChanged', {
|
|
|
969 |
'$target': $attachTo,
|
|
|
970 |
'library': machineName,
|
|
|
971 |
'key': 'newLibrary'
|
|
|
972 |
}, {'bubbles': true, 'external': true});
|
|
|
973 |
|
|
|
974 |
if (skipResize === undefined || !skipResize) {
|
|
|
975 |
// Resize content.
|
|
|
976 |
H5P.trigger(instance, 'resize');
|
|
|
977 |
}
|
|
|
978 |
}
|
|
|
979 |
return instance;
|
|
|
980 |
};
|
|
|
981 |
|
|
|
982 |
/**
|
|
|
983 |
* Used to print useful error messages. (to JavaScript error console)
|
|
|
984 |
*
|
|
|
985 |
* @param {*} err Error to print.
|
|
|
986 |
*/
|
|
|
987 |
H5P.error = function (err) {
|
|
|
988 |
if (window.console !== undefined && console.error !== undefined) {
|
|
|
989 |
console.error(err.stack ? err.stack : err);
|
|
|
990 |
}
|
|
|
991 |
};
|
|
|
992 |
|
|
|
993 |
/**
|
|
|
994 |
* Translate text strings.
|
|
|
995 |
*
|
|
|
996 |
* @param {string} key
|
|
|
997 |
* Translation identifier, may only contain a-zA-Z0-9. No spaces or special chars.
|
|
|
998 |
* @param {Object} [vars]
|
|
|
999 |
* Data for placeholders.
|
|
|
1000 |
* @param {string} [ns]
|
|
|
1001 |
* Translation namespace. Defaults to H5P.
|
|
|
1002 |
* @returns {string}
|
|
|
1003 |
* Translated text
|
|
|
1004 |
*/
|
|
|
1005 |
H5P.t = function (key, vars, ns) {
|
|
|
1006 |
if (ns === undefined) {
|
|
|
1007 |
ns = 'H5P';
|
|
|
1008 |
}
|
|
|
1009 |
|
|
|
1010 |
if (H5PIntegration.l10n[ns] === undefined) {
|
|
|
1011 |
return '[Missing translation namespace "' + ns + '"]';
|
|
|
1012 |
}
|
|
|
1013 |
|
|
|
1014 |
if (H5PIntegration.l10n[ns][key] === undefined) {
|
|
|
1015 |
return '[Missing translation "' + key + '" in "' + ns + '"]';
|
|
|
1016 |
}
|
|
|
1017 |
|
|
|
1018 |
var translation = H5PIntegration.l10n[ns][key];
|
|
|
1019 |
|
|
|
1020 |
if (vars !== undefined) {
|
|
|
1021 |
// Replace placeholder with variables.
|
|
|
1022 |
for (var placeholder in vars) {
|
|
|
1023 |
translation = translation.replace(placeholder, vars[placeholder]);
|
|
|
1024 |
}
|
|
|
1025 |
}
|
|
|
1026 |
|
|
|
1027 |
return translation;
|
|
|
1028 |
};
|
|
|
1029 |
|
|
|
1030 |
/**
|
|
|
1031 |
* Creates a new popup dialog over the H5P content.
|
|
|
1032 |
*
|
|
|
1033 |
* @class
|
|
|
1034 |
* @param {string} name
|
|
|
1035 |
* Used for html class.
|
|
|
1036 |
* @param {string} title
|
|
|
1037 |
* Used for header.
|
|
|
1038 |
* @param {string} content
|
|
|
1039 |
* Displayed inside the dialog.
|
|
|
1040 |
* @param {H5P.jQuery} $element
|
|
|
1041 |
* Which DOM element the dialog should be inserted after.
|
|
|
1042 |
* @param {H5P.jQuery} $returnElement
|
|
|
1043 |
* Which DOM element the focus should be moved to on close
|
|
|
1044 |
*/
|
|
|
1045 |
H5P.Dialog = function (name, title, content, $element, $returnElement) {
|
|
|
1046 |
/** @alias H5P.Dialog# */
|
|
|
1047 |
var self = this;
|
|
|
1048 |
var $dialog = H5P.jQuery('<div class="h5p-popup-dialog h5p-' + name + '-dialog" aria-labelledby="' + name + '-dialog-header" aria-modal="true" role="dialog" tabindex="-1">\
|
|
|
1049 |
<div class="h5p-inner">\
|
|
|
1050 |
<h2 id="' + name + '-dialog-header">' + title + '</h2>\
|
|
|
1051 |
<div class="h5p-scroll-content">' + content + '</div>\
|
|
|
1052 |
<div class="h5p-close" role="button" tabindex="0" aria-label="' + H5P.t('close') + '" title="' + H5P.t('close') + '"></div>\
|
|
|
1053 |
</div>\
|
|
|
1054 |
</div>')
|
|
|
1055 |
.insertAfter($element)
|
|
|
1056 |
.click(function (e) {
|
|
|
1057 |
if (e && e.originalEvent && e.originalEvent.preventClosing) {
|
|
|
1058 |
return;
|
|
|
1059 |
}
|
|
|
1060 |
|
|
|
1061 |
self.close();
|
|
|
1062 |
})
|
|
|
1063 |
.children('.h5p-inner')
|
|
|
1064 |
.click(function (e) {
|
|
|
1065 |
e.originalEvent.preventClosing = true;
|
|
|
1066 |
})
|
|
|
1067 |
.find('.h5p-close')
|
|
|
1068 |
.click(function () {
|
|
|
1069 |
self.close();
|
|
|
1070 |
})
|
|
|
1071 |
.keypress(function (e) {
|
|
|
1072 |
if (e.which === 13 || e.which === 32) {
|
|
|
1073 |
self.close();
|
|
|
1074 |
return false;
|
|
|
1075 |
}
|
|
|
1076 |
})
|
|
|
1077 |
.end()
|
|
|
1078 |
.find('a')
|
|
|
1079 |
.click(function (e) {
|
|
|
1080 |
e.stopPropagation();
|
|
|
1081 |
})
|
|
|
1082 |
.end()
|
|
|
1083 |
.end();
|
|
|
1084 |
|
|
|
1085 |
/**
|
|
|
1086 |
* Opens the dialog.
|
|
|
1087 |
*/
|
|
|
1088 |
self.open = function (scrollbar) {
|
|
|
1089 |
if (scrollbar) {
|
|
|
1090 |
$dialog.css('height', '100%');
|
|
|
1091 |
}
|
|
|
1092 |
setTimeout(function () {
|
|
|
1093 |
$dialog.addClass('h5p-open'); // Fade in
|
|
|
1094 |
// Triggering an event, in case something has to be done after dialog has been opened.
|
|
|
1095 |
H5P.jQuery(self).trigger('dialog-opened', [$dialog]);
|
|
|
1096 |
$dialog.focus();
|
|
|
1097 |
}, 1);
|
|
|
1098 |
};
|
|
|
1099 |
|
|
|
1100 |
/**
|
|
|
1101 |
* Closes the dialog.
|
|
|
1102 |
*/
|
|
|
1103 |
self.close = function () {
|
|
|
1104 |
$dialog.removeClass('h5p-open'); // Fade out
|
|
|
1105 |
setTimeout(function () {
|
|
|
1106 |
$dialog.remove();
|
|
|
1107 |
H5P.jQuery(self).trigger('dialog-closed', [$dialog]);
|
|
|
1108 |
$element.attr('tabindex', '-1');
|
|
|
1109 |
if ($returnElement) {
|
|
|
1110 |
$returnElement.focus();
|
|
|
1111 |
}
|
|
|
1112 |
else {
|
|
|
1113 |
$element.focus();
|
|
|
1114 |
}
|
|
|
1115 |
}, 200);
|
|
|
1116 |
};
|
|
|
1117 |
};
|
|
|
1118 |
|
|
|
1119 |
/**
|
|
|
1120 |
* Gather copyright information for the given content.
|
|
|
1121 |
*
|
|
|
1122 |
* @param {Object} instance
|
|
|
1123 |
* H5P instance to get copyright information for.
|
|
|
1124 |
* @param {Object} parameters
|
|
|
1125 |
* Parameters of the content instance.
|
|
|
1126 |
* @param {number} contentId
|
|
|
1127 |
* Identifies the H5P content
|
|
|
1128 |
* @param {Object} metadata
|
|
|
1129 |
* Metadata of the content instance.
|
|
|
1130 |
* @returns {string} Copyright information.
|
|
|
1131 |
*/
|
|
|
1132 |
H5P.getCopyrights = function (instance, parameters, contentId, metadata) {
|
|
|
1133 |
var copyrights;
|
|
|
1134 |
|
|
|
1135 |
if (instance.getCopyrights !== undefined) {
|
|
|
1136 |
try {
|
|
|
1137 |
// Use the instance's own copyright generator
|
|
|
1138 |
copyrights = instance.getCopyrights();
|
|
|
1139 |
}
|
|
|
1140 |
catch (err) {
|
|
|
1141 |
// Failed, prevent crashing page.
|
|
|
1142 |
}
|
|
|
1143 |
}
|
|
|
1144 |
|
|
|
1145 |
if (copyrights === undefined) {
|
|
|
1146 |
// Create a generic flat copyright list
|
|
|
1147 |
copyrights = new H5P.ContentCopyrights();
|
|
|
1148 |
H5P.findCopyrights(copyrights, parameters, contentId);
|
|
|
1149 |
}
|
|
|
1150 |
|
|
|
1151 |
var metadataCopyrights = H5P.buildMetadataCopyrights(metadata, instance.libraryInfo.machineName);
|
|
|
1152 |
if (metadataCopyrights !== undefined) {
|
|
|
1153 |
copyrights.addMediaInFront(metadataCopyrights);
|
|
|
1154 |
}
|
|
|
1155 |
|
|
|
1156 |
if (copyrights !== undefined) {
|
|
|
1157 |
// Convert to string
|
|
|
1158 |
copyrights = copyrights.toString();
|
|
|
1159 |
}
|
|
|
1160 |
return copyrights;
|
|
|
1161 |
};
|
|
|
1162 |
|
|
|
1163 |
/**
|
|
|
1164 |
* Gather a flat list of copyright information from the given parameters.
|
|
|
1165 |
*
|
|
|
1166 |
* @param {H5P.ContentCopyrights} info
|
|
|
1167 |
* Used to collect all information in.
|
|
|
1168 |
* @param {(Object|Array)} parameters
|
|
|
1169 |
* To search for file objects in.
|
|
|
1170 |
* @param {number} contentId
|
|
|
1171 |
* Used to insert thumbnails for images.
|
|
|
1172 |
* @param {Object} extras - Extras.
|
|
|
1173 |
* @param {object} extras.metadata - Metadata
|
|
|
1174 |
* @param {object} extras.machineName - Library name of some kind.
|
|
|
1175 |
* Metadata of the content instance.
|
|
|
1176 |
*/
|
|
|
1177 |
H5P.findCopyrights = function (info, parameters, contentId, extras) {
|
|
|
1178 |
// If extras are
|
|
|
1179 |
if (extras) {
|
|
|
1180 |
extras.params = parameters;
|
|
|
1181 |
buildFromMetadata(extras, extras.machineName, contentId);
|
|
|
1182 |
}
|
|
|
1183 |
|
|
|
1184 |
var lastContentTypeName;
|
|
|
1185 |
// Cycle through parameters
|
|
|
1186 |
for (var field in parameters) {
|
|
|
1187 |
if (!parameters.hasOwnProperty(field)) {
|
|
|
1188 |
continue; // Do not check
|
|
|
1189 |
}
|
|
|
1190 |
|
|
|
1191 |
/**
|
|
|
1192 |
* @deprecated This hack should be removed after 2017-11-01
|
|
|
1193 |
* The code that was using this was removed by HFP-574
|
|
|
1194 |
* This note was seen on 2018-04-04, and consultation with
|
|
|
1195 |
* higher authorities lead to keeping the code for now ;-)
|
|
|
1196 |
*/
|
|
|
1197 |
if (field === 'overrideSettings') {
|
|
|
1198 |
console.warn("The semantics field 'overrideSettings' is DEPRECATED and should not be used.");
|
|
|
1199 |
console.warn(parameters);
|
|
|
1200 |
continue;
|
|
|
1201 |
}
|
|
|
1202 |
|
|
|
1203 |
var value = parameters[field];
|
|
|
1204 |
|
|
|
1205 |
if (value && value.library && typeof value.library === 'string') {
|
|
|
1206 |
lastContentTypeName = value.library.split(' ')[0];
|
|
|
1207 |
}
|
|
|
1208 |
else if (value && value.library && typeof value.library === 'object') {
|
|
|
1209 |
lastContentTypeName = (value.library.library && typeof value.library.library === 'string') ? value.library.library.split(' ')[0] : lastContentTypeName;
|
|
|
1210 |
}
|
|
|
1211 |
|
|
|
1212 |
if (value instanceof Array) {
|
|
|
1213 |
// Cycle through array
|
|
|
1214 |
H5P.findCopyrights(info, value, contentId);
|
|
|
1215 |
}
|
|
|
1216 |
else if (value instanceof Object) {
|
|
|
1217 |
buildFromMetadata(value, lastContentTypeName, contentId);
|
|
|
1218 |
|
|
|
1219 |
// Check if object is a file with copyrights (old core)
|
|
|
1220 |
if (value.copyright === undefined ||
|
|
|
1221 |
value.copyright.license === undefined ||
|
|
|
1222 |
value.path === undefined ||
|
|
|
1223 |
value.mime === undefined) {
|
|
|
1224 |
|
|
|
1225 |
// Nope, cycle throught object
|
|
|
1226 |
H5P.findCopyrights(info, value, contentId);
|
|
|
1227 |
}
|
|
|
1228 |
else {
|
|
|
1229 |
// Found file, add copyrights
|
|
|
1230 |
var copyrights = new H5P.MediaCopyright(value.copyright);
|
|
|
1231 |
if (value.width !== undefined && value.height !== undefined) {
|
|
|
1232 |
copyrights.setThumbnail(new H5P.Thumbnail(H5P.getPath(value.path, contentId), value.width, value.height));
|
|
|
1233 |
}
|
|
|
1234 |
info.addMedia(copyrights);
|
|
|
1235 |
}
|
|
|
1236 |
}
|
|
|
1237 |
}
|
|
|
1238 |
|
|
|
1239 |
function buildFromMetadata(data, name, contentId) {
|
|
|
1240 |
if (data.metadata) {
|
|
|
1241 |
const metadataCopyrights = H5P.buildMetadataCopyrights(data.metadata, name);
|
|
|
1242 |
if (metadataCopyrights !== undefined) {
|
|
|
1243 |
if (data.params && data.params.contentName === 'Image' && data.params.file) {
|
|
|
1244 |
const path = data.params.file.path;
|
|
|
1245 |
const width = data.params.file.width;
|
|
|
1246 |
const height = data.params.file.height;
|
|
|
1247 |
metadataCopyrights.setThumbnail(new H5P.Thumbnail(H5P.getPath(path, contentId), width, height, data.params.alt));
|
|
|
1248 |
}
|
|
|
1249 |
info.addMedia(metadataCopyrights);
|
|
|
1250 |
}
|
|
|
1251 |
}
|
|
|
1252 |
}
|
|
|
1253 |
};
|
|
|
1254 |
|
|
|
1255 |
H5P.buildMetadataCopyrights = function (metadata) {
|
|
|
1256 |
if (metadata && metadata.license !== undefined && metadata.license !== 'U') {
|
|
|
1257 |
var dataset = {
|
|
|
1258 |
contentType: metadata.contentType,
|
|
|
1259 |
title: metadata.title,
|
|
|
1260 |
author: (metadata.authors && metadata.authors.length > 0) ? metadata.authors.map(function (author) {
|
|
|
1261 |
return (author.role) ? author.name + ' (' + author.role + ')' : author.name;
|
|
|
1262 |
}).join(', ') : undefined,
|
|
|
1263 |
source: metadata.source,
|
|
|
1264 |
year: (metadata.yearFrom) ? (metadata.yearFrom + ((metadata.yearTo) ? '-' + metadata.yearTo: '')) : undefined,
|
|
|
1265 |
license: metadata.license,
|
|
|
1266 |
version: metadata.licenseVersion,
|
|
|
1267 |
licenseExtras: metadata.licenseExtras,
|
|
|
1268 |
changes: (metadata.changes && metadata.changes.length > 0) ? metadata.changes.map(function (change) {
|
|
|
1269 |
return change.log + (change.author ? ', ' + change.author : '') + (change.date ? ', ' + change.date : '');
|
|
|
1270 |
}).join(' / ') : undefined
|
|
|
1271 |
};
|
|
|
1272 |
|
|
|
1273 |
return new H5P.MediaCopyright(dataset);
|
|
|
1274 |
}
|
|
|
1275 |
};
|
|
|
1276 |
|
|
|
1277 |
/**
|
|
|
1278 |
* Display a dialog containing the download button and copy button.
|
|
|
1279 |
*
|
|
|
1280 |
* @param {H5P.jQuery} $element
|
|
|
1281 |
* @param {Object} contentData
|
|
|
1282 |
* @param {Object} library
|
|
|
1283 |
* @param {Object} instance
|
|
|
1284 |
* @param {number} contentId
|
|
|
1285 |
*/
|
|
|
1286 |
H5P.openReuseDialog = function ($element, contentData, library, instance, contentId) {
|
|
|
1287 |
let html = '';
|
|
|
1288 |
if (contentData.displayOptions.export) {
|
|
|
1289 |
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>';
|
|
|
1290 |
}
|
|
|
1291 |
if (contentData.displayOptions.export && contentData.displayOptions.copy) {
|
|
|
1292 |
html += '<div class="h5p-horizontal-line-text"><span>or</span></div>';
|
|
|
1293 |
}
|
|
|
1294 |
if (contentData.displayOptions.copy) {
|
|
|
1295 |
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>';
|
|
|
1296 |
}
|
|
|
1297 |
|
|
|
1298 |
const dialog = new H5P.Dialog('reuse', H5P.t('reuseContent'), html, $element);
|
|
|
1299 |
|
|
|
1300 |
// Selecting embed code when dialog is opened
|
|
|
1301 |
H5P.jQuery(dialog).on('dialog-opened', function (e, $dialog) {
|
|
|
1302 |
H5P.jQuery('<a href="https://h5p.org/node/442225" target="_blank">More Info</a>').click(function (e) {
|
|
|
1303 |
e.stopPropagation();
|
|
|
1304 |
}).appendTo($dialog.find('h2'));
|
|
|
1305 |
$dialog.find('.h5p-download-button').click(function () {
|
|
|
1306 |
window.location.href = contentData.exportUrl;
|
|
|
1307 |
instance.triggerXAPI('downloaded');
|
|
|
1308 |
dialog.close();
|
|
|
1309 |
});
|
|
|
1310 |
$dialog.find('.h5p-copy-button').click(function () {
|
|
|
1311 |
const item = new H5P.ClipboardItem(library);
|
|
|
1312 |
item.contentId = contentId;
|
|
|
1313 |
H5P.setClipboard(item);
|
|
|
1314 |
instance.triggerXAPI('copied');
|
|
|
1315 |
dialog.close();
|
|
|
1316 |
H5P.attachToastTo(
|
|
|
1317 |
H5P.jQuery('.h5p-content:first')[0],
|
|
|
1318 |
H5P.t('contentCopied'),
|
|
|
1319 |
{
|
|
|
1320 |
position: {
|
|
|
1321 |
horizontal: 'centered',
|
|
|
1322 |
vertical: 'centered',
|
|
|
1323 |
noOverflowX: true
|
|
|
1324 |
}
|
|
|
1325 |
}
|
|
|
1326 |
);
|
|
|
1327 |
});
|
|
|
1328 |
H5P.trigger(instance, 'resize');
|
|
|
1329 |
}).on('dialog-closed', function () {
|
|
|
1330 |
H5P.trigger(instance, 'resize');
|
|
|
1331 |
});
|
|
|
1332 |
|
|
|
1333 |
dialog.open();
|
|
|
1334 |
};
|
|
|
1335 |
|
|
|
1336 |
/**
|
|
|
1337 |
* Display a dialog containing the embed code.
|
|
|
1338 |
*
|
|
|
1339 |
* @param {H5P.jQuery} $element
|
|
|
1340 |
* Element to insert dialog after.
|
|
|
1341 |
* @param {string} embedCode
|
|
|
1342 |
* The embed code.
|
|
|
1343 |
* @param {string} resizeCode
|
|
|
1344 |
* The advanced resize code
|
|
|
1345 |
* @param {Object} size
|
|
|
1346 |
* The content's size.
|
|
|
1347 |
* @param {number} size.width
|
|
|
1348 |
* @param {number} size.height
|
|
|
1349 |
*/
|
|
|
1350 |
H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size, instance) {
|
|
|
1351 |
var fullEmbedCode = embedCode + resizeCode;
|
|
|
1352 |
var dialog = new H5P.Dialog('embed', H5P.t('embed'), '<textarea class="h5p-embed-code-container" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>' + H5P.t('size') + ': <input aria-label="'+ H5P.t('width') +'" type="text" value="' + Math.ceil(size.width) + '" class="h5p-embed-size"/> × <input aria-label="'+ H5P.t('width') +'" type="text" value="' + Math.ceil(size.height) + '" class="h5p-embed-size"/> px<br/><div role="button" tabindex="0" class="h5p-expander">' + H5P.t('showAdvanced') + '</div><div class="h5p-expander-content"><p>' + H5P.t('advancedHelp') + '</p><textarea class="h5p-embed-code-container" autocorrect="off" autocapitalize="off" spellcheck="false">' + resizeCode + '</textarea></div>', $element);
|
|
|
1353 |
|
|
|
1354 |
// Selecting embed code when dialog is opened
|
|
|
1355 |
H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) {
|
|
|
1356 |
var $inner = $dialog.find('.h5p-inner');
|
|
|
1357 |
var $scroll = $inner.find('.h5p-scroll-content');
|
|
|
1358 |
var diff = $scroll.outerHeight() - $scroll.innerHeight();
|
|
|
1359 |
var positionInner = function () {
|
|
|
1360 |
H5P.trigger(instance, 'resize');
|
|
|
1361 |
};
|
|
|
1362 |
|
|
|
1363 |
// Handle changing of width/height
|
|
|
1364 |
var $w = $dialog.find('.h5p-embed-size:eq(0)');
|
|
|
1365 |
var $h = $dialog.find('.h5p-embed-size:eq(1)');
|
|
|
1366 |
var getNum = function ($e, d) {
|
|
|
1367 |
var num = parseFloat($e.val());
|
|
|
1368 |
if (isNaN(num)) {
|
|
|
1369 |
return d;
|
|
|
1370 |
}
|
|
|
1371 |
return Math.ceil(num);
|
|
|
1372 |
};
|
|
|
1373 |
var updateEmbed = function () {
|
|
|
1374 |
$dialog.find('.h5p-embed-code-container:first').val(fullEmbedCode.replace(':w', getNum($w, size.width)).replace(':h', getNum($h, size.height)));
|
|
|
1375 |
};
|
|
|
1376 |
|
|
|
1377 |
$w.change(updateEmbed);
|
|
|
1378 |
$h.change(updateEmbed);
|
|
|
1379 |
updateEmbed();
|
|
|
1380 |
|
|
|
1381 |
// Select text and expand textareas
|
|
|
1382 |
$dialog.find('.h5p-embed-code-container').each(function () {
|
|
|
1383 |
H5P.jQuery(this).css('height', this.scrollHeight + 'px').focus(function () {
|
|
|
1384 |
H5P.jQuery(this).select();
|
|
|
1385 |
});
|
|
|
1386 |
});
|
|
|
1387 |
$dialog.find('.h5p-embed-code-container').eq(0).select();
|
|
|
1388 |
positionInner();
|
|
|
1389 |
|
|
|
1390 |
// Expand advanced embed
|
|
|
1391 |
var expand = function () {
|
|
|
1392 |
var $expander = H5P.jQuery(this);
|
|
|
1393 |
var $content = $expander.next();
|
|
|
1394 |
if ($content.is(':visible')) {
|
|
|
1395 |
$expander.removeClass('h5p-open').text(H5P.t('showAdvanced')).attr('aria-expanded', 'true');
|
|
|
1396 |
$content.hide();
|
|
|
1397 |
}
|
|
|
1398 |
else {
|
|
|
1399 |
$expander.addClass('h5p-open').text(H5P.t('hideAdvanced')).attr('aria-expanded', 'false');
|
|
|
1400 |
$content.show();
|
|
|
1401 |
}
|
|
|
1402 |
$dialog.find('.h5p-embed-code-container').each(function () {
|
|
|
1403 |
H5P.jQuery(this).css('height', this.scrollHeight + 'px');
|
|
|
1404 |
});
|
|
|
1405 |
positionInner();
|
|
|
1406 |
};
|
|
|
1407 |
$dialog.find('.h5p-expander').click(expand).keypress(function (event) {
|
|
|
1408 |
if (event.keyCode === 32) {
|
|
|
1409 |
expand.apply(this);
|
|
|
1410 |
return false;
|
|
|
1411 |
}
|
|
|
1412 |
});
|
|
|
1413 |
}).on('dialog-closed', function () {
|
|
|
1414 |
H5P.trigger(instance, 'resize');
|
|
|
1415 |
});
|
|
|
1416 |
|
|
|
1417 |
dialog.open();
|
|
|
1418 |
};
|
|
|
1419 |
|
|
|
1420 |
/**
|
|
|
1421 |
* Show a toast message.
|
|
|
1422 |
*
|
|
|
1423 |
* The reference element could be dom elements the toast should be attached to,
|
|
|
1424 |
* or e.g. the document body for general toast messages.
|
|
|
1425 |
*
|
|
|
1426 |
* @param {DOM} element Reference element to show toast message for.
|
|
|
1427 |
* @param {string} message Message to show.
|
|
|
1428 |
* @param {object} [config] Configuration.
|
|
|
1429 |
* @param {string} [config.style=h5p-toast] Style name for the tooltip.
|
|
|
1430 |
* @param {number} [config.duration=3000] Toast message length in ms.
|
|
|
1431 |
* @param {object} [config.position] Relative positioning of the toast.
|
|
|
1432 |
* @param {string} [config.position.horizontal=centered] [before|left|centered|right|after].
|
|
|
1433 |
* @param {string} [config.position.vertical=below] [above|top|centered|bottom|below].
|
|
|
1434 |
* @param {number} [config.position.offsetHorizontal=0] Extra horizontal offset.
|
|
|
1435 |
* @param {number} [config.position.offsetVertical=0] Extra vetical offset.
|
|
|
1436 |
* @param {boolean} [config.position.noOverflowLeft=false] True to prevent overflow left.
|
|
|
1437 |
* @param {boolean} [config.position.noOverflowRight=false] True to prevent overflow right.
|
|
|
1438 |
* @param {boolean} [config.position.noOverflowTop=false] True to prevent overflow top.
|
|
|
1439 |
* @param {boolean} [config.position.noOverflowBottom=false] True to prevent overflow bottom.
|
|
|
1440 |
* @param {boolean} [config.position.noOverflowX=false] True to prevent overflow left and right.
|
|
|
1441 |
* @param {boolean} [config.position.noOverflowY=false] True to prevent overflow top and bottom.
|
|
|
1442 |
* @param {object} [config.position.overflowReference=document.body] DOM reference for overflow.
|
|
|
1443 |
*/
|
|
|
1444 |
H5P.attachToastTo = function (element, message, config) {
|
|
|
1445 |
if (element === undefined || message === undefined) {
|
|
|
1446 |
return;
|
|
|
1447 |
}
|
|
|
1448 |
|
|
|
1449 |
const eventPath = function (evt) {
|
|
|
1450 |
var path = (evt.composedPath && evt.composedPath()) || evt.path;
|
|
|
1451 |
var target = evt.target;
|
|
|
1452 |
|
|
|
1453 |
if (path != null) {
|
|
|
1454 |
// Safari doesn't include Window, but it should.
|
|
|
1455 |
return (path.indexOf(window) < 0) ? path.concat(window) : path;
|
|
|
1456 |
}
|
|
|
1457 |
|
|
|
1458 |
if (target === window) {
|
|
|
1459 |
return [window];
|
|
|
1460 |
}
|
|
|
1461 |
|
|
|
1462 |
function getParents(node, memo) {
|
|
|
1463 |
memo = memo || [];
|
|
|
1464 |
var parentNode = node.parentNode;
|
|
|
1465 |
|
|
|
1466 |
if (!parentNode) {
|
|
|
1467 |
return memo;
|
|
|
1468 |
}
|
|
|
1469 |
else {
|
|
|
1470 |
return getParents(parentNode, memo.concat(parentNode));
|
|
|
1471 |
}
|
|
|
1472 |
}
|
|
|
1473 |
|
|
|
1474 |
return [target].concat(getParents(target), window);
|
|
|
1475 |
};
|
|
|
1476 |
|
|
|
1477 |
/**
|
|
|
1478 |
* Handle click while toast is showing.
|
|
|
1479 |
*/
|
|
|
1480 |
const clickHandler = function (event) {
|
|
|
1481 |
/*
|
|
|
1482 |
* A common use case will be to attach toasts to buttons that are clicked.
|
|
|
1483 |
* The click would remove the toast message instantly without this check.
|
|
|
1484 |
* Children of the clicked element are also ignored.
|
|
|
1485 |
*/
|
|
|
1486 |
var path = eventPath(event);
|
|
|
1487 |
if (path.indexOf(element) !== -1) {
|
|
|
1488 |
return;
|
|
|
1489 |
}
|
|
|
1490 |
clearTimeout(timer);
|
|
|
1491 |
removeToast();
|
|
|
1492 |
};
|
|
|
1493 |
|
|
|
1494 |
|
|
|
1495 |
|
|
|
1496 |
/**
|
|
|
1497 |
* Remove the toast message.
|
|
|
1498 |
*/
|
|
|
1499 |
const removeToast = function () {
|
|
|
1500 |
document.removeEventListener('click', clickHandler);
|
|
|
1501 |
if (toast.parentNode) {
|
|
|
1502 |
toast.parentNode.removeChild(toast);
|
|
|
1503 |
}
|
|
|
1504 |
};
|
|
|
1505 |
|
|
|
1506 |
/**
|
|
|
1507 |
* Get absolute coordinates for the toast.
|
|
|
1508 |
*
|
|
|
1509 |
* @param {DOM} element Reference element to show toast message for.
|
|
|
1510 |
* @param {DOM} toast Toast element.
|
|
|
1511 |
* @param {object} [position={}] Relative positioning of the toast message.
|
|
|
1512 |
* @param {string} [position.horizontal=centered] [before|left|centered|right|after].
|
|
|
1513 |
* @param {string} [position.vertical=below] [above|top|centered|bottom|below].
|
|
|
1514 |
* @param {number} [position.offsetHorizontal=0] Extra horizontal offset.
|
|
|
1515 |
* @param {number} [position.offsetVertical=0] Extra vetical offset.
|
|
|
1516 |
* @param {boolean} [position.noOverflowLeft=false] True to prevent overflow left.
|
|
|
1517 |
* @param {boolean} [position.noOverflowRight=false] True to prevent overflow right.
|
|
|
1518 |
* @param {boolean} [position.noOverflowTop=false] True to prevent overflow top.
|
|
|
1519 |
* @param {boolean} [position.noOverflowBottom=false] True to prevent overflow bottom.
|
|
|
1520 |
* @param {boolean} [position.noOverflowX=false] True to prevent overflow left and right.
|
|
|
1521 |
* @param {boolean} [position.noOverflowY=false] True to prevent overflow top and bottom.
|
|
|
1522 |
* @return {object}
|
|
|
1523 |
*/
|
|
|
1524 |
const getToastCoordinates = function (element, toast, position) {
|
|
|
1525 |
position = position || {};
|
|
|
1526 |
position.offsetHorizontal = position.offsetHorizontal || 0;
|
|
|
1527 |
position.offsetVertical = position.offsetVertical || 0;
|
|
|
1528 |
|
|
|
1529 |
const toastRect = toast.getBoundingClientRect();
|
|
|
1530 |
const elementRect = element.getBoundingClientRect();
|
|
|
1531 |
|
|
|
1532 |
let left = 0;
|
|
|
1533 |
let top = 0;
|
|
|
1534 |
|
|
|
1535 |
// Compute horizontal position
|
|
|
1536 |
switch (position.horizontal) {
|
|
|
1537 |
case 'before':
|
|
|
1538 |
left = elementRect.left - toastRect.width - position.offsetHorizontal;
|
|
|
1539 |
break;
|
|
|
1540 |
case 'after':
|
|
|
1541 |
left = elementRect.left + elementRect.width + position.offsetHorizontal;
|
|
|
1542 |
break;
|
|
|
1543 |
case 'left':
|
|
|
1544 |
left = elementRect.left + position.offsetHorizontal;
|
|
|
1545 |
break;
|
|
|
1546 |
case 'right':
|
|
|
1547 |
left = elementRect.left + elementRect.width - toastRect.width - position.offsetHorizontal;
|
|
|
1548 |
break;
|
|
|
1549 |
case 'centered':
|
|
|
1550 |
left = elementRect.left + elementRect.width / 2 - toastRect.width / 2 + position.offsetHorizontal;
|
|
|
1551 |
break;
|
|
|
1552 |
default:
|
|
|
1553 |
left = elementRect.left + elementRect.width / 2 - toastRect.width / 2 + position.offsetHorizontal;
|
|
|
1554 |
}
|
|
|
1555 |
|
|
|
1556 |
// Compute vertical position
|
|
|
1557 |
switch (position.vertical) {
|
|
|
1558 |
case 'above':
|
|
|
1559 |
top = elementRect.top - toastRect.height - position.offsetVertical;
|
|
|
1560 |
break;
|
|
|
1561 |
case 'below':
|
|
|
1562 |
top = elementRect.top + elementRect.height + position.offsetVertical;
|
|
|
1563 |
break;
|
|
|
1564 |
case 'top':
|
|
|
1565 |
top = elementRect.top + position.offsetVertical;
|
|
|
1566 |
break;
|
|
|
1567 |
case 'bottom':
|
|
|
1568 |
top = elementRect.top + elementRect.height - toastRect.height - position.offsetVertical;
|
|
|
1569 |
break;
|
|
|
1570 |
case 'centered':
|
|
|
1571 |
top = elementRect.top + elementRect.height / 2 - toastRect.height / 2 + position.offsetVertical;
|
|
|
1572 |
break;
|
|
|
1573 |
default:
|
|
|
1574 |
top = elementRect.top + elementRect.height + position.offsetVertical;
|
|
|
1575 |
}
|
|
|
1576 |
|
|
|
1577 |
// Prevent overflow
|
|
|
1578 |
const overflowElement = document.body;
|
|
|
1579 |
const bounds = overflowElement.getBoundingClientRect();
|
|
|
1580 |
if ((position.noOverflowLeft || position.noOverflowX) && (left < bounds.x)) {
|
|
|
1581 |
left = bounds.x;
|
|
|
1582 |
}
|
|
|
1583 |
if ((position.noOverflowRight || position.noOverflowX) && ((left + toastRect.width) > (bounds.x + bounds.width))) {
|
|
|
1584 |
left = bounds.x + bounds.width - toastRect.width;
|
|
|
1585 |
}
|
|
|
1586 |
if ((position.noOverflowTop || position.noOverflowY) && (top < bounds.y)) {
|
|
|
1587 |
top = bounds.y;
|
|
|
1588 |
}
|
|
|
1589 |
if ((position.noOverflowBottom || position.noOverflowY) && ((top + toastRect.height) > (bounds.y + bounds.height))) {
|
|
|
1590 |
left = bounds.y + bounds.height - toastRect.height;
|
|
|
1591 |
}
|
|
|
1592 |
|
|
|
1593 |
return {left: left, top: top};
|
|
|
1594 |
};
|
|
|
1595 |
|
|
|
1596 |
// Sanitization
|
|
|
1597 |
config = config || {};
|
|
|
1598 |
config.style = config.style || 'h5p-toast';
|
|
|
1599 |
config.duration = config.duration || 3000;
|
|
|
1600 |
|
|
|
1601 |
// Build toast
|
|
|
1602 |
const toast = document.createElement('div');
|
|
|
1603 |
toast.setAttribute('id', config.style);
|
|
|
1604 |
toast.classList.add('h5p-toast-disabled');
|
|
|
1605 |
toast.classList.add(config.style);
|
|
|
1606 |
|
|
|
1607 |
const msg = document.createElement('span');
|
|
|
1608 |
msg.innerHTML = message;
|
|
|
1609 |
toast.appendChild(msg);
|
|
|
1610 |
|
|
|
1611 |
document.body.appendChild(toast);
|
|
|
1612 |
|
|
|
1613 |
// The message has to be set before getting the coordinates
|
|
|
1614 |
const coordinates = getToastCoordinates(element, toast, config.position);
|
|
|
1615 |
toast.style.left = Math.round(coordinates.left) + 'px';
|
|
|
1616 |
toast.style.top = Math.round(coordinates.top) + 'px';
|
|
|
1617 |
|
|
|
1618 |
toast.classList.remove('h5p-toast-disabled');
|
|
|
1619 |
const timer = setTimeout(removeToast, config.duration);
|
|
|
1620 |
|
|
|
1621 |
// The toast can also be removed by clicking somewhere
|
|
|
1622 |
document.addEventListener('click', clickHandler);
|
|
|
1623 |
};
|
|
|
1624 |
|
|
|
1625 |
/**
|
|
|
1626 |
* Copyrights for a H5P Content Library.
|
|
|
1627 |
*
|
|
|
1628 |
* @class
|
|
|
1629 |
*/
|
|
|
1630 |
H5P.ContentCopyrights = function () {
|
|
|
1631 |
var label;
|
|
|
1632 |
var media = [];
|
|
|
1633 |
var content = [];
|
|
|
1634 |
|
|
|
1635 |
/**
|
|
|
1636 |
* Set label.
|
|
|
1637 |
*
|
|
|
1638 |
* @param {string} newLabel
|
|
|
1639 |
*/
|
|
|
1640 |
this.setLabel = function (newLabel) {
|
|
|
1641 |
label = newLabel;
|
|
|
1642 |
};
|
|
|
1643 |
|
|
|
1644 |
/**
|
|
|
1645 |
* Add sub content.
|
|
|
1646 |
*
|
|
|
1647 |
* @param {H5P.MediaCopyright} newMedia
|
|
|
1648 |
*/
|
|
|
1649 |
this.addMedia = function (newMedia) {
|
|
|
1650 |
if (newMedia !== undefined) {
|
|
|
1651 |
media.push(newMedia);
|
|
|
1652 |
}
|
|
|
1653 |
};
|
|
|
1654 |
|
|
|
1655 |
/**
|
|
|
1656 |
* Add sub content in front.
|
|
|
1657 |
*
|
|
|
1658 |
* @param {H5P.MediaCopyright} newMedia
|
|
|
1659 |
*/
|
|
|
1660 |
this.addMediaInFront = function (newMedia) {
|
|
|
1661 |
if (newMedia !== undefined) {
|
|
|
1662 |
media.unshift(newMedia);
|
|
|
1663 |
}
|
|
|
1664 |
};
|
|
|
1665 |
|
|
|
1666 |
/**
|
|
|
1667 |
* Add sub content.
|
|
|
1668 |
*
|
|
|
1669 |
* @param {H5P.ContentCopyrights} newContent
|
|
|
1670 |
*/
|
|
|
1671 |
this.addContent = function (newContent) {
|
|
|
1672 |
if (newContent !== undefined) {
|
|
|
1673 |
content.push(newContent);
|
|
|
1674 |
}
|
|
|
1675 |
};
|
|
|
1676 |
|
|
|
1677 |
/**
|
|
|
1678 |
* Print content copyright.
|
|
|
1679 |
*
|
|
|
1680 |
* @returns {string} HTML.
|
|
|
1681 |
*/
|
|
|
1682 |
this.toString = function () {
|
|
|
1683 |
var html = '';
|
|
|
1684 |
|
|
|
1685 |
// Add media rights
|
|
|
1686 |
for (var i = 0; i < media.length; i++) {
|
|
|
1687 |
html += media[i];
|
|
|
1688 |
}
|
|
|
1689 |
|
|
|
1690 |
// Add sub content rights
|
|
|
1691 |
for (i = 0; i < content.length; i++) {
|
|
|
1692 |
html += content[i];
|
|
|
1693 |
}
|
|
|
1694 |
|
|
|
1695 |
|
|
|
1696 |
if (html !== '') {
|
|
|
1697 |
// Add a label to this info
|
|
|
1698 |
if (label !== undefined) {
|
|
|
1699 |
html = '<h3>' + label + '</h3>' + html;
|
|
|
1700 |
}
|
|
|
1701 |
|
|
|
1702 |
// Add wrapper
|
|
|
1703 |
html = '<div class="h5p-content-copyrights">' + html + '</div>';
|
|
|
1704 |
}
|
|
|
1705 |
|
|
|
1706 |
return html;
|
|
|
1707 |
};
|
|
|
1708 |
};
|
|
|
1709 |
|
|
|
1710 |
/**
|
|
|
1711 |
* A ordered list of copyright fields for media.
|
|
|
1712 |
*
|
|
|
1713 |
* @class
|
|
|
1714 |
* @param {Object} copyright
|
|
|
1715 |
* Copyright information fields.
|
|
|
1716 |
* @param {Object} [labels]
|
|
|
1717 |
* Translation of labels.
|
|
|
1718 |
* @param {Array} [order]
|
|
|
1719 |
* Order of the fields.
|
|
|
1720 |
* @param {Object} [extraFields]
|
|
|
1721 |
* Add extra copyright fields.
|
|
|
1722 |
*/
|
|
|
1723 |
H5P.MediaCopyright = function (copyright, labels, order, extraFields) {
|
|
|
1724 |
var thumbnail;
|
|
|
1725 |
var list = new H5P.DefinitionList();
|
|
|
1726 |
|
|
|
1727 |
/**
|
|
|
1728 |
* Get translated label for field.
|
|
|
1729 |
*
|
|
|
1730 |
* @private
|
|
|
1731 |
* @param {string} fieldName
|
|
|
1732 |
* @returns {string}
|
|
|
1733 |
*/
|
|
|
1734 |
var getLabel = function (fieldName) {
|
|
|
1735 |
if (labels === undefined || labels[fieldName] === undefined) {
|
|
|
1736 |
return H5P.t(fieldName);
|
|
|
1737 |
}
|
|
|
1738 |
|
|
|
1739 |
return labels[fieldName];
|
|
|
1740 |
};
|
|
|
1741 |
|
|
|
1742 |
/**
|
|
|
1743 |
* Get humanized value for the license field.
|
|
|
1744 |
*
|
|
|
1745 |
* @private
|
|
|
1746 |
* @param {string} license
|
|
|
1747 |
* @param {string} [version]
|
|
|
1748 |
* @returns {string}
|
|
|
1749 |
*/
|
|
|
1750 |
var humanizeLicense = function (license, version) {
|
|
|
1751 |
var copyrightLicense = H5P.copyrightLicenses[license];
|
|
|
1752 |
|
|
|
1753 |
// Build license string
|
|
|
1754 |
var value = '';
|
|
|
1755 |
if (!(license === 'PD' && version)) {
|
|
|
1756 |
// Add license label
|
|
|
1757 |
value += (copyrightLicense.hasOwnProperty('label') ? copyrightLicense.label : copyrightLicense);
|
|
|
1758 |
}
|
|
|
1759 |
|
|
|
1760 |
// Check for version info
|
|
|
1761 |
var versionInfo;
|
|
|
1762 |
if (copyrightLicense.versions) {
|
|
|
1763 |
if (copyrightLicense.versions.default && (!version || !copyrightLicense.versions[version])) {
|
|
|
1764 |
version = copyrightLicense.versions.default;
|
|
|
1765 |
}
|
|
|
1766 |
if (version && copyrightLicense.versions[version]) {
|
|
|
1767 |
versionInfo = copyrightLicense.versions[version];
|
|
|
1768 |
}
|
|
|
1769 |
}
|
|
|
1770 |
|
|
|
1771 |
if (versionInfo) {
|
|
|
1772 |
// Add license version
|
|
|
1773 |
if (value) {
|
|
|
1774 |
value += ' ';
|
|
|
1775 |
}
|
|
|
1776 |
value += (versionInfo.hasOwnProperty('label') ? versionInfo.label : versionInfo);
|
|
|
1777 |
}
|
|
|
1778 |
|
|
|
1779 |
// Add link if specified
|
|
|
1780 |
var link;
|
|
|
1781 |
if (copyrightLicense.hasOwnProperty('link')) {
|
|
|
1782 |
link = copyrightLicense.link.replace(':version', copyrightLicense.linkVersions ? copyrightLicense.linkVersions[version] : version);
|
|
|
1783 |
}
|
|
|
1784 |
else if (versionInfo && copyrightLicense.hasOwnProperty('link')) {
|
|
|
1785 |
link = versionInfo.link;
|
|
|
1786 |
}
|
|
|
1787 |
if (link) {
|
|
|
1788 |
value = '<a href="' + link + '" target="_blank">' + value + '</a>';
|
|
|
1789 |
}
|
|
|
1790 |
|
|
|
1791 |
// Generate parenthesis
|
|
|
1792 |
var parenthesis = '';
|
|
|
1793 |
if (license !== 'PD' && license !== 'C') {
|
|
|
1794 |
parenthesis += license;
|
|
|
1795 |
}
|
|
|
1796 |
if (version && version !== 'CC0 1.0') {
|
|
|
1797 |
if (parenthesis && license !== 'GNU GPL') {
|
|
|
1798 |
parenthesis += ' ';
|
|
|
1799 |
}
|
|
|
1800 |
parenthesis += version;
|
|
|
1801 |
}
|
|
|
1802 |
if (parenthesis) {
|
|
|
1803 |
value += ' (' + parenthesis + ')';
|
|
|
1804 |
}
|
|
|
1805 |
if (license === 'C') {
|
|
|
1806 |
value += ' ©';
|
|
|
1807 |
}
|
|
|
1808 |
|
|
|
1809 |
return value;
|
|
|
1810 |
};
|
|
|
1811 |
|
|
|
1812 |
if (copyright !== undefined) {
|
|
|
1813 |
// Add the extra fields
|
|
|
1814 |
for (var field in extraFields) {
|
|
|
1815 |
if (extraFields.hasOwnProperty(field)) {
|
|
|
1816 |
copyright[field] = extraFields[field];
|
|
|
1817 |
}
|
|
|
1818 |
}
|
|
|
1819 |
|
|
|
1820 |
if (order === undefined) {
|
|
|
1821 |
// Set default order
|
|
|
1822 |
order = ['contentType', 'title', 'license', 'author', 'year', 'source', 'licenseExtras', 'changes'];
|
|
|
1823 |
}
|
|
|
1824 |
|
|
|
1825 |
for (var i = 0; i < order.length; i++) {
|
|
|
1826 |
var fieldName = order[i];
|
|
|
1827 |
if (copyright[fieldName] !== undefined && copyright[fieldName] !== '') {
|
|
|
1828 |
var humanValue = copyright[fieldName];
|
|
|
1829 |
if (fieldName === 'license') {
|
|
|
1830 |
humanValue = humanizeLicense(copyright.license, copyright.version);
|
|
|
1831 |
}
|
|
|
1832 |
if (fieldName === 'source') {
|
|
|
1833 |
humanValue = (humanValue) ? '<a href="' + humanValue + '" target="_blank">' + humanValue + '</a>' : undefined;
|
|
|
1834 |
}
|
|
|
1835 |
list.add(new H5P.Field(getLabel(fieldName), humanValue));
|
|
|
1836 |
}
|
|
|
1837 |
}
|
|
|
1838 |
}
|
|
|
1839 |
|
|
|
1840 |
/**
|
|
|
1841 |
* Set thumbnail.
|
|
|
1842 |
*
|
|
|
1843 |
* @param {H5P.Thumbnail} newThumbnail
|
|
|
1844 |
*/
|
|
|
1845 |
this.setThumbnail = function (newThumbnail) {
|
|
|
1846 |
thumbnail = newThumbnail;
|
|
|
1847 |
};
|
|
|
1848 |
|
|
|
1849 |
/**
|
|
|
1850 |
* Checks if this copyright is undisclosed.
|
|
|
1851 |
* I.e. only has the license attribute set, and it's undisclosed.
|
|
|
1852 |
*
|
|
|
1853 |
* @returns {boolean}
|
|
|
1854 |
*/
|
|
|
1855 |
this.undisclosed = function () {
|
|
|
1856 |
if (list.size() === 1) {
|
|
|
1857 |
var field = list.get(0);
|
|
|
1858 |
if (field.getLabel() === getLabel('license') && field.getValue() === humanizeLicense('U')) {
|
|
|
1859 |
return true;
|
|
|
1860 |
}
|
|
|
1861 |
}
|
|
|
1862 |
return false;
|
|
|
1863 |
};
|
|
|
1864 |
|
|
|
1865 |
/**
|
|
|
1866 |
* Print media copyright.
|
|
|
1867 |
*
|
|
|
1868 |
* @returns {string} HTML.
|
|
|
1869 |
*/
|
|
|
1870 |
this.toString = function () {
|
|
|
1871 |
var html = '';
|
|
|
1872 |
|
|
|
1873 |
if (this.undisclosed()) {
|
|
|
1874 |
return html; // No need to print a copyright with a single undisclosed license.
|
|
|
1875 |
}
|
|
|
1876 |
|
|
|
1877 |
if (thumbnail !== undefined) {
|
|
|
1878 |
html += thumbnail;
|
|
|
1879 |
}
|
|
|
1880 |
html += list;
|
|
|
1881 |
|
|
|
1882 |
if (html !== '') {
|
|
|
1883 |
html = '<div class="h5p-media-copyright">' + html + '</div>';
|
|
|
1884 |
}
|
|
|
1885 |
|
|
|
1886 |
return html;
|
|
|
1887 |
};
|
|
|
1888 |
};
|
|
|
1889 |
|
|
|
1890 |
/**
|
|
|
1891 |
* A simple and elegant class for creating thumbnails of images.
|
|
|
1892 |
*
|
|
|
1893 |
* @class
|
|
|
1894 |
* @param {string} source
|
|
|
1895 |
* @param {number} width
|
|
|
1896 |
* @param {number} height
|
|
|
1897 |
* @param {string} alt
|
|
|
1898 |
* alternative text for the thumbnail
|
|
|
1899 |
*/
|
|
|
1900 |
H5P.Thumbnail = function (source, width, height, alt) {
|
|
|
1901 |
var thumbWidth, thumbHeight = 100;
|
|
|
1902 |
if (width !== undefined) {
|
|
|
1903 |
thumbWidth = Math.round(thumbHeight * (width / height));
|
|
|
1904 |
}
|
|
|
1905 |
|
|
|
1906 |
/**
|
|
|
1907 |
* Print thumbnail.
|
|
|
1908 |
*
|
|
|
1909 |
* @returns {string} HTML.
|
|
|
1910 |
*/
|
|
|
1911 |
this.toString = function () {
|
|
|
1912 |
return '<img src="' + source + '" alt="' + (alt ? alt : '') + '" class="h5p-thumbnail" height="' + thumbHeight + '"' + (thumbWidth === undefined ? '' : ' width="' + thumbWidth + '"') + '/>';
|
|
|
1913 |
};
|
|
|
1914 |
};
|
|
|
1915 |
|
|
|
1916 |
/**
|
|
|
1917 |
* Simple data structure class for storing a single field.
|
|
|
1918 |
*
|
|
|
1919 |
* @class
|
|
|
1920 |
* @param {string} label
|
|
|
1921 |
* @param {string} value
|
|
|
1922 |
*/
|
|
|
1923 |
H5P.Field = function (label, value) {
|
|
|
1924 |
/**
|
|
|
1925 |
* Public. Get field label.
|
|
|
1926 |
*
|
|
|
1927 |
* @returns {String}
|
|
|
1928 |
*/
|
|
|
1929 |
this.getLabel = function () {
|
|
|
1930 |
return label;
|
|
|
1931 |
};
|
|
|
1932 |
|
|
|
1933 |
/**
|
|
|
1934 |
* Public. Get field value.
|
|
|
1935 |
*
|
|
|
1936 |
* @returns {String}
|
|
|
1937 |
*/
|
|
|
1938 |
this.getValue = function () {
|
|
|
1939 |
return value;
|
|
|
1940 |
};
|
|
|
1941 |
};
|
|
|
1942 |
|
|
|
1943 |
/**
|
|
|
1944 |
* Simple class for creating a definition list.
|
|
|
1945 |
*
|
|
|
1946 |
* @class
|
|
|
1947 |
*/
|
|
|
1948 |
H5P.DefinitionList = function () {
|
|
|
1949 |
var fields = [];
|
|
|
1950 |
|
|
|
1951 |
/**
|
|
|
1952 |
* Add field to list.
|
|
|
1953 |
*
|
|
|
1954 |
* @param {H5P.Field} field
|
|
|
1955 |
*/
|
|
|
1956 |
this.add = function (field) {
|
|
|
1957 |
fields.push(field);
|
|
|
1958 |
};
|
|
|
1959 |
|
|
|
1960 |
/**
|
|
|
1961 |
* Get Number of fields.
|
|
|
1962 |
*
|
|
|
1963 |
* @returns {number}
|
|
|
1964 |
*/
|
|
|
1965 |
this.size = function () {
|
|
|
1966 |
return fields.length;
|
|
|
1967 |
};
|
|
|
1968 |
|
|
|
1969 |
/**
|
|
|
1970 |
* Get field at given index.
|
|
|
1971 |
*
|
|
|
1972 |
* @param {number} index
|
|
|
1973 |
* @returns {H5P.Field}
|
|
|
1974 |
*/
|
|
|
1975 |
this.get = function (index) {
|
|
|
1976 |
return fields[index];
|
|
|
1977 |
};
|
|
|
1978 |
|
|
|
1979 |
/**
|
|
|
1980 |
* Print definition list.
|
|
|
1981 |
*
|
|
|
1982 |
* @returns {string} HTML.
|
|
|
1983 |
*/
|
|
|
1984 |
this.toString = function () {
|
|
|
1985 |
var html = '';
|
|
|
1986 |
for (var i = 0; i < fields.length; i++) {
|
|
|
1987 |
var field = fields[i];
|
|
|
1988 |
html += '<dt>' + field.getLabel() + '</dt><dd>' + field.getValue() + '</dd>';
|
|
|
1989 |
}
|
|
|
1990 |
return (html === '' ? html : '<dl class="h5p-definition-list">' + html + '</dl>');
|
|
|
1991 |
};
|
|
|
1992 |
};
|
|
|
1993 |
|
|
|
1994 |
/**
|
|
|
1995 |
* THIS FUNCTION/CLASS IS DEPRECATED AND WILL BE REMOVED.
|
|
|
1996 |
*
|
|
|
1997 |
* Helper object for keeping coordinates in the same format all over.
|
|
|
1998 |
*
|
|
|
1999 |
* @deprecated
|
|
|
2000 |
* Will be removed march 2016.
|
|
|
2001 |
* @class
|
|
|
2002 |
* @param {number} x
|
|
|
2003 |
* @param {number} y
|
|
|
2004 |
* @param {number} w
|
|
|
2005 |
* @param {number} h
|
|
|
2006 |
*/
|
|
|
2007 |
H5P.Coords = function (x, y, w, h) {
|
|
|
2008 |
if ( !(this instanceof H5P.Coords) )
|
|
|
2009 |
return new H5P.Coords(x, y, w, h);
|
|
|
2010 |
|
|
|
2011 |
/** @member {number} */
|
|
|
2012 |
this.x = 0;
|
|
|
2013 |
/** @member {number} */
|
|
|
2014 |
this.y = 0;
|
|
|
2015 |
/** @member {number} */
|
|
|
2016 |
this.w = 1;
|
|
|
2017 |
/** @member {number} */
|
|
|
2018 |
this.h = 1;
|
|
|
2019 |
|
|
|
2020 |
if (typeof(x) === 'object') {
|
|
|
2021 |
this.x = x.x;
|
|
|
2022 |
this.y = x.y;
|
|
|
2023 |
this.w = x.w;
|
|
|
2024 |
this.h = x.h;
|
|
|
2025 |
}
|
|
|
2026 |
else {
|
|
|
2027 |
if (x !== undefined) {
|
|
|
2028 |
this.x = x;
|
|
|
2029 |
}
|
|
|
2030 |
if (y !== undefined) {
|
|
|
2031 |
this.y = y;
|
|
|
2032 |
}
|
|
|
2033 |
if (w !== undefined) {
|
|
|
2034 |
this.w = w;
|
|
|
2035 |
}
|
|
|
2036 |
if (h !== undefined) {
|
|
|
2037 |
this.h = h;
|
|
|
2038 |
}
|
|
|
2039 |
}
|
|
|
2040 |
return this;
|
|
|
2041 |
};
|
|
|
2042 |
|
|
|
2043 |
/**
|
|
|
2044 |
* Parse library string into values.
|
|
|
2045 |
*
|
|
|
2046 |
* @param {string} library
|
|
|
2047 |
* library in the format "machineName majorVersion.minorVersion"
|
|
|
2048 |
* @returns {Object}
|
|
|
2049 |
* library as an object with machineName, majorVersion and minorVersion properties
|
|
|
2050 |
* return false if the library parameter is invalid
|
|
|
2051 |
*/
|
|
|
2052 |
H5P.libraryFromString = function (library) {
|
|
|
2053 |
var regExp = /(.+)\s(\d+)\.(\d+)$/g;
|
|
|
2054 |
var res = regExp.exec(library);
|
|
|
2055 |
if (res !== null) {
|
|
|
2056 |
return {
|
|
|
2057 |
'machineName': res[1],
|
|
|
2058 |
'majorVersion': parseInt(res[2]),
|
|
|
2059 |
'minorVersion': parseInt(res[3])
|
|
|
2060 |
};
|
|
|
2061 |
}
|
|
|
2062 |
else {
|
|
|
2063 |
return false;
|
|
|
2064 |
}
|
|
|
2065 |
};
|
|
|
2066 |
|
|
|
2067 |
/**
|
|
|
2068 |
* Get the path to the library
|
|
|
2069 |
*
|
|
|
2070 |
* @param {string} library
|
|
|
2071 |
* The library identifier in the format "machineName-majorVersion.minorVersion".
|
|
|
2072 |
* @returns {string}
|
|
|
2073 |
* The full path to the library.
|
|
|
2074 |
*/
|
|
|
2075 |
H5P.getLibraryPath = function (library) {
|
|
|
2076 |
if (H5PIntegration.urlLibraries !== undefined) {
|
|
|
2077 |
// This is an override for those implementations that has a different libraries URL, e.g. Moodle
|
|
|
2078 |
return H5PIntegration.urlLibraries + '/' + library;
|
|
|
2079 |
}
|
|
|
2080 |
else {
|
|
|
2081 |
return H5PIntegration.url + '/libraries/' + library;
|
|
|
2082 |
}
|
|
|
2083 |
};
|
|
|
2084 |
|
|
|
2085 |
/**
|
|
|
2086 |
* Recursivly clone the given object.
|
|
|
2087 |
*
|
|
|
2088 |
* @param {Object|Array} object
|
|
|
2089 |
* Object to clone.
|
|
|
2090 |
* @param {boolean} [recursive]
|
|
|
2091 |
* @returns {Object|Array}
|
|
|
2092 |
* A clone of object.
|
|
|
2093 |
*/
|
|
|
2094 |
H5P.cloneObject = function (object, recursive) {
|
|
|
2095 |
// TODO: Consider if this needs to be in core. Doesn't $.extend do the same?
|
|
|
2096 |
var clone = object instanceof Array ? [] : {};
|
|
|
2097 |
|
|
|
2098 |
for (var i in object) {
|
|
|
2099 |
if (object.hasOwnProperty(i)) {
|
|
|
2100 |
if (recursive !== undefined && recursive && typeof object[i] === 'object') {
|
|
|
2101 |
clone[i] = H5P.cloneObject(object[i], recursive);
|
|
|
2102 |
}
|
|
|
2103 |
else {
|
|
|
2104 |
clone[i] = object[i];
|
|
|
2105 |
}
|
|
|
2106 |
}
|
|
|
2107 |
}
|
|
|
2108 |
|
|
|
2109 |
return clone;
|
|
|
2110 |
};
|
|
|
2111 |
|
|
|
2112 |
/**
|
|
|
2113 |
* Remove all empty spaces before and after the value.
|
|
|
2114 |
*
|
|
|
2115 |
* @param {string} value
|
|
|
2116 |
* @returns {string}
|
|
|
2117 |
*/
|
|
|
2118 |
H5P.trim = function (value) {
|
|
|
2119 |
return value.replace(/^\s+|\s+$/g, '');
|
|
|
2120 |
|
|
|
2121 |
// TODO: Only include this or String.trim(). What is best?
|
|
|
2122 |
// I'm leaning towards implementing the missing ones: http://kangax.github.io/compat-table/es5/
|
|
|
2123 |
// So should we make this function deprecated?
|
|
|
2124 |
};
|
|
|
2125 |
|
|
|
2126 |
/**
|
|
|
2127 |
* Recursive function that detects deep empty structures.
|
|
|
2128 |
*
|
|
|
2129 |
* @param {*} value
|
|
|
2130 |
* @returns {bool}
|
|
|
2131 |
*/
|
|
|
2132 |
H5P.isEmpty = value => {
|
|
|
2133 |
if (!value && value !== 0 && value !== false) {
|
|
|
2134 |
return true; // undefined, null, NaN and empty strings.
|
|
|
2135 |
}
|
|
|
2136 |
else if (Array.isArray(value)) {
|
|
|
2137 |
for (let i = 0; i < value.length; i++) {
|
|
|
2138 |
if (!H5P.isEmpty(value[i])) {
|
|
|
2139 |
return false; // Array contains a non-empty value
|
|
|
2140 |
}
|
|
|
2141 |
}
|
|
|
2142 |
return true; // Empty array
|
|
|
2143 |
}
|
|
|
2144 |
else if (typeof value === 'object') {
|
|
|
2145 |
for (let prop in value) {
|
|
|
2146 |
if (value.hasOwnProperty(prop) && !H5P.isEmpty(value[prop])) {
|
|
|
2147 |
return false; // Object contains a non-empty value
|
|
|
2148 |
}
|
|
|
2149 |
}
|
|
|
2150 |
return true; // Empty object
|
|
|
2151 |
}
|
|
|
2152 |
return false;
|
|
|
2153 |
};
|
|
|
2154 |
|
|
|
2155 |
/**
|
|
|
2156 |
* Check if JavaScript path/key is loaded.
|
|
|
2157 |
*
|
|
|
2158 |
* @param {string} path
|
|
|
2159 |
* @returns {boolean}
|
|
|
2160 |
*/
|
|
|
2161 |
H5P.jsLoaded = function (path) {
|
|
|
2162 |
H5PIntegration.loadedJs = H5PIntegration.loadedJs || [];
|
|
|
2163 |
return H5P.jQuery.inArray(path, H5PIntegration.loadedJs) !== -1;
|
|
|
2164 |
};
|
|
|
2165 |
|
|
|
2166 |
/**
|
|
|
2167 |
* Check if styles path/key is loaded.
|
|
|
2168 |
*
|
|
|
2169 |
* @param {string} path
|
|
|
2170 |
* @returns {boolean}
|
|
|
2171 |
*/
|
|
|
2172 |
H5P.cssLoaded = function (path) {
|
|
|
2173 |
H5PIntegration.loadedCss = H5PIntegration.loadedCss || [];
|
|
|
2174 |
return H5P.jQuery.inArray(path, H5PIntegration.loadedCss) !== -1;
|
|
|
2175 |
};
|
|
|
2176 |
|
|
|
2177 |
/**
|
|
|
2178 |
* Shuffle an array in place.
|
|
|
2179 |
*
|
|
|
2180 |
* @param {Array} array
|
|
|
2181 |
* Array to shuffle
|
|
|
2182 |
* @returns {Array}
|
|
|
2183 |
* The passed array is returned for chaining.
|
|
|
2184 |
*/
|
|
|
2185 |
H5P.shuffleArray = function (array) {
|
|
|
2186 |
// TODO: Consider if this should be a part of core. I'm guessing very few libraries are going to use it.
|
|
|
2187 |
if (!(array instanceof Array)) {
|
|
|
2188 |
return;
|
|
|
2189 |
}
|
|
|
2190 |
|
|
|
2191 |
var i = array.length, j, tempi, tempj;
|
|
|
2192 |
if ( i === 0 ) return false;
|
|
|
2193 |
while ( --i ) {
|
|
|
2194 |
j = Math.floor( Math.random() * ( i + 1 ) );
|
|
|
2195 |
tempi = array[i];
|
|
|
2196 |
tempj = array[j];
|
|
|
2197 |
array[i] = tempj;
|
|
|
2198 |
array[j] = tempi;
|
|
|
2199 |
}
|
|
|
2200 |
return array;
|
|
|
2201 |
};
|
|
|
2202 |
|
|
|
2203 |
/**
|
|
|
2204 |
* Post finished results for user.
|
|
|
2205 |
*
|
|
|
2206 |
* @deprecated
|
|
|
2207 |
* Do not use this function directly, trigger the finish event instead.
|
|
|
2208 |
* Will be removed march 2016
|
|
|
2209 |
* @param {number} contentId
|
|
|
2210 |
* Identifies the content
|
|
|
2211 |
* @param {number} score
|
|
|
2212 |
* Achieved score/points
|
|
|
2213 |
* @param {number} maxScore
|
|
|
2214 |
* The maximum score/points that can be achieved
|
|
|
2215 |
* @param {number} [time]
|
|
|
2216 |
* Reported time consumption/usage
|
|
|
2217 |
*/
|
|
|
2218 |
H5P.setFinished = function (contentId, score, maxScore, time) {
|
|
|
2219 |
var validScore = typeof score === 'number' || score instanceof Number;
|
|
|
2220 |
if (validScore && H5PIntegration.postUserStatistics === true) {
|
|
|
2221 |
/**
|
|
|
2222 |
* Return unix timestamp for the given JS Date.
|
|
|
2223 |
*
|
|
|
2224 |
* @private
|
|
|
2225 |
* @param {Date} date
|
|
|
2226 |
* @returns {Number}
|
|
|
2227 |
*/
|
|
|
2228 |
var toUnix = function (date) {
|
|
|
2229 |
return Math.round(date.getTime() / 1000);
|
|
|
2230 |
};
|
|
|
2231 |
|
|
|
2232 |
// Post the results
|
|
|
2233 |
const data = {
|
|
|
2234 |
contentId: contentId,
|
|
|
2235 |
score: score,
|
|
|
2236 |
maxScore: maxScore,
|
|
|
2237 |
opened: toUnix(H5P.opened[contentId]),
|
|
|
2238 |
finished: toUnix(new Date()),
|
|
|
2239 |
time: time
|
|
|
2240 |
};
|
|
|
2241 |
H5P.jQuery.post(H5PIntegration.ajax.setFinished, data)
|
|
|
2242 |
.fail(function () {
|
|
|
2243 |
H5P.offlineRequestQueue.add(H5PIntegration.ajax.setFinished, data);
|
|
|
2244 |
});
|
|
|
2245 |
}
|
|
|
2246 |
};
|
|
|
2247 |
|
|
|
2248 |
// Add indexOf to browsers that lack them. (IEs)
|
|
|
2249 |
if (!Array.prototype.indexOf) {
|
|
|
2250 |
Array.prototype.indexOf = function (needle) {
|
|
|
2251 |
for (var i = 0; i < this.length; i++) {
|
|
|
2252 |
if (this[i] === needle) {
|
|
|
2253 |
return i;
|
|
|
2254 |
}
|
|
|
2255 |
}
|
|
|
2256 |
return -1;
|
|
|
2257 |
};
|
|
|
2258 |
}
|
|
|
2259 |
|
|
|
2260 |
// Need to define trim() since this is not available on older IEs,
|
|
|
2261 |
// and trim is used in several libs
|
|
|
2262 |
if (String.prototype.trim === undefined) {
|
|
|
2263 |
String.prototype.trim = function () {
|
|
|
2264 |
return H5P.trim(this);
|
|
|
2265 |
};
|
|
|
2266 |
}
|
|
|
2267 |
|
|
|
2268 |
/**
|
|
|
2269 |
* Trigger an event on an instance
|
|
|
2270 |
*
|
|
|
2271 |
* Helper function that triggers an event if the instance supports event handling
|
|
|
2272 |
*
|
|
|
2273 |
* @param {Object} instance
|
|
|
2274 |
* Instance of H5P content
|
|
|
2275 |
* @param {string} eventType
|
|
|
2276 |
* Type of event to trigger
|
|
|
2277 |
* @param {*} data
|
|
|
2278 |
* @param {Object} extras
|
|
|
2279 |
*/
|
|
|
2280 |
H5P.trigger = function (instance, eventType, data, extras) {
|
|
|
2281 |
// Try new event system first
|
|
|
2282 |
if (instance.trigger !== undefined) {
|
|
|
2283 |
instance.trigger(eventType, data, extras);
|
|
|
2284 |
}
|
|
|
2285 |
// Try deprecated event system
|
|
|
2286 |
else if (instance.$ !== undefined && instance.$.trigger !== undefined) {
|
|
|
2287 |
instance.$.trigger(eventType);
|
|
|
2288 |
}
|
|
|
2289 |
};
|
|
|
2290 |
|
|
|
2291 |
/**
|
|
|
2292 |
* Register an event handler
|
|
|
2293 |
*
|
|
|
2294 |
* Helper function that registers an event handler for an event type if
|
|
|
2295 |
* the instance supports event handling
|
|
|
2296 |
*
|
|
|
2297 |
* @param {Object} instance
|
|
|
2298 |
* Instance of H5P content
|
|
|
2299 |
* @param {string} eventType
|
|
|
2300 |
* Type of event to listen for
|
|
|
2301 |
* @param {H5P.EventCallback} handler
|
|
|
2302 |
* Callback that gets triggered for events of the specified type
|
|
|
2303 |
*/
|
|
|
2304 |
H5P.on = function (instance, eventType, handler) {
|
|
|
2305 |
// Try new event system first
|
|
|
2306 |
if (instance.on !== undefined) {
|
|
|
2307 |
instance.on(eventType, handler);
|
|
|
2308 |
}
|
|
|
2309 |
// Try deprecated event system
|
|
|
2310 |
else if (instance.$ !== undefined && instance.$.on !== undefined) {
|
|
|
2311 |
instance.$.on(eventType, handler);
|
|
|
2312 |
}
|
|
|
2313 |
};
|
|
|
2314 |
|
|
|
2315 |
/**
|
|
|
2316 |
* Generate random UUID
|
|
|
2317 |
*
|
|
|
2318 |
* @returns {string} UUID
|
|
|
2319 |
*/
|
|
|
2320 |
H5P.createUUID = function () {
|
|
|
2321 |
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (char) {
|
|
|
2322 |
var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8);
|
|
|
2323 |
return newChar.toString(16);
|
|
|
2324 |
});
|
|
|
2325 |
};
|
|
|
2326 |
|
|
|
2327 |
/**
|
|
|
2328 |
* Create title
|
|
|
2329 |
*
|
|
|
2330 |
* @param {string} rawTitle
|
|
|
2331 |
* @param {number} maxLength
|
|
|
2332 |
* @returns {string}
|
|
|
2333 |
*/
|
|
|
2334 |
H5P.createTitle = function (rawTitle, maxLength) {
|
|
|
2335 |
if (!rawTitle) {
|
|
|
2336 |
return '';
|
|
|
2337 |
}
|
|
|
2338 |
if (maxLength === undefined) {
|
|
|
2339 |
maxLength = 60;
|
|
|
2340 |
}
|
|
|
2341 |
var title = H5P.jQuery('<div></div>')
|
|
|
2342 |
.text(
|
|
|
2343 |
// Strip tags
|
|
|
2344 |
rawTitle.replace(/(<([^>]+)>)/ig,"")
|
|
|
2345 |
// Escape
|
|
|
2346 |
).text();
|
|
|
2347 |
if (title.length > maxLength) {
|
|
|
2348 |
title = title.substr(0, maxLength - 3) + '...';
|
|
|
2349 |
}
|
|
|
2350 |
return title;
|
|
|
2351 |
};
|
|
|
2352 |
|
|
|
2353 |
// Wrap in privates
|
|
|
2354 |
(function ($) {
|
|
|
2355 |
|
|
|
2356 |
/**
|
|
|
2357 |
* Creates ajax requests for inserting, updateing and deleteing
|
|
|
2358 |
* content user data.
|
|
|
2359 |
*
|
|
|
2360 |
* @private
|
|
|
2361 |
* @param {number} contentId What content to store the data for.
|
|
|
2362 |
* @param {string} dataType Identifies the set of data for this content.
|
|
|
2363 |
* @param {string} subContentId Identifies sub content
|
|
|
2364 |
* @param {function} [done] Callback when ajax is done.
|
|
|
2365 |
* @param {object} [data] To be stored for future use.
|
|
|
2366 |
* @param {boolean} [preload=false] Data is loaded when content is loaded.
|
|
|
2367 |
* @param {boolean} [invalidate=false] Data is invalidated when content changes.
|
|
|
2368 |
* @param {boolean} [async=true]
|
|
|
2369 |
*/
|
|
|
2370 |
function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) {
|
|
|
2371 |
if (H5PIntegration.user === undefined) {
|
|
|
2372 |
// Not logged in, no use in saving.
|
|
|
2373 |
done('Not signed in.');
|
|
|
2374 |
return;
|
|
|
2375 |
}
|
|
|
2376 |
|
|
|
2377 |
var options = {
|
|
|
2378 |
url: H5PIntegration.ajax.contentUserData.replace(':contentId', contentId).replace(':dataType', dataType).replace(':subContentId', subContentId ? subContentId : 0),
|
|
|
2379 |
dataType: 'json',
|
|
|
2380 |
async: async === undefined ? true : async
|
|
|
2381 |
};
|
|
|
2382 |
if (data !== undefined) {
|
|
|
2383 |
options.type = 'POST';
|
|
|
2384 |
options.data = {
|
|
|
2385 |
data: (data === null ? 0 : data),
|
|
|
2386 |
preload: (preload ? 1 : 0),
|
|
|
2387 |
invalidate: (invalidate ? 1 : 0)
|
|
|
2388 |
};
|
|
|
2389 |
}
|
|
|
2390 |
else {
|
|
|
2391 |
options.type = 'GET';
|
|
|
2392 |
}
|
|
|
2393 |
if (done !== undefined) {
|
|
|
2394 |
options.error = function (xhr, error) {
|
|
|
2395 |
done(error);
|
|
|
2396 |
};
|
|
|
2397 |
options.success = function (response) {
|
|
|
2398 |
if (!response.success) {
|
|
|
2399 |
done(response.message);
|
|
|
2400 |
return;
|
|
|
2401 |
}
|
|
|
2402 |
|
|
|
2403 |
if (response.data === false || response.data === undefined) {
|
|
|
2404 |
done();
|
|
|
2405 |
return;
|
|
|
2406 |
}
|
|
|
2407 |
|
|
|
2408 |
done(undefined, response.data);
|
|
|
2409 |
};
|
|
|
2410 |
}
|
|
|
2411 |
|
|
|
2412 |
$.ajax(options);
|
|
|
2413 |
}
|
|
|
2414 |
|
|
|
2415 |
/**
|
|
|
2416 |
* Get user data for given content.
|
|
|
2417 |
*
|
|
|
2418 |
* @param {number} contentId
|
|
|
2419 |
* What content to get data for.
|
|
|
2420 |
* @param {string} dataId
|
|
|
2421 |
* Identifies the set of data for this content.
|
|
|
2422 |
* @param {function} done
|
|
|
2423 |
* Callback with error and data parameters.
|
|
|
2424 |
* @param {string} [subContentId]
|
|
|
2425 |
* Identifies which data belongs to sub content.
|
|
|
2426 |
*/
|
|
|
2427 |
H5P.getUserData = function (contentId, dataId, done, subContentId) {
|
|
|
2428 |
if (!subContentId) {
|
|
|
2429 |
subContentId = 0; // Default
|
|
|
2430 |
}
|
|
|
2431 |
|
|
|
2432 |
H5PIntegration.contents = H5PIntegration.contents || {};
|
|
|
2433 |
var content = H5PIntegration.contents['cid-' + contentId] || {};
|
|
|
2434 |
var preloadedData = content.contentUserData;
|
|
|
2435 |
if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId] !== undefined) {
|
|
|
2436 |
if (preloadedData[subContentId][dataId] === 'RESET') {
|
|
|
2437 |
done(undefined, null);
|
|
|
2438 |
return;
|
|
|
2439 |
}
|
|
|
2440 |
try {
|
|
|
2441 |
done(undefined, JSON.parse(preloadedData[subContentId][dataId]));
|
|
|
2442 |
}
|
|
|
2443 |
catch (err) {
|
|
|
2444 |
done(err);
|
|
|
2445 |
}
|
|
|
2446 |
}
|
|
|
2447 |
else {
|
|
|
2448 |
contentUserDataAjax(contentId, dataId, subContentId, function (err, data) {
|
|
|
2449 |
if (err || data === undefined) {
|
|
|
2450 |
done(err, data);
|
|
|
2451 |
return; // Error or no data
|
|
|
2452 |
}
|
|
|
2453 |
|
|
|
2454 |
// Cache in preloaded
|
|
|
2455 |
if (content.contentUserData === undefined) {
|
|
|
2456 |
content.contentUserData = preloadedData = {};
|
|
|
2457 |
}
|
|
|
2458 |
if (preloadedData[subContentId] === undefined) {
|
|
|
2459 |
preloadedData[subContentId] = {};
|
|
|
2460 |
}
|
|
|
2461 |
preloadedData[subContentId][dataId] = data;
|
|
|
2462 |
|
|
|
2463 |
// Done. Try to decode JSON
|
|
|
2464 |
try {
|
|
|
2465 |
done(undefined, JSON.parse(data));
|
|
|
2466 |
}
|
|
|
2467 |
catch (e) {
|
|
|
2468 |
done(e);
|
|
|
2469 |
}
|
|
|
2470 |
});
|
|
|
2471 |
}
|
|
|
2472 |
};
|
|
|
2473 |
|
|
|
2474 |
/**
|
|
|
2475 |
* Async error handling.
|
|
|
2476 |
*
|
|
|
2477 |
* @callback H5P.ErrorCallback
|
|
|
2478 |
* @param {*} error
|
|
|
2479 |
*/
|
|
|
2480 |
|
|
|
2481 |
/**
|
|
|
2482 |
* Set user data for given content.
|
|
|
2483 |
*
|
|
|
2484 |
* @param {number} contentId
|
|
|
2485 |
* What content to get data for.
|
|
|
2486 |
* @param {string} dataId
|
|
|
2487 |
* Identifies the set of data for this content.
|
|
|
2488 |
* @param {Object} data
|
|
|
2489 |
* The data that is to be stored.
|
|
|
2490 |
* @param {Object} [extras]
|
|
|
2491 |
* Extra properties
|
|
|
2492 |
* @param {string} [extras.subContentId]
|
|
|
2493 |
* Identifies which data belongs to sub content.
|
|
|
2494 |
* @param {boolean} [extras.preloaded=true]
|
|
|
2495 |
* If the data should be loaded when content is loaded.
|
|
|
2496 |
* @param {boolean} [extras.deleteOnChange=false]
|
|
|
2497 |
* If the data should be invalidated when the content changes.
|
|
|
2498 |
* @param {H5P.ErrorCallback} [extras.errorCallback]
|
|
|
2499 |
* Callback with error as parameters.
|
|
|
2500 |
* @param {boolean} [extras.async=true]
|
|
|
2501 |
*/
|
|
|
2502 |
H5P.setUserData = function (contentId, dataId, data, extras) {
|
|
|
2503 |
var options = H5P.jQuery.extend(true, {}, {
|
|
|
2504 |
subContentId: 0,
|
|
|
2505 |
preloaded: true,
|
|
|
2506 |
deleteOnChange: false,
|
|
|
2507 |
async: true
|
|
|
2508 |
}, extras);
|
|
|
2509 |
|
|
|
2510 |
try {
|
|
|
2511 |
data = JSON.stringify(data);
|
|
|
2512 |
}
|
|
|
2513 |
catch (err) {
|
|
|
2514 |
if (options.errorCallback) {
|
|
|
2515 |
options.errorCallback(err);
|
|
|
2516 |
}
|
|
|
2517 |
return; // Failed to serialize.
|
|
|
2518 |
}
|
|
|
2519 |
|
|
|
2520 |
var content = H5PIntegration.contents['cid-' + contentId];
|
|
|
2521 |
if (content === undefined) {
|
|
|
2522 |
content = H5PIntegration.contents['cid-' + contentId] = {};
|
|
|
2523 |
}
|
|
|
2524 |
if (!content.contentUserData) {
|
|
|
2525 |
content.contentUserData = {};
|
|
|
2526 |
}
|
|
|
2527 |
var preloadedData = content.contentUserData;
|
|
|
2528 |
if (preloadedData[options.subContentId] === undefined) {
|
|
|
2529 |
preloadedData[options.subContentId] = {};
|
|
|
2530 |
}
|
|
|
2531 |
if (data === preloadedData[options.subContentId][dataId]) {
|
|
|
2532 |
return; // No need to save this twice.
|
|
|
2533 |
}
|
|
|
2534 |
|
|
|
2535 |
preloadedData[options.subContentId][dataId] = data;
|
|
|
2536 |
contentUserDataAjax(contentId, dataId, options.subContentId, function (error) {
|
|
|
2537 |
if (options.errorCallback && error) {
|
|
|
2538 |
options.errorCallback(error);
|
|
|
2539 |
}
|
|
|
2540 |
}, data, options.preloaded, options.deleteOnChange, options.async);
|
|
|
2541 |
};
|
|
|
2542 |
|
|
|
2543 |
/**
|
|
|
2544 |
* Delete user data for given content.
|
|
|
2545 |
*
|
|
|
2546 |
* @param {number} contentId
|
|
|
2547 |
* What content to remove data for.
|
|
|
2548 |
* @param {string} dataId
|
|
|
2549 |
* Identifies the set of data for this content.
|
|
|
2550 |
* @param {string} [subContentId]
|
|
|
2551 |
* Identifies which data belongs to sub content.
|
|
|
2552 |
*/
|
|
|
2553 |
H5P.deleteUserData = function (contentId, dataId, subContentId) {
|
|
|
2554 |
if (!subContentId) {
|
|
|
2555 |
subContentId = 0; // Default
|
|
|
2556 |
}
|
|
|
2557 |
|
|
|
2558 |
// Remove from preloaded/cache
|
|
|
2559 |
var preloadedData = H5PIntegration.contents['cid-' + contentId].contentUserData;
|
|
|
2560 |
if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) {
|
|
|
2561 |
delete preloadedData[subContentId][dataId];
|
|
|
2562 |
}
|
|
|
2563 |
|
|
|
2564 |
contentUserDataAjax(contentId, dataId, subContentId, undefined, null);
|
|
|
2565 |
};
|
|
|
2566 |
|
|
|
2567 |
/**
|
|
|
2568 |
* Function for getting content for a certain ID
|
|
|
2569 |
*
|
|
|
2570 |
* @param {number} contentId
|
|
|
2571 |
* @return {Object}
|
|
|
2572 |
*/
|
|
|
2573 |
H5P.getContentForInstance = function (contentId) {
|
|
|
2574 |
var key = 'cid-' + contentId;
|
|
|
2575 |
var exists = H5PIntegration && H5PIntegration.contents &&
|
|
|
2576 |
H5PIntegration.contents[key];
|
|
|
2577 |
|
|
|
2578 |
return exists ? H5PIntegration.contents[key] : undefined;
|
|
|
2579 |
};
|
|
|
2580 |
|
|
|
2581 |
/**
|
|
|
2582 |
* Prepares the content parameters for storing in the clipboard.
|
|
|
2583 |
*
|
|
|
2584 |
* @class
|
|
|
2585 |
* @param {Object} parameters The parameters for the content to store
|
|
|
2586 |
* @param {string} [genericProperty] If only part of the parameters are generic, which part
|
|
|
2587 |
* @param {string} [specificKey] If the parameters are specific, what content type does it fit
|
|
|
2588 |
* @returns {Object} Ready for the clipboard
|
|
|
2589 |
*/
|
|
|
2590 |
H5P.ClipboardItem = function (parameters, genericProperty, specificKey) {
|
|
|
2591 |
var self = this;
|
|
|
2592 |
|
|
|
2593 |
/**
|
|
|
2594 |
* Set relative dimensions when params contains a file with a width and a height.
|
|
|
2595 |
* Very useful to be compatible with wysiwyg editors.
|
|
|
2596 |
*
|
|
|
2597 |
* @private
|
|
|
2598 |
*/
|
|
|
2599 |
var setDimensionsFromFile = function () {
|
|
|
2600 |
if (!self.generic) {
|
|
|
2601 |
return;
|
|
|
2602 |
}
|
|
|
2603 |
var params = self.specific[self.generic];
|
|
|
2604 |
if (!params.params.file || !params.params.file.width || !params.params.file.height) {
|
|
|
2605 |
return;
|
|
|
2606 |
}
|
|
|
2607 |
|
|
|
2608 |
self.width = 20; // %
|
|
|
2609 |
self.height = (params.params.file.height / params.params.file.width) * self.width;
|
|
|
2610 |
};
|
|
|
2611 |
|
|
|
2612 |
if (!genericProperty) {
|
|
|
2613 |
genericProperty = 'action';
|
|
|
2614 |
parameters = {
|
|
|
2615 |
action: parameters
|
|
|
2616 |
};
|
|
|
2617 |
}
|
|
|
2618 |
|
|
|
2619 |
self.specific = parameters;
|
|
|
2620 |
|
|
|
2621 |
if (genericProperty && parameters[genericProperty]) {
|
|
|
2622 |
self.generic = genericProperty;
|
|
|
2623 |
}
|
|
|
2624 |
if (specificKey) {
|
|
|
2625 |
self.from = specificKey;
|
|
|
2626 |
}
|
|
|
2627 |
|
|
|
2628 |
if (window.H5PEditor && H5PEditor.contentId) {
|
|
|
2629 |
self.contentId = H5PEditor.contentId;
|
|
|
2630 |
}
|
|
|
2631 |
|
|
|
2632 |
if (!self.specific.width && !self.specific.height) {
|
|
|
2633 |
setDimensionsFromFile();
|
|
|
2634 |
}
|
|
|
2635 |
};
|
|
|
2636 |
|
|
|
2637 |
/**
|
|
|
2638 |
* Store item in the H5P Clipboard.
|
|
|
2639 |
*
|
|
|
2640 |
* @param {H5P.ClipboardItem|*} clipboardItem
|
|
|
2641 |
*/
|
|
|
2642 |
H5P.clipboardify = function (clipboardItem) {
|
|
|
2643 |
if (!(clipboardItem instanceof H5P.ClipboardItem)) {
|
|
|
2644 |
clipboardItem = new H5P.ClipboardItem(clipboardItem);
|
|
|
2645 |
}
|
|
|
2646 |
H5P.setClipboard(clipboardItem);
|
|
|
2647 |
};
|
|
|
2648 |
|
|
|
2649 |
/**
|
|
|
2650 |
* Retrieve parsed clipboard data.
|
|
|
2651 |
*
|
|
|
2652 |
* @return {Object}
|
|
|
2653 |
*/
|
|
|
2654 |
H5P.getClipboard = function () {
|
|
|
2655 |
return parseClipboard();
|
|
|
2656 |
};
|
|
|
2657 |
|
|
|
2658 |
/**
|
|
|
2659 |
* Set item in the H5P Clipboard.
|
|
|
2660 |
*
|
|
|
2661 |
* @param {H5P.ClipboardItem|object} clipboardItem - Data to be set.
|
|
|
2662 |
*/
|
|
|
2663 |
H5P.setClipboard = function (clipboardItem) {
|
|
|
2664 |
localStorage.setItem('h5pClipboard', JSON.stringify(clipboardItem));
|
|
|
2665 |
|
|
|
2666 |
// Trigger an event so all 'Paste' buttons may be enabled.
|
|
|
2667 |
H5P.externalDispatcher.trigger('datainclipboard', {reset: false});
|
|
|
2668 |
};
|
|
|
2669 |
|
|
|
2670 |
/**
|
|
|
2671 |
* Get config for a library
|
|
|
2672 |
*
|
|
|
2673 |
* @param string machineName
|
|
|
2674 |
* @return Object
|
|
|
2675 |
*/
|
|
|
2676 |
H5P.getLibraryConfig = function (machineName) {
|
|
|
2677 |
var hasConfig = H5PIntegration.libraryConfig && H5PIntegration.libraryConfig[machineName];
|
|
|
2678 |
return hasConfig ? H5PIntegration.libraryConfig[machineName] : {};
|
|
|
2679 |
};
|
|
|
2680 |
|
|
|
2681 |
/**
|
|
|
2682 |
* Get item from the H5P Clipboard.
|
|
|
2683 |
*
|
|
|
2684 |
* @private
|
|
|
2685 |
* @return {Object}
|
|
|
2686 |
*/
|
|
|
2687 |
var parseClipboard = function () {
|
|
|
2688 |
var clipboardData = localStorage.getItem('h5pClipboard');
|
|
|
2689 |
if (!clipboardData) {
|
|
|
2690 |
return;
|
|
|
2691 |
}
|
|
|
2692 |
|
|
|
2693 |
// Try to parse clipboard dat
|
|
|
2694 |
try {
|
|
|
2695 |
clipboardData = JSON.parse(clipboardData);
|
|
|
2696 |
}
|
|
|
2697 |
catch (err) {
|
|
|
2698 |
console.error('Unable to parse JSON from clipboard.', err);
|
|
|
2699 |
return;
|
|
|
2700 |
}
|
|
|
2701 |
|
|
|
2702 |
// Update file URLs and reset content Ids
|
|
|
2703 |
recursiveUpdate(clipboardData.specific, function (path) {
|
|
|
2704 |
var isTmpFile = (path.substr(-4, 4) === '#tmp');
|
|
|
2705 |
if (!isTmpFile && clipboardData.contentId && !path.match(/^https?:\/\//i)) {
|
|
|
2706 |
// Comes from existing content
|
|
|
2707 |
|
|
|
2708 |
let prefix;
|
|
|
2709 |
if (H5PEditor.contentId) {
|
|
|
2710 |
// .. to existing content
|
|
|
2711 |
prefix = '../' + clipboardData.contentId + '/';
|
|
|
2712 |
}
|
|
|
2713 |
else {
|
|
|
2714 |
// .. to new content
|
|
|
2715 |
prefix = (H5PEditor.contentRelUrl ? H5PEditor.contentRelUrl : '../content/') + clipboardData.contentId + '/';
|
|
|
2716 |
}
|
|
|
2717 |
return path.substr(0, prefix.length) === prefix ? path : prefix + path;
|
|
|
2718 |
}
|
|
|
2719 |
|
|
|
2720 |
return path; // Will automatically be looked for in tmp folder
|
|
|
2721 |
});
|
|
|
2722 |
|
|
|
2723 |
|
|
|
2724 |
if (clipboardData.generic) {
|
|
|
2725 |
// Use reference instead of key
|
|
|
2726 |
clipboardData.generic = clipboardData.specific[clipboardData.generic];
|
|
|
2727 |
}
|
|
|
2728 |
|
|
|
2729 |
return clipboardData;
|
|
|
2730 |
};
|
|
|
2731 |
|
|
|
2732 |
/**
|
|
|
2733 |
* Update file URLs and reset content IDs.
|
|
|
2734 |
* Useful when copying content.
|
|
|
2735 |
*
|
|
|
2736 |
* @private
|
|
|
2737 |
* @param {object} params Reference
|
|
|
2738 |
* @param {function} handler Modifies the path to work when pasted
|
|
|
2739 |
*/
|
|
|
2740 |
var recursiveUpdate = function (params, handler) {
|
|
|
2741 |
for (var prop in params) {
|
|
|
2742 |
if (params.hasOwnProperty(prop) && params[prop] instanceof Object) {
|
|
|
2743 |
var obj = params[prop];
|
|
|
2744 |
if (obj.path !== undefined && obj.mime !== undefined) {
|
|
|
2745 |
obj.path = handler(obj.path);
|
|
|
2746 |
}
|
|
|
2747 |
else {
|
|
|
2748 |
if (obj.library !== undefined && obj.subContentId !== undefined) {
|
|
|
2749 |
// Avoid multiple content with same ID
|
|
|
2750 |
delete obj.subContentId;
|
|
|
2751 |
}
|
|
|
2752 |
recursiveUpdate(obj, handler);
|
|
|
2753 |
}
|
|
|
2754 |
}
|
|
|
2755 |
}
|
|
|
2756 |
};
|
|
|
2757 |
|
|
|
2758 |
// Init H5P when page is fully loadded
|
|
|
2759 |
$(document).ready(function () {
|
|
|
2760 |
|
|
|
2761 |
window.addEventListener('storage', function (event) {
|
|
|
2762 |
// Pick up clipboard changes from other tabs
|
|
|
2763 |
if (event.key === 'h5pClipboard') {
|
|
|
2764 |
// Trigger an event so all 'Paste' buttons may be enabled.
|
|
|
2765 |
H5P.externalDispatcher.trigger('datainclipboard', {reset: event.newValue === null});
|
|
|
2766 |
}
|
|
|
2767 |
});
|
|
|
2768 |
|
|
|
2769 |
var ccVersions = {
|
|
|
2770 |
'default': '4.0',
|
|
|
2771 |
'4.0': H5P.t('licenseCC40'),
|
|
|
2772 |
'3.0': H5P.t('licenseCC30'),
|
|
|
2773 |
'2.5': H5P.t('licenseCC25'),
|
|
|
2774 |
'2.0': H5P.t('licenseCC20'),
|
|
|
2775 |
'1.0': H5P.t('licenseCC10'),
|
|
|
2776 |
};
|
|
|
2777 |
|
|
|
2778 |
/**
|
|
|
2779 |
* Maps copyright license codes to their human readable counterpart.
|
|
|
2780 |
*
|
|
|
2781 |
* @type {Object}
|
|
|
2782 |
*/
|
|
|
2783 |
H5P.copyrightLicenses = {
|
|
|
2784 |
'U': H5P.t('licenseU'),
|
|
|
2785 |
'CC BY': {
|
|
|
2786 |
label: H5P.t('licenseCCBY'),
|
|
|
2787 |
link: 'http://creativecommons.org/licenses/by/:version',
|
|
|
2788 |
versions: ccVersions
|
|
|
2789 |
},
|
|
|
2790 |
'CC BY-SA': {
|
|
|
2791 |
label: H5P.t('licenseCCBYSA'),
|
|
|
2792 |
link: 'http://creativecommons.org/licenses/by-sa/:version',
|
|
|
2793 |
versions: ccVersions
|
|
|
2794 |
},
|
|
|
2795 |
'CC BY-ND': {
|
|
|
2796 |
label: H5P.t('licenseCCBYND'),
|
|
|
2797 |
link: 'http://creativecommons.org/licenses/by-nd/:version',
|
|
|
2798 |
versions: ccVersions
|
|
|
2799 |
},
|
|
|
2800 |
'CC BY-NC': {
|
|
|
2801 |
label: H5P.t('licenseCCBYNC'),
|
|
|
2802 |
link: 'http://creativecommons.org/licenses/by-nc/:version',
|
|
|
2803 |
versions: ccVersions
|
|
|
2804 |
},
|
|
|
2805 |
'CC BY-NC-SA': {
|
|
|
2806 |
label: H5P.t('licenseCCBYNCSA'),
|
|
|
2807 |
link: 'http://creativecommons.org/licenses/by-nc-sa/:version',
|
|
|
2808 |
versions: ccVersions
|
|
|
2809 |
},
|
|
|
2810 |
'CC BY-NC-ND': {
|
|
|
2811 |
label: H5P.t('licenseCCBYNCND'),
|
|
|
2812 |
link: 'http://creativecommons.org/licenses/by-nc-nd/:version',
|
|
|
2813 |
versions: ccVersions
|
|
|
2814 |
},
|
|
|
2815 |
'CC0 1.0': {
|
|
|
2816 |
label: H5P.t('licenseCC010'),
|
|
|
2817 |
link: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
|
2818 |
},
|
|
|
2819 |
'GNU GPL': {
|
|
|
2820 |
label: H5P.t('licenseGPL'),
|
|
|
2821 |
link: 'http://www.gnu.org/licenses/gpl-:version-standalone.html',
|
|
|
2822 |
linkVersions: {
|
|
|
2823 |
'v3': '3.0',
|
|
|
2824 |
'v2': '2.0',
|
|
|
2825 |
'v1': '1.0'
|
|
|
2826 |
},
|
|
|
2827 |
versions: {
|
|
|
2828 |
'default': 'v3',
|
|
|
2829 |
'v3': H5P.t('licenseV3'),
|
|
|
2830 |
'v2': H5P.t('licenseV2'),
|
|
|
2831 |
'v1': H5P.t('licenseV1')
|
|
|
2832 |
}
|
|
|
2833 |
},
|
|
|
2834 |
'PD': {
|
|
|
2835 |
label: H5P.t('licensePD'),
|
|
|
2836 |
versions: {
|
|
|
2837 |
'CC0 1.0': {
|
|
|
2838 |
label: H5P.t('licenseCC010'),
|
|
|
2839 |
link: 'https://creativecommons.org/publicdomain/zero/1.0/'
|
|
|
2840 |
},
|
|
|
2841 |
'CC PDM': {
|
|
|
2842 |
label: H5P.t('licensePDM'),
|
|
|
2843 |
link: 'https://creativecommons.org/publicdomain/mark/1.0/'
|
|
|
2844 |
}
|
|
|
2845 |
}
|
|
|
2846 |
},
|
|
|
2847 |
'ODC PDDL': '<a href="http://opendatacommons.org/licenses/pddl/1.0/" target="_blank">Public Domain Dedication and Licence</a>',
|
|
|
2848 |
'CC PDM': {
|
|
|
2849 |
label: H5P.t('licensePDM'),
|
|
|
2850 |
link: 'https://creativecommons.org/publicdomain/mark/1.0/'
|
|
|
2851 |
},
|
|
|
2852 |
'C': H5P.t('licenseC'),
|
|
|
2853 |
};
|
|
|
2854 |
|
|
|
2855 |
/**
|
|
|
2856 |
* Indicates if H5P is embedded on an external page using iframe.
|
|
|
2857 |
* @member {boolean} H5P.externalEmbed
|
|
|
2858 |
*/
|
|
|
2859 |
|
|
|
2860 |
// Relay events to top window. This must be done before H5P.init
|
|
|
2861 |
// since events may be fired on initialization.
|
|
|
2862 |
if (H5P.isFramed && H5P.externalEmbed === false) {
|
|
|
2863 |
H5P.externalDispatcher.on('*', function (event) {
|
|
|
2864 |
window.parent.H5P.externalDispatcher.trigger.call(this, event);
|
|
|
2865 |
});
|
|
|
2866 |
}
|
|
|
2867 |
|
|
|
2868 |
/**
|
|
|
2869 |
* Prevent H5P Core from initializing. Must be overriden before document ready.
|
|
|
2870 |
* @member {boolean} H5P.preventInit
|
|
|
2871 |
*/
|
|
|
2872 |
if (!H5P.preventInit) {
|
|
|
2873 |
// Note that this start script has to be an external resource for it to
|
|
|
2874 |
// load in correct order in IE9.
|
|
|
2875 |
H5P.init(document.body);
|
|
|
2876 |
}
|
|
|
2877 |
|
|
|
2878 |
if (H5PIntegration.saveFreq !== false) {
|
|
|
2879 |
// When was the last state stored
|
|
|
2880 |
var lastStoredOn = 0;
|
|
|
2881 |
// Store the current state of the H5P when leaving the page.
|
|
|
2882 |
var storeCurrentState = function () {
|
|
|
2883 |
// Make sure at least 250 ms has passed since last save
|
|
|
2884 |
var currentTime = new Date().getTime();
|
|
|
2885 |
if (currentTime - lastStoredOn > 250) {
|
|
|
2886 |
lastStoredOn = currentTime;
|
|
|
2887 |
for (var i = 0; i < H5P.instances.length; i++) {
|
|
|
2888 |
var instance = H5P.instances[i];
|
|
|
2889 |
if (instance.getCurrentState instanceof Function ||
|
|
|
2890 |
typeof instance.getCurrentState === 'function') {
|
|
|
2891 |
var state = instance.getCurrentState();
|
|
|
2892 |
if (state !== undefined) {
|
|
|
2893 |
// Async is not used to prevent the request from being cancelled.
|
|
|
2894 |
H5P.setUserData(instance.contentId, 'state', state, {deleteOnChange: true, async: false});
|
|
|
2895 |
}
|
|
|
2896 |
}
|
|
|
2897 |
}
|
|
|
2898 |
}
|
|
|
2899 |
};
|
|
|
2900 |
// iPad does not support beforeunload, therefore using unload
|
|
|
2901 |
H5P.$window.one('beforeunload unload', function () {
|
|
|
2902 |
// Only want to do this once
|
|
|
2903 |
H5P.$window.off('pagehide beforeunload unload');
|
|
|
2904 |
storeCurrentState();
|
|
|
2905 |
});
|
|
|
2906 |
// pagehide is used on iPad when tabs are switched
|
|
|
2907 |
H5P.$window.on('pagehide', storeCurrentState);
|
|
|
2908 |
}
|
|
|
2909 |
});
|
|
|
2910 |
|
|
|
2911 |
})(H5P.jQuery);
|