1 |
efrain |
1 |
/* global ns */
|
|
|
2 |
/**
|
|
|
3 |
* This file contains helper functions for the editor.
|
|
|
4 |
*/
|
|
|
5 |
|
|
|
6 |
// Grab common resources set in parent window, but avoid sharing back resources set in iframe)
|
|
|
7 |
window.ns = window.H5PEditor = H5P.jQuery.extend(false, {}, window.parent.H5PEditor);
|
|
|
8 |
ns.$ = H5P.jQuery;
|
|
|
9 |
|
|
|
10 |
// Load needed resources from parent.
|
|
|
11 |
H5PIntegration = H5P.jQuery.extend(false, {}, window.parent.H5PIntegration);
|
|
|
12 |
H5PIntegration.loadedJs = [];
|
|
|
13 |
H5PIntegration.loadedCss = [];
|
|
|
14 |
|
|
|
15 |
/**
|
|
|
16 |
* Constants used within editor
|
|
|
17 |
*
|
|
|
18 |
* @type {{otherLibraries: string}}
|
|
|
19 |
*/
|
|
|
20 |
ns.constants = {
|
|
|
21 |
otherLibraries: 'Other Libraries',
|
|
|
22 |
};
|
|
|
23 |
|
|
|
24 |
/**
|
|
|
25 |
* Keep track of our widgets.
|
|
|
26 |
*/
|
|
|
27 |
ns.widgets = {};
|
|
|
28 |
|
|
|
29 |
/**
|
|
|
30 |
* Caches library data (semantics, js and css)
|
|
|
31 |
*/
|
|
|
32 |
ns.libraryCache = {};
|
|
|
33 |
|
|
|
34 |
/**
|
|
|
35 |
* Keeps track of callbacks to run once a library gets loaded.
|
|
|
36 |
*/
|
|
|
37 |
ns.loadedCallbacks = [];
|
|
|
38 |
|
|
|
39 |
/**
|
|
|
40 |
* Keep track of which libraries have been loaded in the browser, i.e CSS is
|
|
|
41 |
* added and JS have been run
|
|
|
42 |
*
|
|
|
43 |
* @type {Object}
|
|
|
44 |
*/
|
|
|
45 |
ns.libraryLoaded = {};
|
|
|
46 |
|
|
|
47 |
/**
|
|
|
48 |
* Indiciates if the user is using Internet Explorer.
|
|
|
49 |
*/
|
|
|
50 |
ns.isIE = navigator.userAgent.match(/; MSIE \d+.\d+;/) !== null;
|
|
|
51 |
|
|
|
52 |
/**
|
|
|
53 |
* Keep track of renderable common fields.
|
|
|
54 |
*
|
|
|
55 |
* @type {Object}
|
|
|
56 |
*/
|
|
|
57 |
ns.renderableCommonFields = {};
|
|
|
58 |
|
|
|
59 |
(() => {
|
|
|
60 |
const loading = {}; // Map of callbacks for each src being loaded
|
|
|
61 |
|
|
|
62 |
/**
|
|
|
63 |
* Help load JavaScripts, prevents double loading.
|
|
|
64 |
*
|
|
|
65 |
* @param {string} src
|
|
|
66 |
* @param {Function} done Callback
|
|
|
67 |
*/
|
|
|
68 |
ns.loadJs = (src, done) => {
|
|
|
69 |
if (H5P.jsLoaded(src)) {
|
|
|
70 |
// Already loaded
|
|
|
71 |
done();
|
|
|
72 |
return;
|
|
|
73 |
}
|
|
|
74 |
|
|
|
75 |
if (loading[src] !== undefined) {
|
|
|
76 |
// Loading in progress...
|
|
|
77 |
loading[src].push(done);
|
|
|
78 |
return;
|
|
|
79 |
}
|
|
|
80 |
|
|
|
81 |
loading[src] = [done];
|
|
|
82 |
|
|
|
83 |
// Load using script tag
|
|
|
84 |
var script = document.createElement('script');
|
|
|
85 |
script.type = 'text/javascript';
|
|
|
86 |
script.charset = 'UTF-8';
|
|
|
87 |
script.async = false;
|
|
|
88 |
script.onload = function () {
|
|
|
89 |
H5PIntegration.loadedJs.push(src);
|
|
|
90 |
loading[src].forEach(cb => cb());
|
|
|
91 |
delete loading[src];
|
|
|
92 |
};
|
|
|
93 |
script.onerror = function (err) {
|
|
|
94 |
loading[src].forEach(cb => cb(err));
|
|
|
95 |
delete loading[src];
|
|
|
96 |
};
|
|
|
97 |
script.src = src;
|
|
|
98 |
document.head.appendChild(script);
|
|
|
99 |
};
|
|
|
100 |
})();
|
|
|
101 |
|
|
|
102 |
/**
|
|
|
103 |
* Helper function invoked when a library is requested. Will add CSS and eval JS
|
|
|
104 |
* if not already done.
|
|
|
105 |
*
|
|
|
106 |
* @private
|
|
|
107 |
* @param {string} libraryName On the form "machineName majorVersion.minorVersion"
|
|
|
108 |
* @param {Function} callback
|
|
|
109 |
*/
|
|
|
110 |
ns.libraryRequested = function (libraryName, callback) {
|
|
|
111 |
var libraryData = ns.libraryCache[libraryName];
|
|
|
112 |
|
|
|
113 |
if (!ns.libraryLoaded[libraryName]) {
|
|
|
114 |
// Add CSS.
|
|
|
115 |
if (libraryData.css !== undefined) {
|
|
|
116 |
libraryData.css.forEach(function (path) {
|
|
|
117 |
if (!H5P.cssLoaded(path)) {
|
|
|
118 |
H5PIntegration.loadedCss.push(path);
|
|
|
119 |
if (path) {
|
|
|
120 |
ns.$('head').append('<link ' +
|
|
|
121 |
'rel="stylesheet" ' +
|
|
|
122 |
'href="' + path + '" ' +
|
|
|
123 |
'type="text/css" ' +
|
|
|
124 |
'/>');
|
|
|
125 |
}
|
|
|
126 |
}
|
|
|
127 |
});
|
|
|
128 |
}
|
|
|
129 |
|
|
|
130 |
// Add JS
|
|
|
131 |
var loadingJs = false;
|
|
|
132 |
if (libraryData.javascript !== undefined && libraryData.javascript.length) {
|
|
|
133 |
libraryData.javascript.forEach(function (path) {
|
|
|
134 |
if (!H5P.jsLoaded(path)) {
|
|
|
135 |
loadingJs = true;
|
|
|
136 |
ns.loadJs(path, function (err) {
|
|
|
137 |
if (err) {
|
|
|
138 |
console.error('Error while loading script', err);
|
|
|
139 |
return;
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
var isFinishedLoading = libraryData.javascript.reduce(function (hasLoaded, jsPath) {
|
|
|
143 |
return hasLoaded && H5P.jsLoaded(jsPath);
|
|
|
144 |
}, true);
|
|
|
145 |
|
|
|
146 |
if (isFinishedLoading) {
|
|
|
147 |
ns.libraryLoaded[libraryName] = true;
|
|
|
148 |
|
|
|
149 |
// Need to set translations after all scripts have been loaded
|
|
|
150 |
if (libraryData.translations) {
|
|
|
151 |
for (var machineName in libraryData.translations) {
|
|
|
152 |
H5PEditor.language[machineName] = libraryData.translations[machineName];
|
|
|
153 |
}
|
|
|
154 |
}
|
|
|
155 |
|
|
|
156 |
callback(ns.libraryCache[libraryName].semantics);
|
|
|
157 |
}
|
|
|
158 |
});
|
|
|
159 |
}
|
|
|
160 |
});
|
|
|
161 |
}
|
|
|
162 |
if (!loadingJs) {
|
|
|
163 |
// Don't have to wait for any scripts, run callback
|
|
|
164 |
ns.libraryLoaded[libraryName] = true;
|
|
|
165 |
callback(ns.libraryCache[libraryName].semantics);
|
|
|
166 |
}
|
|
|
167 |
}
|
|
|
168 |
else {
|
|
|
169 |
// Already loaded, run callback
|
|
|
170 |
callback(ns.libraryCache[libraryName].semantics);
|
|
|
171 |
}
|
|
|
172 |
};
|
|
|
173 |
|
|
|
174 |
/**
|
|
|
175 |
* Loads the given library, inserts any css and js and
|
|
|
176 |
* then runs the callback with the samantics as an argument.
|
|
|
177 |
*
|
|
|
178 |
* @param {string} libraryName
|
|
|
179 |
* On the form machineName majorVersion.minorVersion
|
|
|
180 |
* @param {function} callback
|
|
|
181 |
* @returns {undefined}
|
|
|
182 |
*/
|
|
|
183 |
ns.loadLibrary = function (libraryName, callback) {
|
|
|
184 |
switch (ns.libraryCache[libraryName]) {
|
|
|
185 |
default:
|
|
|
186 |
// Get semantics from cache.
|
|
|
187 |
ns.libraryRequested(libraryName, callback);
|
|
|
188 |
break;
|
|
|
189 |
|
|
|
190 |
case 0:
|
|
|
191 |
// Add to queue.
|
|
|
192 |
if (ns.loadedCallbacks[libraryName] === undefined) {
|
|
|
193 |
ns.loadedCallbacks[libraryName] = [];
|
|
|
194 |
}
|
|
|
195 |
ns.loadedCallbacks[libraryName].push(callback);
|
|
|
196 |
break;
|
|
|
197 |
|
|
|
198 |
case undefined:
|
|
|
199 |
// Load semantics.
|
|
|
200 |
ns.libraryCache[libraryName] = 0; // Indicates that others should queue.
|
|
|
201 |
ns.loadedCallbacks[libraryName] = []; // Other callbacks to run once loaded.
|
|
|
202 |
var library = ns.libraryFromString(libraryName);
|
|
|
203 |
|
|
|
204 |
var url = ns.getAjaxUrl('libraries', library);
|
|
|
205 |
|
|
|
206 |
// Add content language to URL
|
|
|
207 |
if (ns.contentLanguage !== undefined) {
|
|
|
208 |
url += (url.indexOf('?') === -1 ? '?' : '&') + 'language=' + ns.contentLanguage;
|
|
|
209 |
}
|
|
|
210 |
// Add common fields default lanuage to URL
|
|
|
211 |
const defaultLanguage = ns.defaultLanguage; // Avoid changes after sending AJAX
|
|
|
212 |
if (defaultLanguage !== undefined) {
|
|
|
213 |
url += (url.indexOf('?') === -1 ? '?' : '&') + 'default-language=' + defaultLanguage;
|
|
|
214 |
}
|
|
|
215 |
|
|
|
216 |
// Fire away!
|
|
|
217 |
ns.$.ajax({
|
|
|
218 |
url: url,
|
|
|
219 |
success: function (libraryData) {
|
|
|
220 |
libraryData.translation = { // Used to cache all the translations
|
|
|
221 |
en: libraryData.semantics
|
|
|
222 |
};
|
|
|
223 |
let languageSemantics = [];
|
|
|
224 |
if (libraryData.language !== null) {
|
|
|
225 |
languageSemantics = JSON.parse(libraryData.language).semantics;
|
|
|
226 |
delete libraryData.language; // Avoid caching a lot of unused data
|
|
|
227 |
}
|
|
|
228 |
var semantics = ns.$.extend(true, [], libraryData.semantics, languageSemantics);
|
|
|
229 |
if (libraryData.defaultLanguage !== null) {
|
|
|
230 |
libraryData.translation[defaultLanguage] = JSON.parse(libraryData.defaultLanguage).semantics;
|
|
|
231 |
delete libraryData.defaultLanguage; // Avoid caching a lot of unused data
|
|
|
232 |
ns.updateCommonFieldsDefault(semantics, libraryData.translation[defaultLanguage]);
|
|
|
233 |
}
|
|
|
234 |
libraryData.semantics = semantics;
|
|
|
235 |
ns.libraryCache[libraryName] = libraryData;
|
|
|
236 |
|
|
|
237 |
ns.libraryRequested(libraryName, function (semantics) {
|
|
|
238 |
callback(semantics);
|
|
|
239 |
|
|
|
240 |
// Run queue.
|
|
|
241 |
if (ns.loadedCallbacks[libraryName]) {
|
|
|
242 |
for (var i = 0; i < ns.loadedCallbacks[libraryName].length; i++) {
|
|
|
243 |
ns.loadedCallbacks[libraryName][i](semantics);
|
|
|
244 |
}
|
|
|
245 |
}
|
|
|
246 |
});
|
|
|
247 |
},
|
|
|
248 |
error: function (jqXHR, textStatus, errorThrown) {
|
|
|
249 |
if (window['console'] !== undefined) {
|
|
|
250 |
console.warn('Ajax request failed');
|
|
|
251 |
console.warn(jqXHR);
|
|
|
252 |
console.warn(textStatus);
|
|
|
253 |
console.warn(errorThrown);
|
|
|
254 |
}
|
|
|
255 |
},
|
|
|
256 |
dataType: 'json'
|
|
|
257 |
});
|
|
|
258 |
}
|
|
|
259 |
};
|
|
|
260 |
|
|
|
261 |
/**
|
|
|
262 |
* Update common fields default values for the given semantics.
|
|
|
263 |
* Works by reference.
|
|
|
264 |
*
|
|
|
265 |
* @param {Array} semantics
|
|
|
266 |
* @param {Array} translation
|
|
|
267 |
* @param {boolean} [parentIsCommon] Used to indicated that one of the ancestors is a common field
|
|
|
268 |
*/
|
|
|
269 |
ns.updateCommonFieldsDefault = function (semantics, translation, parentIsCommon) {
|
|
|
270 |
for (let i = 0; i < semantics.length; i++) {
|
|
|
271 |
const isCommon = (semantics[i].common === true || parentIsCommon);
|
|
|
272 |
if (isCommon && semantics[i].default !== undefined &&
|
|
|
273 |
translation[i] !== undefined && translation[i].default !== undefined) {
|
|
|
274 |
// Update value
|
|
|
275 |
semantics[i].default = translation[i].default;
|
|
|
276 |
}
|
|
|
277 |
if (semantics[i].fields !== undefined && semantics[i].fields.length &&
|
|
|
278 |
translation[i].fields !== undefined && translation[i].fields.length) {
|
|
|
279 |
// Look into sub fields
|
|
|
280 |
ns.updateCommonFieldsDefault(semantics[i].fields, translation[i].fields, isCommon);
|
|
|
281 |
}
|
|
|
282 |
if (semantics[i].field !== undefined && translation[i].field !== undefined ) {
|
|
|
283 |
// Look into sub field
|
|
|
284 |
ns.updateCommonFieldsDefault([semantics[i].field], [translation[i].field], isCommon);
|
|
|
285 |
}
|
|
|
286 |
}
|
|
|
287 |
};
|
|
|
288 |
|
|
|
289 |
/**
|
|
|
290 |
* Reset loaded libraries - i.e removes CSS added previously.
|
|
|
291 |
* @method
|
|
|
292 |
* @return {[type]}
|
|
|
293 |
*/
|
|
|
294 |
ns.resetLoadedLibraries = function () {
|
|
|
295 |
ns.$('head style.h5p-editor-style').remove();
|
|
|
296 |
H5PIntegration.loadedCss = [];
|
|
|
297 |
H5PIntegration.loadedJs = [];
|
|
|
298 |
ns.loadedCallbacks = [];
|
|
|
299 |
ns.libraryLoaded = {};
|
|
|
300 |
ns.libraryCache = {};
|
|
|
301 |
};
|
|
|
302 |
|
|
|
303 |
/**
|
|
|
304 |
* Render common fields of content type with given machine name
|
|
|
305 |
*
|
|
|
306 |
* @param {string} machineName Machine name of content type with common fields
|
|
|
307 |
* @param {Array} [libraries] Library data for machine name
|
|
|
308 |
*/
|
|
|
309 |
ns.renderCommonField = function (machineName, libraries) {
|
|
|
310 |
var commonFields = ns.renderableCommonFields[machineName].fields;
|
|
|
311 |
var renderableCommonFields = [];
|
|
|
312 |
var ancestor;
|
|
|
313 |
|
|
|
314 |
commonFields.forEach(function (field) {
|
|
|
315 |
if (!field.rendered) {
|
|
|
316 |
var commonField = ns.addCommonField(
|
|
|
317 |
field.field,
|
|
|
318 |
field.parent,
|
|
|
319 |
field.params,
|
|
|
320 |
field.ancestor,
|
|
|
321 |
true
|
|
|
322 |
);
|
|
|
323 |
if (commonField.setValues.length === 1) {
|
|
|
324 |
renderableCommonFields.push({
|
|
|
325 |
field: field,
|
|
|
326 |
instance: commonField.instance
|
|
|
327 |
});
|
|
|
328 |
field.instance = commonField.instance;
|
|
|
329 |
}
|
|
|
330 |
}
|
|
|
331 |
field.rendered = true;
|
|
|
332 |
});
|
|
|
333 |
|
|
|
334 |
// Render common fields if found
|
|
|
335 |
if (renderableCommonFields.length) {
|
|
|
336 |
var libraryName = machineName === ns.constants.otherLibraries ? machineName
|
|
|
337 |
: (machineName.length ? machineName.split(' ')[0] : '');
|
|
|
338 |
if (libraries.length && libraries[0].title) {
|
|
|
339 |
libraryName = libraries[0].title;
|
|
|
340 |
}
|
|
|
341 |
|
|
|
342 |
// Create a library wrapper
|
|
|
343 |
var hasLibraryWrapper = !!ns.renderableCommonFields[machineName].wrapper;
|
|
|
344 |
var commonFieldsLibraryWrapper = ns.renderableCommonFields[machineName].wrapper;
|
|
|
345 |
if (!hasLibraryWrapper) {
|
|
|
346 |
commonFieldsLibraryWrapper = document.createElement('fieldset');
|
|
|
347 |
var libraryWrapperClass = libraryName.replace(/\s+/g, '-').toLowerCase();
|
|
|
348 |
|
|
|
349 |
commonFieldsLibraryWrapper.classList.add('common-fields-library-wrapper');
|
|
|
350 |
commonFieldsLibraryWrapper.classList.add('common-fields-' + libraryWrapperClass);
|
|
|
351 |
|
|
|
352 |
var libraryTitle = document.createElement('legend');
|
|
|
353 |
libraryTitle.classList.add('common-field-legend');
|
|
|
354 |
libraryTitle.textContent = libraryName;
|
|
|
355 |
libraryTitle.tabIndex = '0';
|
|
|
356 |
libraryTitle.setAttribute('role', 'button');
|
|
|
357 |
libraryTitle.addEventListener('click', function () {
|
|
|
358 |
commonFieldsLibraryWrapper.classList.toggle('expanded');
|
|
|
359 |
});
|
|
|
360 |
libraryTitle.addEventListener('keypress', function (e) {
|
|
|
361 |
if (e.which === 32) {
|
|
|
362 |
commonFieldsLibraryWrapper.classList.toggle('expanded');
|
|
|
363 |
}
|
|
|
364 |
});
|
|
|
365 |
commonFieldsLibraryWrapper.appendChild(libraryTitle);
|
|
|
366 |
|
|
|
367 |
ns.renderableCommonFields[machineName].wrapper = commonFieldsLibraryWrapper;
|
|
|
368 |
}
|
|
|
369 |
|
|
|
370 |
renderableCommonFields.forEach(function (commonField) {
|
|
|
371 |
commonField.instance.appendTo(ns.$(commonFieldsLibraryWrapper));
|
|
|
372 |
// Gather under a common ancestor
|
|
|
373 |
if (commonField.field && commonField.field.ancestor) {
|
|
|
374 |
ancestor = commonField.field.ancestor;
|
|
|
375 |
|
|
|
376 |
// Ensure that params are updated after common field instance is
|
|
|
377 |
// appended since this ensures that defaults are set for common fields
|
|
|
378 |
const field = commonField.field;
|
|
|
379 |
const library = field.parent.currentLibrary;
|
|
|
380 |
const fieldName = field.field.name;
|
|
|
381 |
const ancestorField = ancestor.commonFields[library][fieldName];
|
|
|
382 |
ancestorField.params = field.params[fieldName];
|
|
|
383 |
}
|
|
|
384 |
});
|
|
|
385 |
|
|
|
386 |
if (!hasLibraryWrapper && ancestor) {
|
|
|
387 |
ancestor.$common[0].appendChild(commonFieldsLibraryWrapper);
|
|
|
388 |
}
|
|
|
389 |
}
|
|
|
390 |
};
|
|
|
391 |
|
|
|
392 |
/**
|
|
|
393 |
* Recursively traverse parents to find the library our field belongs to
|
|
|
394 |
*
|
|
|
395 |
* @param parent
|
|
|
396 |
* @returns {*}
|
|
|
397 |
*/
|
|
|
398 |
ns.getParentLibrary = function (parent) {
|
|
|
399 |
if (!parent) {
|
|
|
400 |
return null;
|
|
|
401 |
}
|
|
|
402 |
|
|
|
403 |
if (parent.currentLibrary) {
|
|
|
404 |
return parent.currentLibrary;
|
|
|
405 |
}
|
|
|
406 |
|
|
|
407 |
return ns.getParentLibrary(parent.parent);
|
|
|
408 |
};
|
|
|
409 |
|
|
|
410 |
/**
|
|
|
411 |
* Recursive processing of the semantics chunks.
|
|
|
412 |
*
|
|
|
413 |
* @param {array} semanticsChunk
|
|
|
414 |
* @param {object} params
|
|
|
415 |
* @param {jQuery} $wrapper
|
|
|
416 |
* @param {mixed} parent
|
|
|
417 |
* @param {string} [machineName] Machine name of library that is being processed
|
|
|
418 |
* @returns {undefined}
|
|
|
419 |
*/
|
|
|
420 |
ns.processSemanticsChunk = function (semanticsChunk, params, $wrapper, parent, machineName) {
|
|
|
421 |
var ancestor;
|
|
|
422 |
parent.children = [];
|
|
|
423 |
|
|
|
424 |
if (parent.passReadies === undefined) {
|
|
|
425 |
throw 'Widget tried to run processSemanticsChunk without handling ready callbacks. [field:' + parent.field.type + ':' + parent.field.name + ']';
|
|
|
426 |
}
|
|
|
427 |
|
|
|
428 |
if (!parent.passReadies) {
|
|
|
429 |
// If the parent can't pass ready callbacks we need to take care of them.
|
|
|
430 |
parent.readies = [];
|
|
|
431 |
}
|
|
|
432 |
|
|
|
433 |
for (var i = 0; i < semanticsChunk.length; i++) {
|
|
|
434 |
var field = semanticsChunk[i];
|
|
|
435 |
|
|
|
436 |
// Check generic field properties.
|
|
|
437 |
if (field.name === undefined) {
|
|
|
438 |
throw ns.t('core', 'missingProperty', {':index': i, ':property': 'name'});
|
|
|
439 |
}
|
|
|
440 |
if (field.type === undefined) {
|
|
|
441 |
throw ns.t('core', 'missingProperty', {':index': i, ':property': 'type'});
|
|
|
442 |
}
|
|
|
443 |
|
|
|
444 |
// Set default value.
|
|
|
445 |
if (params[field.name] === undefined && field['default'] !== undefined) {
|
|
|
446 |
params[field.name] = field['default'];
|
|
|
447 |
}
|
|
|
448 |
|
|
|
449 |
var widget = ns.getWidgetName(field);
|
|
|
450 |
|
|
|
451 |
// TODO: Remove later, this is here for debugging purposes.
|
|
|
452 |
if (ns.widgets[widget] === undefined) {
|
|
|
453 |
$wrapper.append('<div>[field:' + field.type + ':' + widget + ':' + field.name + ']</div>');
|
|
|
454 |
continue;
|
|
|
455 |
}
|
|
|
456 |
|
|
|
457 |
// Add common fields to bottom of form.
|
|
|
458 |
if (field.common !== undefined && field.common) {
|
|
|
459 |
if (ancestor === undefined) {
|
|
|
460 |
ancestor = ns.findAncestor(parent);
|
|
|
461 |
}
|
|
|
462 |
|
|
|
463 |
var parentLibrary = ns.getParentLibrary(parent);
|
|
|
464 |
var library = machineName ? machineName
|
|
|
465 |
: (field.library ? field.library
|
|
|
466 |
: (parentLibrary ? parentLibrary
|
|
|
467 |
: ns.constants.otherLibraries));
|
|
|
468 |
ns.renderableCommonFields[library] = ns.renderableCommonFields[library] || {};
|
|
|
469 |
ns.renderableCommonFields[library].fields = ns.renderableCommonFields[library].fields || [];
|
|
|
470 |
|
|
|
471 |
// Add renderable if it doesn't exist
|
|
|
472 |
ns.renderableCommonFields[library].fields.push({
|
|
|
473 |
field: field,
|
|
|
474 |
parent: parent,
|
|
|
475 |
params: params,
|
|
|
476 |
ancestor: ancestor,
|
|
|
477 |
rendered: false
|
|
|
478 |
});
|
|
|
479 |
continue;
|
|
|
480 |
}
|
|
|
481 |
|
|
|
482 |
var fieldInstance = new ns.widgets[widget](parent, field, params[field.name], function (field, value) {
|
|
|
483 |
if (value === undefined) {
|
|
|
484 |
delete params[field.name];
|
|
|
485 |
}
|
|
|
486 |
else {
|
|
|
487 |
params[field.name] = value;
|
|
|
488 |
}
|
|
|
489 |
});
|
|
|
490 |
fieldInstance.appendTo($wrapper);
|
|
|
491 |
parent.children.push(fieldInstance);
|
|
|
492 |
}
|
|
|
493 |
|
|
|
494 |
// Render all gathered common field
|
|
|
495 |
if (ns.renderableCommonFields) {
|
|
|
496 |
for (var commonFieldMachineName in ns.renderableCommonFields) {
|
|
|
497 |
if (commonFieldMachineName === ns.constants.otherLibraries) {
|
|
|
498 |
// No need to grab library info
|
|
|
499 |
ns.renderCommonField(commonFieldMachineName);
|
|
|
500 |
}
|
|
|
501 |
else {
|
|
|
502 |
// Get title for common fields group
|
|
|
503 |
H5PEditor.LibraryListCache.getLibraries(
|
|
|
504 |
[commonFieldMachineName],
|
|
|
505 |
ns.renderCommonField.bind(this, commonFieldMachineName)
|
|
|
506 |
);
|
|
|
507 |
}
|
|
|
508 |
}
|
|
|
509 |
}
|
|
|
510 |
|
|
|
511 |
if (!parent.passReadies) {
|
|
|
512 |
// Run ready callbacks.
|
|
|
513 |
for (i = 0; i < parent.readies.length; i++) {
|
|
|
514 |
parent.readies[i]();
|
|
|
515 |
}
|
|
|
516 |
delete parent.readies;
|
|
|
517 |
}
|
|
|
518 |
};
|
|
|
519 |
|
|
|
520 |
/**
|
|
|
521 |
* Attach ancestor of parent's common fields to a new wrapper
|
|
|
522 |
*
|
|
|
523 |
* @param {Object} parent Parent content type instance that common fields should be attached to
|
|
|
524 |
* @param {HTMLElement} wrapper New wrapper of common fields
|
|
|
525 |
*/
|
|
|
526 |
ns.setCommonFieldsWrapper = function (parent, wrapper) {
|
|
|
527 |
var ancestor = ns.findAncestor(parent);
|
|
|
528 |
// Hide the ancestor whose children will be reattached elsewhere
|
|
|
529 |
wrapper.appendChild(ancestor.$common[0]);
|
|
|
530 |
};
|
|
|
531 |
|
|
|
532 |
/**
|
|
|
533 |
* Add a field to the common container.
|
|
|
534 |
*
|
|
|
535 |
* @param {object} field
|
|
|
536 |
* @param {object} parent
|
|
|
537 |
* @param {object} params
|
|
|
538 |
* @param {object} ancestor
|
|
|
539 |
* @param {boolean} [skipAppendTo] Skips appending the common field if set
|
|
|
540 |
* @returns {undefined}
|
|
|
541 |
*/
|
|
|
542 |
ns.addCommonField = function (field, parent, params, ancestor, skipAppendTo) {
|
|
|
543 |
var commonField;
|
|
|
544 |
|
|
|
545 |
// Group all fields based on library name + version
|
|
|
546 |
if (ancestor.commonFields[parent.currentLibrary] === undefined) {
|
|
|
547 |
ancestor.commonFields[parent.currentLibrary] = {};
|
|
|
548 |
}
|
|
|
549 |
|
|
|
550 |
// Field name will have to be unique for library
|
|
|
551 |
if (ancestor.commonFields[parent.currentLibrary][field.name] === undefined) {
|
|
|
552 |
var widget = ns.getWidgetName(field);
|
|
|
553 |
ancestor.commonFields[parent.currentLibrary][field.name] = {
|
|
|
554 |
instance: new ns.widgets[widget](parent, field, params[field.name], function (field, value) {
|
|
|
555 |
for (var i = 0; i < commonField.setValues.length; i++) {
|
|
|
556 |
commonField.setValues[i](field, value);
|
|
|
557 |
}
|
|
|
558 |
}),
|
|
|
559 |
setValues: [],
|
|
|
560 |
parents: []
|
|
|
561 |
};
|
|
|
562 |
}
|
|
|
563 |
|
|
|
564 |
commonField = ancestor.commonFields[parent.currentLibrary][field.name];
|
|
|
565 |
commonField.parents.push(ns.findLibraryAncestor(parent));
|
|
|
566 |
commonField.setValues.push(function (field, value) {
|
|
|
567 |
if (value === undefined) {
|
|
|
568 |
delete params[field.name];
|
|
|
569 |
}
|
|
|
570 |
else {
|
|
|
571 |
params[field.name] = value;
|
|
|
572 |
}
|
|
|
573 |
});
|
|
|
574 |
|
|
|
575 |
if (commonField.setValues.length === 1) {
|
|
|
576 |
ancestor.$common.parent().removeClass('hidden');
|
|
|
577 |
if (!skipAppendTo) {
|
|
|
578 |
commonField.instance.appendTo(ancestor.$common);
|
|
|
579 |
}
|
|
|
580 |
commonField.params = params[field.name];
|
|
|
581 |
}
|
|
|
582 |
else {
|
|
|
583 |
params[field.name] = commonField.params;
|
|
|
584 |
}
|
|
|
585 |
|
|
|
586 |
parent.children.push(commonField.instance);
|
|
|
587 |
return commonField;
|
|
|
588 |
};
|
|
|
589 |
|
|
|
590 |
/**
|
|
|
591 |
* Find the nearest library ancestor. Used when adding commonfields.
|
|
|
592 |
*
|
|
|
593 |
* @param {object} parent
|
|
|
594 |
* @returns {ns.findLibraryAncestor.parent|@exp;ns@call;findLibraryAncestor}
|
|
|
595 |
*/
|
|
|
596 |
ns.findLibraryAncestor = function (parent) {
|
|
|
597 |
if (parent.parent === undefined || parent.field.type === 'library') {
|
|
|
598 |
return parent;
|
|
|
599 |
}
|
|
|
600 |
return ns.findLibraryAncestor(parent.parent);
|
|
|
601 |
};
|
|
|
602 |
|
|
|
603 |
/**
|
|
|
604 |
* getParentZebra
|
|
|
605 |
*
|
|
|
606 |
* Alternate the background color of fields
|
|
|
607 |
*
|
|
|
608 |
* @param parent
|
|
|
609 |
* @returns {string} to determine background color of callee
|
|
|
610 |
*/
|
|
|
611 |
ns.getParentZebra = function (parent) {
|
|
|
612 |
if (parent.zebra) {
|
|
|
613 |
return parent.zebra;
|
|
|
614 |
}
|
|
|
615 |
else {
|
|
|
616 |
return ns.getParentZebra(parent.parent);
|
|
|
617 |
}
|
|
|
618 |
};
|
|
|
619 |
|
|
|
620 |
/**
|
|
|
621 |
* Find the nearest ancestor which handles commonFields.
|
|
|
622 |
*
|
|
|
623 |
* @param {type} parent
|
|
|
624 |
* @returns {@exp;ns@call;findAncestor|ns.findAncestor.parent}
|
|
|
625 |
*/
|
|
|
626 |
ns.findAncestor = function (parent) {
|
|
|
627 |
if (parent.commonFields === undefined) {
|
|
|
628 |
return ns.findAncestor(parent.parent);
|
|
|
629 |
}
|
|
|
630 |
return parent;
|
|
|
631 |
};
|
|
|
632 |
|
|
|
633 |
/**
|
|
|
634 |
* Call remove on the given children.
|
|
|
635 |
*
|
|
|
636 |
* @param {Array} children
|
|
|
637 |
* @returns {unresolved}
|
|
|
638 |
*/
|
|
|
639 |
ns.removeChildren = function (children) {
|
|
|
640 |
if (children === undefined) {
|
|
|
641 |
return;
|
|
|
642 |
}
|
|
|
643 |
|
|
|
644 |
for (var i = 0; i < children.length; i++) {
|
|
|
645 |
// Common fields will be removed by library.
|
|
|
646 |
var isCommonField = (children[i].field === undefined ||
|
|
|
647 |
children[i].field.common === undefined ||
|
|
|
648 |
!children[i].field.common);
|
|
|
649 |
|
|
|
650 |
var hasRemove = (children[i].remove instanceof Function ||
|
|
|
651 |
typeof children[i].remove === 'function');
|
|
|
652 |
|
|
|
653 |
if (isCommonField && hasRemove) {
|
|
|
654 |
children[i].remove();
|
|
|
655 |
}
|
|
|
656 |
}
|
|
|
657 |
};
|
|
|
658 |
|
|
|
659 |
/**
|
|
|
660 |
* Find field from path.
|
|
|
661 |
*
|
|
|
662 |
* @param {String} path
|
|
|
663 |
* @param {Object} parent
|
|
|
664 |
* @returns {@exp;ns.Form@call;findField|Boolean}
|
|
|
665 |
*/
|
|
|
666 |
ns.findField = function (path, parent) {
|
|
|
667 |
if (typeof path === 'string') {
|
|
|
668 |
path = path.split('/');
|
|
|
669 |
}
|
|
|
670 |
|
|
|
671 |
if (path[0] === '..') {
|
|
|
672 |
path.splice(0, 1);
|
|
|
673 |
return ns.findField(path, parent.parent);
|
|
|
674 |
}
|
|
|
675 |
if (parent.children) {
|
|
|
676 |
for (var i = 0; i < parent.children.length; i++) {
|
|
|
677 |
if (parent.children[i].field.name === path[0]) {
|
|
|
678 |
path.splice(0, 1);
|
|
|
679 |
if (path.length) {
|
|
|
680 |
return ns.findField(path, parent.children[i]);
|
|
|
681 |
}
|
|
|
682 |
else {
|
|
|
683 |
return parent.children[i];
|
|
|
684 |
}
|
|
|
685 |
}
|
|
|
686 |
}
|
|
|
687 |
}
|
|
|
688 |
|
|
|
689 |
return false;
|
|
|
690 |
};
|
|
|
691 |
|
|
|
692 |
/**
|
|
|
693 |
* Find a semantics field in the semantics structure by name of the field
|
|
|
694 |
* Will return the first found by depth first search if there are identically named fields
|
|
|
695 |
*
|
|
|
696 |
* @param {string} fieldName Name of the field we wish to find
|
|
|
697 |
* @param {Object|Array} semanticsStructure Semantics we wish to find the field within
|
|
|
698 |
* @returns {null|Object} Returns the field if found, otherwise null.
|
|
|
699 |
*/
|
|
|
700 |
ns.findSemanticsField = function (fieldName, semanticsStructure) {
|
|
|
701 |
if (Array.isArray(semanticsStructure)) {
|
|
|
702 |
for (let i = 0; i < semanticsStructure.length; i++) {
|
|
|
703 |
var semanticsField = ns.findSemanticsField(fieldName, semanticsStructure[i]);
|
|
|
704 |
if (semanticsField !== null) {
|
|
|
705 |
// Return immediately if field is found
|
|
|
706 |
return semanticsField;
|
|
|
707 |
}
|
|
|
708 |
}
|
|
|
709 |
return null;
|
|
|
710 |
}
|
|
|
711 |
else if (semanticsStructure.name === fieldName) {
|
|
|
712 |
return semanticsStructure;
|
|
|
713 |
}
|
|
|
714 |
else if (semanticsStructure.field) {
|
|
|
715 |
// Process field
|
|
|
716 |
return ns.findSemanticsField(fieldName, semanticsStructure.field);
|
|
|
717 |
}
|
|
|
718 |
else if (semanticsStructure.fields) {
|
|
|
719 |
// Process fields
|
|
|
720 |
return ns.findSemanticsField(fieldName, semanticsStructure.fields);
|
|
|
721 |
}
|
|
|
722 |
else {
|
|
|
723 |
// No matching semantics found within known properties and list structures
|
|
|
724 |
return null;
|
|
|
725 |
}
|
|
|
726 |
};
|
|
|
727 |
|
|
|
728 |
/**
|
|
|
729 |
* Follow a field and get all changes to its params.
|
|
|
730 |
*
|
|
|
731 |
* @param {Object} parent The parent object of the field.
|
|
|
732 |
* @param {String} path Relative to parent object.
|
|
|
733 |
* @param {Function} callback Gets called for params changes.
|
|
|
734 |
* @returns {undefined}
|
|
|
735 |
*/
|
|
|
736 |
ns.followField = function (parent, path, callback) {
|
|
|
737 |
if (path === undefined) {
|
|
|
738 |
return;
|
|
|
739 |
}
|
|
|
740 |
|
|
|
741 |
// Find field when tree is ready.
|
|
|
742 |
parent.ready(function () {
|
|
|
743 |
var def;
|
|
|
744 |
|
|
|
745 |
if (path instanceof Object) {
|
|
|
746 |
// We have an object with default values
|
|
|
747 |
def = H5P.cloneObject(path);
|
|
|
748 |
|
|
|
749 |
if (path.field === undefined) {
|
|
|
750 |
callback(path, null);
|
|
|
751 |
return; // Exit if we have no field to follow.
|
|
|
752 |
}
|
|
|
753 |
|
|
|
754 |
path = def.field;
|
|
|
755 |
delete def.field;
|
|
|
756 |
}
|
|
|
757 |
|
|
|
758 |
var field = ns.findField(path, parent);
|
|
|
759 |
|
|
|
760 |
if (!field) {
|
|
|
761 |
throw ns.t('core', 'unknownFieldPath', {':path': path});
|
|
|
762 |
}
|
|
|
763 |
if (field.changes === undefined) {
|
|
|
764 |
throw ns.t('core', 'noFollow', {':path': path});
|
|
|
765 |
}
|
|
|
766 |
|
|
|
767 |
var params = (field.params === undefined ? def : field.params);
|
|
|
768 |
callback(params, field.changes.length + 1);
|
|
|
769 |
|
|
|
770 |
field.changes.push(function () {
|
|
|
771 |
var params = (field.params === undefined ? def : field.params);
|
|
|
772 |
callback(params);
|
|
|
773 |
});
|
|
|
774 |
});
|
|
|
775 |
};
|
|
|
776 |
|
|
|
777 |
/**
|
|
|
778 |
* Create HTML wrapper for error messages.
|
|
|
779 |
*
|
|
|
780 |
* @param {String} message
|
|
|
781 |
* @returns {String}
|
|
|
782 |
*/
|
|
|
783 |
ns.createError = function (message) {
|
|
|
784 |
return '<p>' + message + '</p>';
|
|
|
785 |
};
|
|
|
786 |
|
|
|
787 |
/**
|
|
|
788 |
* Turn a numbered importance into a string.
|
|
|
789 |
*
|
|
|
790 |
* @param {string} importance
|
|
|
791 |
* @returns {String}
|
|
|
792 |
*/
|
|
|
793 |
ns.createImportance = function (importance) {
|
|
|
794 |
return importance ? 'importance-' + importance : '';
|
|
|
795 |
};
|
|
|
796 |
|
|
|
797 |
/**
|
|
|
798 |
* Create HTML wrapper for field items.
|
|
|
799 |
* Makes sure the different elements are placed in an consistent order.
|
|
|
800 |
*
|
|
|
801 |
* @param {string} type
|
|
|
802 |
* @param {string} [label]
|
|
|
803 |
* @param {string} [description]
|
|
|
804 |
* @param {string} [content]
|
|
|
805 |
* @deprecated since version 1.12 (Jan. 2017, will be removed Jan. 2018). Use createFieldMarkup instead.
|
|
|
806 |
* @see createFieldMarkup
|
|
|
807 |
* @returns {string} HTML
|
|
|
808 |
*/
|
|
|
809 |
ns.createItem = function (type, label, description, content) {
|
|
|
810 |
return '<div class="field ' + type + '">' +
|
|
|
811 |
(label ? label : '') +
|
|
|
812 |
(description ? '<div class="h5peditor-field-description">' + description + '</div>' : '') +
|
|
|
813 |
(content ? content : '') +
|
|
|
814 |
'<div class="h5p-errors"></div>' +
|
|
|
815 |
'</div>';
|
|
|
816 |
};
|
|
|
817 |
|
|
|
818 |
/**
|
|
|
819 |
* An object describing the semantics of a field
|
|
|
820 |
* @typedef {Object} SemanticField
|
|
|
821 |
* @property {string} name
|
|
|
822 |
* @property {string} type
|
|
|
823 |
* @property {string} label
|
|
|
824 |
* @property {string} [importance]
|
|
|
825 |
* @property {string} [description]
|
|
|
826 |
* @property {string} [widget]
|
|
|
827 |
* @property {boolean} [optional]
|
|
|
828 |
*/
|
|
|
829 |
|
|
|
830 |
/**
|
|
|
831 |
* Create HTML wrapper for a field item.
|
|
|
832 |
* Replacement for createItem()
|
|
|
833 |
*
|
|
|
834 |
* @since 1.12
|
|
|
835 |
* @param {SemanticField} field
|
|
|
836 |
* @param {string} content
|
|
|
837 |
* @param {string} [inputId]
|
|
|
838 |
* @return {string}
|
|
|
839 |
*/
|
|
|
840 |
ns.createFieldMarkup = function (field, content, inputId) {
|
|
|
841 |
content = content || '';
|
|
|
842 |
var markup = this.createLabel(field, '', inputId) + this.createDescription(field.description, inputId) + content;
|
|
|
843 |
|
|
|
844 |
return this.wrapFieldMarkup(field, markup);
|
|
|
845 |
};
|
|
|
846 |
|
|
|
847 |
/**
|
|
|
848 |
* Create HTML wrapper for a boolean field item.
|
|
|
849 |
*
|
|
|
850 |
* @param {SemanticField} field
|
|
|
851 |
* @param {string} content
|
|
|
852 |
* @param {string} [inputId]
|
|
|
853 |
*
|
|
|
854 |
* @return {string}
|
|
|
855 |
*/
|
|
|
856 |
ns.createBooleanFieldMarkup = function (field, content, inputId) {
|
|
|
857 |
var markup = '<label class="h5peditor-label">' +
|
|
|
858 |
content + (field.label || field.name || '') + '</label>' +
|
|
|
859 |
this.createDescription(field.description, inputId);
|
|
|
860 |
|
|
|
861 |
return this.wrapFieldMarkup(field, markup);
|
|
|
862 |
};
|
|
|
863 |
|
|
|
864 |
/**
|
|
|
865 |
* Wraps a field with some metadata classes, and adds error field
|
|
|
866 |
*
|
|
|
867 |
* @param {SemanticField} field
|
|
|
868 |
* @param {string} markup
|
|
|
869 |
*
|
|
|
870 |
* @private
|
|
|
871 |
* @return {string}
|
|
|
872 |
*/
|
|
|
873 |
ns.wrapFieldMarkup = function (field, markup) {
|
|
|
874 |
// removes undefined and joins
|
|
|
875 |
var wrapperClasses = this.joinNonEmptyStrings(['field', 'field-name-' + field.name, field.type, ns.createImportance(field.importance), field.widget]);
|
|
|
876 |
|
|
|
877 |
// wrap and return
|
|
|
878 |
return '<div class="' + wrapperClasses + '">' +
|
|
|
879 |
markup +
|
|
|
880 |
'<div class="h5p-errors"></div>' +
|
|
|
881 |
'</div>';
|
|
|
882 |
};
|
|
|
883 |
|
|
|
884 |
/**
|
|
|
885 |
* Joins an array of strings if they are defined and non empty
|
|
|
886 |
*
|
|
|
887 |
* @param {string[]} arr
|
|
|
888 |
* @param {string} [separator] Default is space
|
|
|
889 |
* @return {string}
|
|
|
890 |
*/
|
|
|
891 |
ns.joinNonEmptyStrings = function (arr, separator) {
|
|
|
892 |
separator = separator || ' ';
|
|
|
893 |
|
|
|
894 |
return arr.filter(function (str) {
|
|
|
895 |
return str !== undefined && str.length > 0;
|
|
|
896 |
}).join(separator);
|
|
|
897 |
};
|
|
|
898 |
|
|
|
899 |
/**
|
|
|
900 |
* Create HTML for select options.
|
|
|
901 |
*
|
|
|
902 |
* @param {String} value
|
|
|
903 |
* @param {String} text
|
|
|
904 |
* @param {Boolean} selected
|
|
|
905 |
* @returns {String}
|
|
|
906 |
*/
|
|
|
907 |
ns.createOption = function (value, text, selected) {
|
|
|
908 |
return '<option value="' + value + '"' + (selected !== undefined && selected ? ' selected="selected"' : '') + '>' + text + '</option>';
|
|
|
909 |
};
|
|
|
910 |
|
|
|
911 |
/**
|
|
|
912 |
* Create HTML for text input.
|
|
|
913 |
*
|
|
|
914 |
* @param {String} value
|
|
|
915 |
* @param {number} maxLength
|
|
|
916 |
* @param {String} placeholder
|
|
|
917 |
* @param {number} [id]
|
|
|
918 |
* @param {number} [describedby]
|
|
|
919 |
* @returns {String}
|
|
|
920 |
*/
|
|
|
921 |
ns.createText = function (value, maxLength, placeholder, id, describedby) {
|
|
|
922 |
var html = '<input class="h5peditor-text" type="text"';
|
|
|
923 |
|
|
|
924 |
if (id !== undefined) {
|
|
|
925 |
html += ' id="' + id + '"';
|
|
|
926 |
}
|
|
|
927 |
|
|
|
928 |
if (describedby !== undefined) {
|
|
|
929 |
html += ' aria-describedby="' + describedby + '"';
|
|
|
930 |
}
|
|
|
931 |
|
|
|
932 |
if (value !== undefined) {
|
|
|
933 |
html += ' value="' + value + '"';
|
|
|
934 |
}
|
|
|
935 |
|
|
|
936 |
if (placeholder !== undefined) {
|
|
|
937 |
html += ' placeholder="' + placeholder + '"';
|
|
|
938 |
}
|
|
|
939 |
|
|
|
940 |
html += ' maxlength="' + (maxLength === undefined ? 255 : maxLength) + '"/>';
|
|
|
941 |
|
|
|
942 |
return html;
|
|
|
943 |
};
|
|
|
944 |
|
|
|
945 |
ns.getNextFieldId = (function (counter) {
|
|
|
946 |
/**
|
|
|
947 |
* Generates a consistent and unique field ID for the given field.
|
|
|
948 |
*
|
|
|
949 |
* @param {Object} field
|
|
|
950 |
* @return {number}
|
|
|
951 |
*/
|
|
|
952 |
return function (field) {
|
|
|
953 |
return 'field-' + field.name.toLowerCase() + '-' + (counter++);
|
|
|
954 |
};
|
|
|
955 |
})(-1);
|
|
|
956 |
|
|
|
957 |
/**
|
|
|
958 |
* Helps generates a consistent description ID across fields.
|
|
|
959 |
*
|
|
|
960 |
* @param {string} id
|
|
|
961 |
* @return {string}
|
|
|
962 |
*/
|
|
|
963 |
ns.getDescriptionId = function (id) {
|
|
|
964 |
return id + '-description';
|
|
|
965 |
};
|
|
|
966 |
|
|
|
967 |
/**
|
|
|
968 |
* Create a label to wrap content in.
|
|
|
969 |
*
|
|
|
970 |
* @param {SemanticField} field
|
|
|
971 |
* @param {String} [content]
|
|
|
972 |
* @param {String} [inputId]
|
|
|
973 |
* @returns {String}
|
|
|
974 |
*/
|
|
|
975 |
ns.createLabel = function (field, content, inputId) {
|
|
|
976 |
// New items can be added next to the label within the flex-wrapper
|
|
|
977 |
var html = '<label class="h5peditor-label-wrapper"';
|
|
|
978 |
|
|
|
979 |
if (inputId !== undefined) {
|
|
|
980 |
html += ' for="' + inputId + '"';
|
|
|
981 |
}
|
|
|
982 |
html+= '>'
|
|
|
983 |
|
|
|
984 |
// Temporary fix for the old version of CoursePresentation's custom editor
|
|
|
985 |
if (field.widget === 'coursepresentation' && field.name === 'presentation') {
|
|
|
986 |
field.label = 0;
|
|
|
987 |
}
|
|
|
988 |
|
|
|
989 |
if (field.label !== 0) {
|
|
|
990 |
html += '<span class="h5peditor-label' + (field.optional ? '' : ' h5peditor-required') + '">' + (field.label === undefined ? field.name : field.label) + '</span>';
|
|
|
991 |
}
|
|
|
992 |
|
|
|
993 |
return html + (content || '') + '</label>';
|
|
|
994 |
};
|
|
|
995 |
|
|
|
996 |
/**
|
|
|
997 |
* Create a description
|
|
|
998 |
* @param {String} description
|
|
|
999 |
* @param {number} [inputId] Used to reference description from input
|
|
|
1000 |
* @returns {string}
|
|
|
1001 |
*/
|
|
|
1002 |
ns.createDescription = function (description, inputId) {
|
|
|
1003 |
var html = '';
|
|
|
1004 |
if (description !== undefined) {
|
|
|
1005 |
html += '<div class="h5peditor-field-description"';
|
|
|
1006 |
if (inputId !== undefined) {
|
|
|
1007 |
html += ' id="' + ns.getDescriptionId(inputId) + '"';
|
|
|
1008 |
}
|
|
|
1009 |
html += '>' + description + '</div>';
|
|
|
1010 |
}
|
|
|
1011 |
return html;
|
|
|
1012 |
};
|
|
|
1013 |
|
|
|
1014 |
/**
|
|
|
1015 |
* Create an important description
|
|
|
1016 |
* @param {Object} importantDescription
|
|
|
1017 |
* @returns {String}
|
|
|
1018 |
*/
|
|
|
1019 |
ns.createImportantDescription = function (importantDescription) {
|
|
|
1020 |
var html = '';
|
|
|
1021 |
|
|
|
1022 |
if (importantDescription !== undefined) {
|
|
|
1023 |
html += '<div class="h5peditor-field-important-description">' +
|
|
|
1024 |
'<div class="important-description-tail">' +
|
|
|
1025 |
'</div>' +
|
|
|
1026 |
'<div class="important-description-close" role="button" tabindex="0" aria-label="' + ns.t('core', 'hideImportantInstructions') + '">' +
|
|
|
1027 |
'<span>' +
|
|
|
1028 |
ns.t('core', 'hide') +
|
|
|
1029 |
'</span>' +
|
|
|
1030 |
'</div>' +
|
|
|
1031 |
'<span class="h5p-info-icon">' +
|
|
|
1032 |
'</span>' +
|
|
|
1033 |
'<span class="important-description-title">' +
|
|
|
1034 |
ns.t('core', 'importantInstructions') +
|
|
|
1035 |
'</span>';
|
|
|
1036 |
|
|
|
1037 |
if (importantDescription.description !== undefined) {
|
|
|
1038 |
html += '<div class="important-description-content">' +
|
|
|
1039 |
importantDescription.description +
|
|
|
1040 |
'</div>';
|
|
|
1041 |
}
|
|
|
1042 |
|
|
|
1043 |
if (importantDescription.example !== undefined) {
|
|
|
1044 |
html += '<div class="important-description-example">' +
|
|
|
1045 |
'<div class="important-description-example-title">' +
|
|
|
1046 |
'<span>' +
|
|
|
1047 |
ns.t('core', 'example') +
|
|
|
1048 |
':</span>' +
|
|
|
1049 |
'</div>' +
|
|
|
1050 |
'<div class="important-description-example-text">' +
|
|
|
1051 |
'<span>' +
|
|
|
1052 |
importantDescription.example +
|
|
|
1053 |
'</span>' +
|
|
|
1054 |
'</div>' +
|
|
|
1055 |
'</div>';
|
|
|
1056 |
}
|
|
|
1057 |
|
|
|
1058 |
html += '</div>' +
|
|
|
1059 |
'<span class="important-description-show" role="button" tabindex="0">' +
|
|
|
1060 |
ns.t('core', 'showImportantInstructions') +
|
|
|
1061 |
'</span><span class="important-description-clear-right"></span>';
|
|
|
1062 |
}
|
|
|
1063 |
|
|
|
1064 |
return html;
|
|
|
1065 |
};
|
|
|
1066 |
|
|
|
1067 |
/**
|
|
|
1068 |
* Bind events to important description
|
|
|
1069 |
* @param {Object} widget
|
|
|
1070 |
* @param {String} fieldName
|
|
|
1071 |
* @param {Object} parent
|
|
|
1072 |
*/
|
|
|
1073 |
ns.bindImportantDescriptionEvents = function (widget, fieldName, parent) {
|
|
|
1074 |
var context;
|
|
|
1075 |
|
|
|
1076 |
if (!widget.field.important) {
|
|
|
1077 |
return;
|
|
|
1078 |
}
|
|
|
1079 |
|
|
|
1080 |
// Generate a context string for using as referance in ex. localStorage.
|
|
|
1081 |
var librarySelector = ns.findLibraryAncestor(parent);
|
|
|
1082 |
if (librarySelector.currentLibrary !== undefined) {
|
|
|
1083 |
var lib = librarySelector.currentLibrary.split(' ')[0];
|
|
|
1084 |
context = (lib + '-' + fieldName).replace(/\.|_/g,'-') + '-important-description-open';
|
|
|
1085 |
}
|
|
|
1086 |
|
|
|
1087 |
// Set first occurance to visible
|
|
|
1088 |
ns.storage.get(context, function (value) {
|
|
|
1089 |
if (value === undefined || value === true) {
|
|
|
1090 |
widget.$item.addClass('important-description-visible');
|
|
|
1091 |
}
|
|
|
1092 |
});
|
|
|
1093 |
|
|
|
1094 |
widget.$item.addClass('has-important-description');
|
|
|
1095 |
|
|
|
1096 |
// Bind events to toggle button and update aria-pressed
|
|
|
1097 |
widget.$item.find('.important-description-show')
|
|
|
1098 |
.click(function () {
|
|
|
1099 |
widget.$item.addClass('important-description-visible');
|
|
|
1100 |
ns.storage.set(context, true);
|
|
|
1101 |
})
|
|
|
1102 |
.keydown(function (event) {
|
|
|
1103 |
if (event.which == 13 || event.which == 32) {
|
|
|
1104 |
ns.$(this).trigger('click');
|
|
|
1105 |
event.preventDefault();
|
|
|
1106 |
}
|
|
|
1107 |
});
|
|
|
1108 |
|
|
|
1109 |
// Bind events to close button and update aria-pressed of toggle button
|
|
|
1110 |
widget.$item.find('.important-description-close')
|
|
|
1111 |
.click(function () {
|
|
|
1112 |
widget.$item.removeClass('important-description-visible');
|
|
|
1113 |
ns.storage.set(context, false);
|
|
|
1114 |
})
|
|
|
1115 |
.keydown(function (event) {
|
|
|
1116 |
if (event.which == 13 || event.which == 32) {
|
|
|
1117 |
ns.$(this).trigger('click');
|
|
|
1118 |
event.preventDefault();
|
|
|
1119 |
}
|
|
|
1120 |
});
|
|
|
1121 |
};
|
|
|
1122 |
|
|
|
1123 |
/**
|
|
|
1124 |
* Generate markup for the copy and paste buttons.
|
|
|
1125 |
*
|
|
|
1126 |
* @returns {string} HTML
|
|
|
1127 |
*/
|
|
|
1128 |
ns.createCopyPasteButtons = function () {
|
|
|
1129 |
return '<div class="h5peditor-copypaste-wrap">' +
|
|
|
1130 |
'<button class="h5peditor-copy-button disabled" title="' + H5PEditor.t('core', 'copyToClipboard') + '" disabled>' + ns.t('core', 'copyButton') + '</button>' +
|
|
|
1131 |
'<button class="h5peditor-paste-button disabled" title="' + H5PEditor.t('core', 'pasteFromClipboard') + '" disabled>' + ns.t('core', 'pasteButton') + '</button>' +
|
|
|
1132 |
'</div><div class="h5peditor-clearfix"></div>';
|
|
|
1133 |
};
|
|
|
1134 |
|
|
|
1135 |
/**
|
|
|
1136 |
* Confirm replace if there is content selected
|
|
|
1137 |
*
|
|
|
1138 |
* @param {string} library Current selected library
|
|
|
1139 |
* @param {number} top Offset
|
|
|
1140 |
* @param {function} next Next callback
|
|
|
1141 |
*/
|
|
|
1142 |
ns.confirmReplace = function (library, top, next) {
|
|
|
1143 |
if (library) {
|
|
|
1144 |
// Confirm changing library
|
|
|
1145 |
var confirmReplace = new H5P.ConfirmationDialog({
|
|
|
1146 |
headerText: H5PEditor.t('core', 'pasteContent'),
|
|
|
1147 |
dialogText: H5PEditor.t('core', 'confirmPasteContent'),
|
|
|
1148 |
confirmText: H5PEditor.t('core', 'confirmPasteButtonText')
|
|
|
1149 |
}).appendTo(document.body);
|
|
|
1150 |
confirmReplace.on('confirmed', next);
|
|
|
1151 |
confirmReplace.show(top);
|
|
|
1152 |
}
|
|
|
1153 |
else {
|
|
|
1154 |
// No need to confirm
|
|
|
1155 |
next();
|
|
|
1156 |
}
|
|
|
1157 |
};
|
|
|
1158 |
|
|
|
1159 |
/**
|
|
|
1160 |
* Check if any errors has been set.
|
|
|
1161 |
*
|
|
|
1162 |
* @param {jQuery} $errors
|
|
|
1163 |
* @param {jQuery} $input
|
|
|
1164 |
* @param {String} value
|
|
|
1165 |
* @returns {mixed}
|
|
|
1166 |
*/
|
|
|
1167 |
ns.checkErrors = function ($errors, $input, value) {
|
|
|
1168 |
if ($errors.children().length) {
|
|
|
1169 |
$input.keyup(function (event) {
|
|
|
1170 |
if (event.keyCode === 9) { // TAB
|
|
|
1171 |
return;
|
|
|
1172 |
}
|
|
|
1173 |
$errors.html('');
|
|
|
1174 |
$input.removeClass('error');
|
|
|
1175 |
$input.unbind('keyup');
|
|
|
1176 |
});
|
|
|
1177 |
|
|
|
1178 |
return false;
|
|
|
1179 |
}
|
|
|
1180 |
return value;
|
|
|
1181 |
};
|
|
|
1182 |
|
|
|
1183 |
/**
|
|
|
1184 |
* @param {object} library
|
|
|
1185 |
* with machineName, majorVersion and minorVersion params
|
|
|
1186 |
* @returns {string}
|
|
|
1187 |
* Concatinated version of the library
|
|
|
1188 |
*/
|
|
|
1189 |
ns.libraryToString = function (library) {
|
|
|
1190 |
return library.name + ' ' + library.majorVersion + '.' + library.minorVersion;
|
|
|
1191 |
};
|
|
|
1192 |
|
|
|
1193 |
/**
|
|
|
1194 |
* TODO: Remove from here, and use from H5P instead(move this to the h5p.js...)
|
|
|
1195 |
*
|
|
|
1196 |
* @param {string} library
|
|
|
1197 |
* library in the format machineName majorVersion.minorVersion
|
|
|
1198 |
* @returns
|
|
|
1199 |
* library as an object with machineName, majorVersion and minorVersion properties
|
|
|
1200 |
* return false if the library parameter is invalid
|
|
|
1201 |
*/
|
|
|
1202 |
ns.libraryFromString = function (library) {
|
|
|
1203 |
var regExp = /(.+)\s(\d+)\.(\d+)$/g;
|
|
|
1204 |
var res = regExp.exec(library);
|
|
|
1205 |
if (res !== null) {
|
|
|
1206 |
return {
|
|
|
1207 |
'machineName': res[1],
|
|
|
1208 |
'majorVersion': res[2],
|
|
|
1209 |
'minorVersion': res[3]
|
|
|
1210 |
};
|
|
|
1211 |
}
|
|
|
1212 |
else {
|
|
|
1213 |
H5P.error('Invalid überName');
|
|
|
1214 |
return false;
|
|
|
1215 |
}
|
|
|
1216 |
};
|
|
|
1217 |
|
|
|
1218 |
/**
|
|
|
1219 |
* Helper function for detecting field widget.
|
|
|
1220 |
*
|
|
|
1221 |
* @param {Object} field
|
|
|
1222 |
* @returns {String} Widget name
|
|
|
1223 |
*/
|
|
|
1224 |
ns.getWidgetName = function (field) {
|
|
|
1225 |
return (field.widget === undefined ? field.type : field.widget);
|
|
|
1226 |
};
|
|
|
1227 |
|
|
|
1228 |
/**
|
|
|
1229 |
* Mimics how php's htmlspecialchars works (the way we uses it)
|
|
|
1230 |
*/
|
|
|
1231 |
ns.htmlspecialchars = function (string) {
|
|
|
1232 |
return string.toString().replace(/</g, '<').replace(/>/g, '>').replace(/'/g, ''').replace(/"/g, '"');
|
|
|
1233 |
};
|
|
|
1234 |
|
|
|
1235 |
/**
|
|
|
1236 |
* Makes it easier to add consistent buttons across the editor widget.
|
|
|
1237 |
*
|
|
|
1238 |
* @param {string} id Typical CSS class format
|
|
|
1239 |
* @param {string} title Human readable format
|
|
|
1240 |
* @param {function} handler Action handler when triggered
|
|
|
1241 |
* @param {boolean} [displayTitle=false] Show button with text
|
|
|
1242 |
* @return {H5P.jQuery}
|
|
|
1243 |
*/
|
|
|
1244 |
ns.createButton = function (id, title, handler, displayTitle) {
|
|
|
1245 |
var options = {
|
|
|
1246 |
class: 'h5peditor-button ' + (displayTitle ? 'h5peditor-button-textual ' : '') + id,
|
|
|
1247 |
role: 'button',
|
|
|
1248 |
tabIndex: 0,
|
|
|
1249 |
'aria-disabled': 'false',
|
|
|
1250 |
on: {
|
|
|
1251 |
click: function () {
|
|
|
1252 |
handler.call(this);
|
|
|
1253 |
},
|
|
|
1254 |
keydown: function (event) {
|
|
|
1255 |
switch (event.which) {
|
|
|
1256 |
case 13: // Enter
|
|
|
1257 |
case 32: // Space
|
|
|
1258 |
handler.call(this);
|
|
|
1259 |
event.preventDefault();
|
|
|
1260 |
}
|
|
|
1261 |
}
|
|
|
1262 |
}
|
|
|
1263 |
};
|
|
|
1264 |
|
|
|
1265 |
// Determine if we're a icon only button or have a textual label
|
|
|
1266 |
options[displayTitle ? 'html' : 'aria-label'] = title;
|
|
|
1267 |
|
|
|
1268 |
return ns.$('<div/>', options);
|
|
|
1269 |
};
|
|
|
1270 |
|
|
|
1271 |
/**
|
|
|
1272 |
* Check if the current library is entitled for the metadata button. True by default.
|
|
|
1273 |
*
|
|
|
1274 |
* It will probably be okay to remove this check at some point in time when
|
|
|
1275 |
* the majority of content types and plugins have been updated to a version
|
|
|
1276 |
* that supports the metadata system.
|
|
|
1277 |
*
|
|
|
1278 |
* @param {string} library - Current library.
|
|
|
1279 |
* @return {boolean} True, if form should have the metadata button.
|
|
|
1280 |
*/
|
|
|
1281 |
ns.enableMetadata = function (library) {
|
|
|
1282 |
|
|
|
1283 |
if (!library || typeof library !== 'string') {
|
|
|
1284 |
return false;
|
|
|
1285 |
}
|
|
|
1286 |
|
|
|
1287 |
library = H5P.libraryFromString(library);
|
|
|
1288 |
if (!library) {
|
|
|
1289 |
return false;
|
|
|
1290 |
}
|
|
|
1291 |
|
|
|
1292 |
// This list holds all old libraries (/older versions implicitly) that need an update for metadata
|
|
|
1293 |
const blackList = [
|
|
|
1294 |
// Should never have metadata because it does not make sense
|
|
|
1295 |
'H5P.IVHotspot 1.2',
|
|
|
1296 |
'H5P.Link 1.3',
|
|
|
1297 |
'H5P.TwitterUserFeed 1.0',
|
|
|
1298 |
'H5P.GoToQuestion 1.3',
|
|
|
1299 |
'H5P.Nil 1.0',
|
|
|
1300 |
|
|
|
1301 |
// Copyright information moved to metadata
|
|
|
1302 |
'H5P.Audio 1.2',
|
|
|
1303 |
'H5P.Video 1.4',
|
|
|
1304 |
'H5P.Image 1.0',
|
|
|
1305 |
|
|
|
1306 |
// Title moved to metadata
|
|
|
1307 |
'H5P.DocumentExportPage 1.3',
|
|
|
1308 |
'H5P.ExportableTextArea 1.2',
|
|
|
1309 |
'H5P.GoalsAssessmentPage 1.3',
|
|
|
1310 |
'H5P.GoalsPage 1.4',
|
|
|
1311 |
'H5P.StandardPage 1.3',
|
|
|
1312 |
'H5P.DragQuestion 1.12',
|
|
|
1313 |
'H5P.ImageHotspotQuestion 1.7',
|
|
|
1314 |
|
|
|
1315 |
// Custom editor changed
|
|
|
1316 |
'H5P.CoursePresentation 1.19',
|
|
|
1317 |
'H5P.InteractiveVideo 1.19'
|
|
|
1318 |
];
|
|
|
1319 |
|
|
|
1320 |
let block = blackList.filter(function (item) {
|
|
|
1321 |
// + ' ' makes sure to avoid partial matches
|
|
|
1322 |
return item.indexOf(library.machineName + ' ') !== -1;
|
|
|
1323 |
});
|
|
|
1324 |
if (block.length === 0) {
|
|
|
1325 |
return true;
|
|
|
1326 |
}
|
|
|
1327 |
|
|
|
1328 |
block = H5P.libraryFromString(block[0]);
|
|
|
1329 |
if (library.majorVersion > block.majorVersion || library.majorVersion === block.majorVersion && library.minorVersion > block.minorVersion) {
|
|
|
1330 |
return true;
|
|
|
1331 |
}
|
|
|
1332 |
|
|
|
1333 |
return false;
|
|
|
1334 |
};
|
|
|
1335 |
|
|
|
1336 |
// Backwards compatibilty
|
|
|
1337 |
ns.attachToastTo = H5P.attachToastTo;
|
|
|
1338 |
|
|
|
1339 |
/**
|
|
|
1340 |
* Check if clipboard can be pasted.
|
|
|
1341 |
*
|
|
|
1342 |
* @param {Object} clipboard Clipboard data.
|
|
|
1343 |
* @param {Object} libs Libraries to compare against.
|
|
|
1344 |
* @return {boolean} True, if content can be pasted.
|
|
|
1345 |
*/
|
|
|
1346 |
ns.canPaste = function (clipboard, libs) {
|
|
|
1347 |
return (this.canPastePlus(clipboard, libs)).canPaste;
|
|
|
1348 |
};
|
|
|
1349 |
|
|
|
1350 |
/**
|
|
|
1351 |
* Check if clipboard can be pasted and give reason if not.
|
|
|
1352 |
*
|
|
|
1353 |
* @param {Object} clipboard Clipboard data.
|
|
|
1354 |
* @param {Object} libs Libraries to compare against.
|
|
|
1355 |
* @return {Object} Results. {canPaste: boolean, reason: string, description: string}.
|
|
|
1356 |
*/
|
|
|
1357 |
ns.canPastePlus = function (clipboard, libs) {
|
|
|
1358 |
// Clipboard is empty
|
|
|
1359 |
if (!clipboard || !clipboard.generic) {
|
|
|
1360 |
return {
|
|
|
1361 |
canPaste: false,
|
|
|
1362 |
reason: 'pasteNoContent',
|
|
|
1363 |
description: ns.t('core', 'pasteNoContent')
|
|
|
1364 |
};
|
|
|
1365 |
}
|
|
|
1366 |
|
|
|
1367 |
// No libraries to compare to
|
|
|
1368 |
if (libs === undefined) {
|
|
|
1369 |
return {
|
|
|
1370 |
canPaste: false,
|
|
|
1371 |
reason: 'pasteError',
|
|
|
1372 |
description: ns.t('core', 'pasteError')
|
|
|
1373 |
};
|
|
|
1374 |
}
|
|
|
1375 |
|
|
|
1376 |
// Translate Hub format to common library format
|
|
|
1377 |
if (libs.libraries !== undefined) {
|
|
|
1378 |
libs = libs.libraries;
|
|
|
1379 |
libs.forEach(function (lib) {
|
|
|
1380 |
lib.name = lib.machineName;
|
|
|
1381 |
lib.majorVersion = lib.localMajorVersion;
|
|
|
1382 |
lib.minorVersion = lib.localMinorVersion;
|
|
|
1383 |
});
|
|
|
1384 |
}
|
|
|
1385 |
|
|
|
1386 |
// Check if clipboard library type is available
|
|
|
1387 |
const machineNameClip = clipboard.generic.library.split(' ')[0];
|
|
|
1388 |
let candidates = libs.filter(function (library) {
|
|
|
1389 |
return library.name === machineNameClip;
|
|
|
1390 |
});
|
|
|
1391 |
if (candidates.length === 0) {
|
|
|
1392 |
return {
|
|
|
1393 |
canPaste: false,
|
|
|
1394 |
reason: 'pasteContentNotSupported',
|
|
|
1395 |
description: ns.t('core', 'pasteContentNotSupported')
|
|
|
1396 |
};
|
|
|
1397 |
}
|
|
|
1398 |
|
|
|
1399 |
// Check if clipboard library version is available
|
|
|
1400 |
const versionClip = clipboard.generic.library.split(' ')[1];
|
|
|
1401 |
for (let i = 0; i < candidates.length; i++) {
|
|
|
1402 |
if (candidates[i].majorVersion + '.' + candidates[i].minorVersion === versionClip) {
|
|
|
1403 |
if (candidates[i].restricted !== true) {
|
|
|
1404 |
return {
|
|
|
1405 |
canPaste: true
|
|
|
1406 |
};
|
|
|
1407 |
}
|
|
|
1408 |
else {
|
|
|
1409 |
return {
|
|
|
1410 |
canPaste: false,
|
|
|
1411 |
reason: 'pasteContentRestricted',
|
|
|
1412 |
description: ns.t('core', 'pasteContentRestricted')
|
|
|
1413 |
};
|
|
|
1414 |
}
|
|
|
1415 |
}
|
|
|
1416 |
}
|
|
|
1417 |
|
|
|
1418 |
// Sort remaining candidates by version number
|
|
|
1419 |
candidates = candidates
|
|
|
1420 |
.map(function (candidate) {
|
|
|
1421 |
return '' + candidate.majorVersion + '.' + candidate.minorVersion;
|
|
|
1422 |
})
|
|
|
1423 |
.map(function (candidate) {
|
|
|
1424 |
return candidate.replace(/\d+/g, function (d) {
|
|
|
1425 |
return +d + 1000;
|
|
|
1426 |
});
|
|
|
1427 |
})
|
|
|
1428 |
.sort()
|
|
|
1429 |
.map(function (candidate) {
|
|
|
1430 |
return candidate.replace(/\d+/g, function (d) {
|
|
|
1431 |
return +d - 1000;
|
|
|
1432 |
});
|
|
|
1433 |
});
|
|
|
1434 |
|
|
|
1435 |
// Clipboard library is newer than latest available local library
|
|
|
1436 |
const candidateMax = candidates.slice(-1)[0];
|
|
|
1437 |
if (+candidateMax.split('.')[0] < +versionClip.split('.')[0] ||
|
|
|
1438 |
(+candidateMax.split('.')[0] === +versionClip.split('.')[0] &&
|
|
|
1439 |
+candidateMax.split('.')[1] < +versionClip.split('.')[1])) {
|
|
|
1440 |
return {
|
|
|
1441 |
canPaste: false,
|
|
|
1442 |
reason: 'pasteTooNew',
|
|
|
1443 |
description: ns.t('core', 'pasteTooNew', {
|
|
|
1444 |
':clip': versionClip,
|
|
|
1445 |
':local': candidateMax
|
|
|
1446 |
})
|
|
|
1447 |
};
|
|
|
1448 |
}
|
|
|
1449 |
|
|
|
1450 |
// Clipboard library is older than latest available local library
|
|
|
1451 |
const candidateMin = candidates.slice(0, 1)[0];
|
|
|
1452 |
if (+candidateMin.split('.')[0] > +versionClip.split('.')[0] ||
|
|
|
1453 |
(+candidateMin.split('.')[0] === +versionClip.split('.')[0] &&
|
|
|
1454 |
+candidateMin.split('.')[1] > +versionClip.split('.')[1])) {
|
|
|
1455 |
return {
|
|
|
1456 |
canPaste: false,
|
|
|
1457 |
reason: 'pasteTooOld',
|
|
|
1458 |
description: ns.t('core', 'pasteTooOld', {
|
|
|
1459 |
':clip': versionClip,
|
|
|
1460 |
':local': candidateMin
|
|
|
1461 |
})
|
|
|
1462 |
};
|
|
|
1463 |
}
|
|
|
1464 |
|
|
|
1465 |
return {
|
|
|
1466 |
canPaste: false,
|
|
|
1467 |
reason: 'pasteError',
|
|
|
1468 |
description: ns.t('core', 'pasteError')
|
|
|
1469 |
};
|
|
|
1470 |
};
|
|
|
1471 |
|
|
|
1472 |
// Factory for creating storage instance
|
|
|
1473 |
ns.storage = (function () {
|
|
|
1474 |
var instance = {
|
|
|
1475 |
get: function (key, next) {
|
|
|
1476 |
var value;
|
|
|
1477 |
|
|
|
1478 |
// Get value from browser storage
|
|
|
1479 |
if (window.localStorage !== undefined) {
|
|
|
1480 |
value = !!window.localStorage.getItem(key);
|
|
|
1481 |
}
|
|
|
1482 |
|
|
|
1483 |
// Try to get a better value from user data storage
|
|
|
1484 |
try {
|
|
|
1485 |
H5P.getUserData(0, key, function (err, result) {
|
|
|
1486 |
if (!err) {
|
|
|
1487 |
value = result;
|
|
|
1488 |
}
|
|
|
1489 |
next(value);
|
|
|
1490 |
});
|
|
|
1491 |
}
|
|
|
1492 |
catch (err) {
|
|
|
1493 |
next(value);
|
|
|
1494 |
}
|
|
|
1495 |
},
|
|
|
1496 |
set: function (key, value) {
|
|
|
1497 |
|
|
|
1498 |
// Store in browser
|
|
|
1499 |
if (window.localStorage !== undefined) {
|
|
|
1500 |
window.localStorage.setItem(key, value);
|
|
|
1501 |
}
|
|
|
1502 |
|
|
|
1503 |
// Try to store in user data storage
|
|
|
1504 |
try {
|
|
|
1505 |
H5P.setUserData(0, key, value);
|
|
|
1506 |
}
|
|
|
1507 |
catch (err) { /*Intentionally left empty*/ }
|
|
|
1508 |
}
|
|
|
1509 |
};
|
|
|
1510 |
return instance;
|
|
|
1511 |
})();
|
|
|
1512 |
|
|
|
1513 |
/**
|
|
|
1514 |
* Small helper class for library data.
|
|
|
1515 |
*
|
|
|
1516 |
* @class
|
|
|
1517 |
* @param {string} nameVersionString
|
|
|
1518 |
*/
|
|
|
1519 |
ns.ContentType = function ContentType(nameVersionString) {
|
|
|
1520 |
const libraryNameSplit = nameVersionString.split(' ');
|
|
|
1521 |
const libraryVersionSplit = libraryNameSplit[1].split('.');
|
|
|
1522 |
|
|
|
1523 |
this.machineName = libraryNameSplit[0];
|
|
|
1524 |
this.majorVersion = libraryVersionSplit[0];
|
|
|
1525 |
this.minorVersion = libraryVersionSplit[1];
|
|
|
1526 |
};
|
|
|
1527 |
|
|
|
1528 |
/**
|
|
|
1529 |
* Look for the best possible upgrade for the given library
|
|
|
1530 |
*
|
|
|
1531 |
* @param {ns.ContentType} library
|
|
|
1532 |
* @param {Array} libraries Where to look
|
|
|
1533 |
*/
|
|
|
1534 |
ns.ContentType.getPossibleUpgrade = function (library, libraries) {
|
|
|
1535 |
let possibleUpgrade;
|
|
|
1536 |
|
|
|
1537 |
for (let i = 0; i < libraries.length; i++) {
|
|
|
1538 |
const candiate = libraries[i];
|
|
|
1539 |
if (candiate.installed !== false && ns.ContentType.hasSameName(candiate, library) && ns.ContentType.isHigherVersion(candiate, library)) {
|
|
|
1540 |
|
|
|
1541 |
// Check if the upgrade is better than the previous upgrade we found
|
|
|
1542 |
if (!possibleUpgrade || ns.ContentType.isHigherVersion(candiate, possibleUpgrade)) {
|
|
|
1543 |
possibleUpgrade = candiate;
|
|
|
1544 |
}
|
|
|
1545 |
}
|
|
|
1546 |
}
|
|
|
1547 |
|
|
|
1548 |
return possibleUpgrade;
|
|
|
1549 |
};
|
|
|
1550 |
|
|
|
1551 |
/**
|
|
|
1552 |
* Check if candiate is a higher version than original.
|
|
|
1553 |
*
|
|
|
1554 |
* @param {Object} candiate Library object
|
|
|
1555 |
* @param {Object} original Library object
|
|
|
1556 |
* @returns {boolean}
|
|
|
1557 |
*/
|
|
|
1558 |
ns.ContentType.isHigherVersion = function (candiate, original) {
|
|
|
1559 |
return (ns.ContentType.getMajorVersion(candiate) > ns.ContentType.getMajorVersion(original) ||
|
|
|
1560 |
(ns.ContentType.getMajorVersion(candiate) == ns.ContentType.getMajorVersion(original) &&
|
|
|
1561 |
ns.ContentType.getMinorVersion(candiate) > ns.ContentType.getMinorVersion(original)));
|
|
|
1562 |
};
|
|
|
1563 |
|
|
|
1564 |
/**
|
|
|
1565 |
* Check if candiate has same name as original.
|
|
|
1566 |
*
|
|
|
1567 |
* @param {Object} candiate Library object
|
|
|
1568 |
* @param {Object} original Library object
|
|
|
1569 |
* @returns {boolean}
|
|
|
1570 |
*/
|
|
|
1571 |
ns.ContentType.hasSameName = function (candiate, original) {
|
|
|
1572 |
return (ns.ContentType.getName(candiate) === ns.ContentType.getName(original));
|
|
|
1573 |
};
|
|
|
1574 |
|
|
|
1575 |
/**
|
|
|
1576 |
* Check if candiate has same name as original.
|
|
|
1577 |
*
|
|
|
1578 |
* @param {Object} candiate Library object
|
|
|
1579 |
* @param {Object} original Library object
|
|
|
1580 |
* @returns {string}
|
|
|
1581 |
*/
|
|
|
1582 |
ns.ContentType.getNameVersionString = function (library) {
|
|
|
1583 |
return ns.ContentType.getName(library) + ' ' + ns.ContentType.getMajorVersion(library) + '.' + ns.ContentType.getMinorVersion(library);
|
|
|
1584 |
};
|
|
|
1585 |
|
|
|
1586 |
/**
|
|
|
1587 |
* Get the major version from a library object.
|
|
|
1588 |
*
|
|
|
1589 |
* @param {Object} library
|
|
|
1590 |
* @returns {number}
|
|
|
1591 |
*/
|
|
|
1592 |
ns.ContentType.getMajorVersion = function (library) {
|
|
|
1593 |
return parseInt((library.localMajorVersion !== undefined ? library.localMajorVersion : library.majorVersion));
|
|
|
1594 |
};
|
|
|
1595 |
|
|
|
1596 |
/**
|
|
|
1597 |
* Get the minor version from a library object.
|
|
|
1598 |
*
|
|
|
1599 |
* @param {Object} library
|
|
|
1600 |
* @returns {number}
|
|
|
1601 |
*/
|
|
|
1602 |
ns.ContentType.getMinorVersion = function (library) {
|
|
|
1603 |
return parseInt((library.localMinorVersion !== undefined ? library.localMinorVersion : library.minorVersion));
|
|
|
1604 |
};
|
|
|
1605 |
|
|
|
1606 |
/**
|
|
|
1607 |
* Get the name from a library object.
|
|
|
1608 |
*
|
|
|
1609 |
* @param {Object} library
|
|
|
1610 |
* @returns {string}
|
|
|
1611 |
*/
|
|
|
1612 |
ns.ContentType.getName = function (library) {
|
|
|
1613 |
return (library.machineName !== undefined ? library.machineName : library.name);
|
|
|
1614 |
};
|
|
|
1615 |
|
|
|
1616 |
|
|
|
1617 |
ns.upgradeContent = (function () {
|
|
|
1618 |
|
|
|
1619 |
/**
|
|
|
1620 |
* A wrapper for loading library data for the content upgrade scripts.
|
|
|
1621 |
*
|
|
|
1622 |
* @param {string} name Library name
|
|
|
1623 |
* @param {H5P.Version} version
|
|
|
1624 |
* @param {Function} next Callback
|
|
|
1625 |
*/
|
|
|
1626 |
const loadLibrary = function (name, version, next) {
|
|
|
1627 |
const library = name + ' ' + version.major + '.' + version.minor;
|
|
|
1628 |
ns.loadLibrary(library, function () {
|
|
|
1629 |
next(null, ns.libraryCache[library]);
|
|
|
1630 |
});
|
|
|
1631 |
};
|
|
|
1632 |
|
|
|
1633 |
return function contentUpgrade(fromLibrary, toLibrary, parameters, done) {
|
|
|
1634 |
ns.loadJs(H5PIntegration.libraryUrl + '/h5p-version.js' + H5PIntegration.pluginCacheBuster, function (err) {
|
|
|
1635 |
ns.loadJs(H5PIntegration.libraryUrl + '/h5p-content-upgrade-process.js' + H5PIntegration.pluginCacheBuster, function (err) {
|
|
|
1636 |
// TODO: Avoid stringify the parameters
|
|
|
1637 |
new H5P.ContentUpgradeProcess(ns.ContentType.getName(fromLibrary), new H5P.Version(fromLibrary), new H5P.Version(toLibrary), JSON.stringify(parameters), 1, function (name, version, next) {
|
|
|
1638 |
loadLibrary(name, version, function (err, library) {
|
|
|
1639 |
if (library.upgradesScript) {
|
|
|
1640 |
ns.loadJs(library.upgradesScript, function (err) {
|
|
|
1641 |
if (err) {
|
|
|
1642 |
err = 'Error loading upgrades ' + name + ' ' + version;
|
|
|
1643 |
}
|
|
|
1644 |
next(err, library);
|
|
|
1645 |
});
|
|
|
1646 |
}
|
|
|
1647 |
else {
|
|
|
1648 |
next(null, library);
|
|
|
1649 |
}
|
|
|
1650 |
});
|
|
|
1651 |
|
|
|
1652 |
}, function (err, result) {
|
|
|
1653 |
if (err) {
|
|
|
1654 |
let header = 'Failed';
|
|
|
1655 |
let message = 'Could not upgrade content';
|
|
|
1656 |
switch (err.type) {
|
|
|
1657 |
case 'errorTooHighVersion':
|
|
|
1658 |
message += ': ' + ns.t('core', 'errorTooHighVersion', {'%used': err.used, '%supported': err.supported});
|
|
|
1659 |
break;
|
|
|
1660 |
|
|
|
1661 |
case 'errorNotSupported':
|
|
|
1662 |
message += ': ' + ns.t('core', 'errorNotSupported', {'%used': err.used});
|
|
|
1663 |
break;
|
|
|
1664 |
|
|
|
1665 |
case 'errorParamsBroken':
|
|
|
1666 |
message += ': ' + ns.t('core', 'errorParamsBroken');
|
|
|
1667 |
break;
|
|
|
1668 |
|
|
|
1669 |
case 'libraryMissing':
|
|
|
1670 |
message += ': ' + ns.t('core', 'libraryMissing', {'%lib': err.library});
|
|
|
1671 |
break;
|
|
|
1672 |
|
|
|
1673 |
case 'scriptMissing':
|
|
|
1674 |
message += ': ' + ns.t('core', 'scriptMissing', {'%lib': err.library});
|
|
|
1675 |
break;
|
|
|
1676 |
}
|
|
|
1677 |
|
|
|
1678 |
var confirmErrorDialog = new H5P.ConfirmationDialog({
|
|
|
1679 |
headerText: header,
|
|
|
1680 |
dialogText: message,
|
|
|
1681 |
confirmText: 'Continue'
|
|
|
1682 |
}).appendTo(document.body);
|
|
|
1683 |
confirmErrorDialog.show();
|
|
|
1684 |
}
|
|
|
1685 |
done(err, result);
|
|
|
1686 |
});
|
|
|
1687 |
});
|
|
|
1688 |
});
|
|
|
1689 |
};
|
|
|
1690 |
})();
|
|
|
1691 |
|
|
|
1692 |
// List of language code mappings used by the editor
|
|
|
1693 |
ns.supportedLanguages = {
|
|
|
1694 |
'aa': 'Afar',
|
|
|
1695 |
'ab': 'Abkhazian (аҧсуа бызшәа)',
|
|
|
1696 |
'ae': 'Avestan',
|
|
|
1697 |
'af': 'Afrikaans',
|
|
|
1698 |
'ak': 'Akan',
|
|
|
1699 |
'am': 'Amharic (አማርኛ)',
|
|
|
1700 |
'ar': 'Arabic (العربية)',
|
|
|
1701 |
'as': 'Assamese',
|
|
|
1702 |
'ast': 'Asturian',
|
|
|
1703 |
'av': 'Avar',
|
|
|
1704 |
'ay': 'Aymara',
|
|
|
1705 |
'az': 'Azerbaijani (azərbaycan)',
|
|
|
1706 |
'ba': 'Bashkir',
|
|
|
1707 |
'be': 'Belarusian (Беларуская)',
|
|
|
1708 |
'bg': 'Bulgarian (Български)',
|
|
|
1709 |
'bh': 'Bihari',
|
|
|
1710 |
'bi': 'Bislama',
|
|
|
1711 |
'bm': 'Bambara (Bamanankan)',
|
|
|
1712 |
'bn': 'Bengali',
|
|
|
1713 |
'bo': 'Tibetan',
|
|
|
1714 |
'br': 'Breton',
|
|
|
1715 |
'bs': 'Bosnian (Bosanski)',
|
|
|
1716 |
'ca': 'Catalan (Català)',
|
|
|
1717 |
'ce': 'Chechen',
|
|
|
1718 |
'ch': 'Chamorro',
|
|
|
1719 |
'co': 'Corsican',
|
|
|
1720 |
'cr': 'Cree',
|
|
|
1721 |
'cs': 'Czech (Čeština)',
|
|
|
1722 |
'cu': 'Old Slavonic',
|
|
|
1723 |
'cv': 'Chuvash',
|
|
|
1724 |
'cy': 'Welsh (Cymraeg)',
|
|
|
1725 |
'da': 'Danish (Dansk)',
|
|
|
1726 |
'de': 'German (Deutsch)',
|
|
|
1727 |
'dv': 'Maldivian',
|
|
|
1728 |
'dz': 'Bhutani',
|
|
|
1729 |
'ee': 'Ewe (Ɛʋɛ)',
|
|
|
1730 |
'el': 'Greek (Ελληνικά)',
|
|
|
1731 |
'en': 'English',
|
|
|
1732 |
'en-gb': 'English, British',
|
|
|
1733 |
'eo': 'Esperanto',
|
|
|
1734 |
'es': 'Spanish (Español)',
|
|
|
1735 |
'es-mx': 'Spanish, Mexican',
|
|
|
1736 |
'et': 'Estonian (Eesti)',
|
|
|
1737 |
'eu': 'Basque (Euskera)',
|
|
|
1738 |
'fa': 'Persian (فارسی)',
|
|
|
1739 |
'ff': 'Fulah (Fulfulde)',
|
|
|
1740 |
'fi': 'Finnish (Suomi)',
|
|
|
1741 |
'fil': 'Filipino',
|
|
|
1742 |
'fj': 'Fiji',
|
|
|
1743 |
'fo': 'Faeroese',
|
|
|
1744 |
'fr': 'French (Français)',
|
|
|
1745 |
'fy': 'Frisian (Frysk)',
|
|
|
1746 |
'ga': 'Irish (Gaeilge)',
|
|
|
1747 |
'gd': 'Scots Gaelic',
|
|
|
1748 |
'gl': 'Galician (Galego)',
|
|
|
1749 |
'gn': 'Guarani',
|
|
|
1750 |
'gsw-berne': 'Swiss German',
|
|
|
1751 |
'gu': 'Gujarati',
|
|
|
1752 |
'gv': 'Manx',
|
|
|
1753 |
'ha': 'Hausa',
|
|
|
1754 |
'he': 'Hebrew (עברית)',
|
|
|
1755 |
'hi': 'Hindi (हिन्दी)',
|
|
|
1756 |
'ho': 'Hiri Motu',
|
|
|
1757 |
'hr': 'Croatian (Hrvatski)',
|
|
|
1758 |
'hsb': 'Upper Sorbian (hornjoserbšćina)',
|
|
|
1759 |
'ht': 'Haitian Creole',
|
|
|
1760 |
'hu': 'Hungarian (Magyar)',
|
|
|
1761 |
'hy': 'Armenian (Õ€Õ¡ÕµÕ¥Ö€Õ¥Õ¶)',
|
|
|
1762 |
'hz': 'Herero',
|
|
|
1763 |
'ia': 'Interlingua',
|
|
|
1764 |
'id': 'Indonesian (Bahasa Indonesia)',
|
|
|
1765 |
'ie': 'Interlingue',
|
|
|
1766 |
'ig': 'Igbo',
|
|
|
1767 |
'ik': 'Inupiak',
|
|
|
1768 |
'is': 'Icelandic (Íslenska)',
|
|
|
1769 |
'it': 'Italian (Italiano)',
|
|
|
1770 |
'iu': 'Inuktitut',
|
|
|
1771 |
'ja': 'Japanese (日本語)',
|
|
|
1772 |
'jv': 'Javanese',
|
|
|
1773 |
'ka': 'Georgian',
|
|
|
1774 |
'kg': 'Kongo',
|
|
|
1775 |
'ki': 'Kikuyu',
|
|
|
1776 |
'kj': 'Kwanyama',
|
|
|
1777 |
'kk': 'Kazakh (Қазақ)',
|
|
|
1778 |
'kl': 'Greenlandic',
|
|
|
1779 |
'km': 'Cambodian',
|
|
|
1780 |
'kn': 'Kannada (ಕನà³à²¨à²¡)',
|
|
|
1781 |
'ko': 'Korean (한국어)',
|
|
|
1782 |
'kr': 'Kanuri',
|
|
|
1783 |
'ks': 'Kashmiri',
|
|
|
1784 |
'ku': 'Kurdish (Kurdî)',
|
|
|
1785 |
'kv': 'Komi',
|
|
|
1786 |
'kw': 'Cornish',
|
|
|
1787 |
'ky': 'Kyrgyz (Кыргызча)',
|
|
|
1788 |
'la': 'Latin (Latina)',
|
|
|
1789 |
'lb': 'Luxembourgish',
|
|
|
1790 |
'lg': 'Luganda',
|
|
|
1791 |
'ln': 'Lingala',
|
|
|
1792 |
'lo': 'Laothian',
|
|
|
1793 |
'lt': 'Lithuanian (Lietuvių)',
|
|
|
1794 |
'lv': 'Latvian (Latviešu)',
|
|
|
1795 |
'mg': 'Malagasy',
|
|
|
1796 |
'mh': 'Marshallese',
|
|
|
1797 |
'mi': 'Māori',
|
|
|
1798 |
'mk': 'Macedonian (Македонски)',
|
|
|
1799 |
'ml': 'Malayalam (മലയാളം)',
|
|
|
1800 |
'mn': 'Mongolian',
|
|
|
1801 |
'mo': 'Moldavian',
|
|
|
1802 |
'mr': 'Marathi',
|
|
|
1803 |
'ms': 'Malay (Bahasa Melayu)',
|
|
|
1804 |
'mt': 'Maltese (Malti)',
|
|
|
1805 |
'my': 'Burmese',
|
|
|
1806 |
'na': 'Nauru',
|
|
|
1807 |
'nd': 'North Ndebele',
|
|
|
1808 |
'ne': 'Nepali',
|
|
|
1809 |
'ng': 'Ndonga',
|
|
|
1810 |
'nl': 'Dutch (Nederlands)',
|
|
|
1811 |
'nb': 'Norwegian Bokmål (Bokmål)',
|
|
|
1812 |
'nn': 'Norwegian Nynorsk (Nynorsk)',
|
|
|
1813 |
'nr': 'South Ndebele',
|
|
|
1814 |
'nv': 'Navajo',
|
|
|
1815 |
'ny': 'Chichewa',
|
|
|
1816 |
'oc': 'Occitan',
|
|
|
1817 |
'om': 'Oromo',
|
|
|
1818 |
'or': 'Oriya',
|
|
|
1819 |
'os': 'Ossetian',
|
|
|
1820 |
'pa': 'Punjabi',
|
|
|
1821 |
'pi': 'Pali',
|
|
|
1822 |
'pl': 'Polish (Polski)',
|
|
|
1823 |
'ps': 'Pashto (پښتو)',
|
|
|
1824 |
'pt': 'Portuguese, International',
|
|
|
1825 |
'pt-pt': 'Portuguese, Portugal (Português)',
|
|
|
1826 |
'pt-br': 'Portuguese, Brazil (Português)',
|
|
|
1827 |
'qu': 'Quechua',
|
|
|
1828 |
'rm': 'Rhaeto-Romance',
|
|
|
1829 |
'rn': 'Kirundi',
|
|
|
1830 |
'ro': 'Romanian (Română)',
|
|
|
1831 |
'ru': 'Russian (Русский)',
|
|
|
1832 |
'rw': 'Kinyarwanda',
|
|
|
1833 |
'sa': 'Sanskrit',
|
|
|
1834 |
'sc': 'Sardinian',
|
|
|
1835 |
'sco': 'Scots',
|
|
|
1836 |
'sd': 'Sindhi',
|
|
|
1837 |
'se': 'Northern Sami',
|
|
|
1838 |
'sg': 'Sango',
|
|
|
1839 |
'sh': 'Serbo-Croatian',
|
|
|
1840 |
'si': 'Sinhala (සිංහල)',
|
|
|
1841 |
'sk': 'Slovak (Slovenčina)',
|
|
|
1842 |
'sl': 'Slovenian (Slovenščina)',
|
|
|
1843 |
'sm': 'Samoan',
|
|
|
1844 |
'sma': 'Sámi (Southern)',
|
|
|
1845 |
'sme': 'Sámi (Northern)',
|
|
|
1846 |
'smj': 'Sámi (Lule)',
|
|
|
1847 |
'sn': 'Shona',
|
|
|
1848 |
'so': 'Somali',
|
|
|
1849 |
'sq': 'Albanian (Shqip)',
|
|
|
1850 |
'sr': 'Serbian (Српски)',
|
|
|
1851 |
'ss': 'Siswati',
|
|
|
1852 |
'st': 'Sesotho',
|
|
|
1853 |
'su': 'Sudanese',
|
|
|
1854 |
'sv': 'Swedish (Svenska)',
|
|
|
1855 |
'sw': 'Swahili (Kiswahili)',
|
|
|
1856 |
'ta': 'Tamil (தமிழà¯)',
|
|
|
1857 |
'te': 'Telugu (తెలà±à°—à±)',
|
|
|
1858 |
'tg': 'Tajik',
|
|
|
1859 |
'th': 'Thai (ภาษาไทย)',
|
|
|
1860 |
'ti': 'Tigrinya',
|
|
|
1861 |
'tk': 'Turkmen',
|
|
|
1862 |
'tl': 'Tagalog',
|
|
|
1863 |
'tn': 'Setswana',
|
|
|
1864 |
'to': 'Tonga',
|
|
|
1865 |
'tr': 'Turkish (Türkçe)',
|
|
|
1866 |
'ts': 'Tsonga',
|
|
|
1867 |
'tt': 'Tatar (Tatarça)',
|
|
|
1868 |
'tw': 'Twi',
|
|
|
1869 |
'ty': 'Tahitian',
|
|
|
1870 |
'ug': 'Uyghur',
|
|
|
1871 |
'uk': 'Ukrainian (Українська)',
|
|
|
1872 |
'ur': 'Urdu (اردو)',
|
|
|
1873 |
'uz': "Uzbek (o'zbek)",
|
|
|
1874 |
've': 'Venda',
|
|
|
1875 |
'vi': 'Vietnamese (Tiếng Việt)',
|
|
|
1876 |
'wo': 'Wolof',
|
|
|
1877 |
'xh': 'Xhosa (isiXhosa)',
|
|
|
1878 |
'xx-lolspeak': 'Lolspeak)',
|
|
|
1879 |
'yi': 'Yiddish',
|
|
|
1880 |
'yo': 'Yoruba (Yorùbá)',
|
|
|
1881 |
'za': 'Zhuang',
|
|
|
1882 |
'zh': 'Chinese',
|
|
|
1883 |
'zh-hans': 'Chinese, Simplified (简体中文)',
|
|
|
1884 |
'zh-hant': 'Chinese, Traditional (繁體中文)',
|
|
|
1885 |
'zh-tw': 'Chinese, Taiwan, Traditional',
|
|
|
1886 |
'zu': 'Zulu (isiZulu)'
|
|
|
1887 |
};
|