Merge branch 'master' of github.com:h5p/h5p-php-library
commit
1752d4eccf
|
@ -30,7 +30,7 @@ class H5PDevelopment {
|
|||
$this->findLibraries($filesPath . '/development');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get contents of file.
|
||||
*
|
||||
|
@ -41,15 +41,15 @@ class H5PDevelopment {
|
|||
if (file_exists($file) === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
$contents = file_get_contents($file);
|
||||
if ($contents === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Scans development directory and find all libraries.
|
||||
*
|
||||
|
@ -57,39 +57,39 @@ class H5PDevelopment {
|
|||
*/
|
||||
private function findLibraries($path) {
|
||||
$this->libraries = array();
|
||||
|
||||
|
||||
if (is_dir($path) === FALSE) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$contents = scandir($path);
|
||||
|
||||
|
||||
for ($i = 0, $s = count($contents); $i < $s; $i++) {
|
||||
if ($contents[$i]{0} === '.') {
|
||||
continue; // Skip hidden stuff.
|
||||
}
|
||||
|
||||
|
||||
$libraryPath = $path . '/' . $contents[$i];
|
||||
$libraryJSON = $this->getFileContents($libraryPath . '/library.json');
|
||||
if ($libraryJSON === NULL) {
|
||||
continue; // No JSON file, skip.
|
||||
}
|
||||
|
||||
|
||||
$library = json_decode($libraryJSON, TRUE);
|
||||
if ($library === FALSE) {
|
||||
continue; // Invalid JSON.
|
||||
}
|
||||
|
||||
|
||||
// TODO: Validate props? Not really needed, is it? this is a dev site.
|
||||
|
||||
|
||||
// Save/update library.
|
||||
$library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
|
||||
$this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE);
|
||||
|
||||
|
||||
$library['path'] = $libraryPath;
|
||||
$this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Should we remove libraries without files? Not really needed, but must be cleaned up some time, right?
|
||||
|
||||
// Go trough libraries and insert dependencies. Missing deps. will just be ignored and not available. (I guess?!)
|
||||
|
@ -106,17 +106,17 @@ class H5PDevelopment {
|
|||
}
|
||||
// TODO: Deps must be inserted into h5p_nodes_libraries as well... ? But only if they are used?!
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array Libraris in development folder.
|
||||
*/
|
||||
public function getLibraries() {
|
||||
return $this->libraries;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get library
|
||||
*
|
||||
*
|
||||
* @param string $name of the library.
|
||||
* @param int $majorVersion of the library.
|
||||
* @param int $minorVersion of the library.
|
||||
|
@ -126,10 +126,10 @@ class H5PDevelopment {
|
|||
$library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion);
|
||||
return isset($this->libraries[$library]) === TRUE ? $this->libraries[$library] : NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get semantics for the given library.
|
||||
*
|
||||
*
|
||||
* @param string $name of the library.
|
||||
* @param int $majorVersion of the library.
|
||||
* @param int $minorVersion of the library.
|
||||
|
@ -137,32 +137,32 @@ class H5PDevelopment {
|
|||
*/
|
||||
public function getSemantics($name, $majorVersion, $minorVersion) {
|
||||
$library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion);
|
||||
|
||||
|
||||
if (isset($this->libraries[$library]) === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
return $this->getFileContents($this->libraries[$library]['path'] . '/semantics.json');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get translations for the given library.
|
||||
*
|
||||
*
|
||||
* @param string $name of the library.
|
||||
* @param int $majorVersion of the library.
|
||||
* @param int $minorVersion of the library.
|
||||
* @return string Translation
|
||||
*/
|
||||
public function getLanguage($name, $majorVersion, $minorVersion) {
|
||||
public function getLanguage($name, $majorVersion, $minorVersion, $language) {
|
||||
$library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion);
|
||||
|
||||
|
||||
if (isset($this->libraries[$library]) === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $this->language . '.json');
|
||||
|
||||
return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $language . '.json');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes library as string on the form "name majorVersion.minorVersion"
|
||||
*
|
||||
|
@ -175,4 +175,3 @@ class H5PDevelopment {
|
|||
return $name . ' ' . $majorVersion . '.' . $minorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
663
h5p.classes.php
663
h5p.classes.php
File diff suppressed because it is too large
Load Diff
|
@ -1,19 +1,20 @@
|
|||
/*jshint -W083 */
|
||||
var H5PUpgrades = H5PUpgrades || {};
|
||||
|
||||
(function ($) {
|
||||
var info, $container, librariesCache = {};
|
||||
|
||||
|
||||
// Initialize
|
||||
$(document).ready(function () {
|
||||
// Get library info
|
||||
info = H5PIntegration.getLibraryInfo();
|
||||
|
||||
|
||||
// Get and reset container
|
||||
$container = $('#h5p-admin-container').html('<p>' + info.message + '</p>');
|
||||
|
||||
|
||||
// Make it possible to select version
|
||||
var $version = $(getVersionSelect(info.versions)).appendTo($container);
|
||||
|
||||
|
||||
// Add "go" button
|
||||
$('<button/>', {
|
||||
class: 'h5p-admin-upgrade-button',
|
||||
|
@ -23,11 +24,11 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
new ContentUpgrade($version.val());
|
||||
}
|
||||
}).appendTo($container);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate html for version select.
|
||||
*
|
||||
*
|
||||
* @param {Object} versions
|
||||
* @returns {String}
|
||||
*/
|
||||
|
@ -41,18 +42,18 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
return html;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private. Helps process each property on the given object asynchronously in serial order.
|
||||
*
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {Function} process
|
||||
* @param {Function} finished
|
||||
*/
|
||||
var asyncSerial = function (obj, process, finished) {
|
||||
var id, isArray = obj instanceof Array;
|
||||
|
||||
// Keep track of each property that belongs to this object.
|
||||
|
||||
// Keep track of each property that belongs to this object.
|
||||
if (!isArray) {
|
||||
var ids = [];
|
||||
for (id in obj) {
|
||||
|
@ -61,9 +62,9 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var i = -1; // Keeps track of the current property
|
||||
|
||||
|
||||
/**
|
||||
* Private. Process the next property
|
||||
*/
|
||||
|
@ -71,10 +72,10 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
id = isArray ? i : ids[i];
|
||||
process(id, obj[id], check);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private. Check if we're done or have an error.
|
||||
*
|
||||
*
|
||||
* @param {String} err
|
||||
*/
|
||||
var check = function (err) {
|
||||
|
@ -89,13 +90,13 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
|
||||
check(); // Start
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Make it easy to keep track of version details.
|
||||
*
|
||||
*
|
||||
* @param {String} version
|
||||
* @param {Number} libraryId
|
||||
* @returns {_L1.Version}
|
||||
|
@ -103,19 +104,19 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
function Version(version, libraryId) {
|
||||
if (libraryId !== undefined) {
|
||||
version = info.versions[libraryId];
|
||||
|
||||
|
||||
// Public
|
||||
this.libraryId = libraryId;
|
||||
}
|
||||
var versionSplit = version.split('.', 3);
|
||||
|
||||
|
||||
// Public
|
||||
this.major = versionSplit[0];
|
||||
this.minor = versionSplit[1];
|
||||
|
||||
|
||||
/**
|
||||
* Public. Custom string for this object.
|
||||
*
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
this.toString = function () {
|
||||
|
@ -125,17 +126,17 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
|
||||
/**
|
||||
* Displays a throbber in the status field.
|
||||
*
|
||||
*
|
||||
* @param {String} msg
|
||||
* @returns {_L1.Throbber}
|
||||
*/
|
||||
function Throbber(msg) {
|
||||
var $throbber = H5PUtils.throbber(msg);
|
||||
$container.html('').append($throbber);
|
||||
|
||||
|
||||
/**
|
||||
* Makes it possible to set the progress.
|
||||
*
|
||||
*
|
||||
* @param {String} progress
|
||||
*/
|
||||
this.setProgress = function (progress) {
|
||||
|
@ -145,16 +146,16 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
|
||||
/**
|
||||
* Start a new content upgrade.
|
||||
*
|
||||
*
|
||||
* @param {Number} libraryId
|
||||
* @returns {_L1.ContentUpgrade}
|
||||
*/
|
||||
function ContentUpgrade(libraryId) {
|
||||
var self = this;
|
||||
|
||||
|
||||
// Get selected version
|
||||
self.version = new Version(null, libraryId);
|
||||
|
||||
|
||||
// Create throbber with loading text and progress
|
||||
self.throbber = new Throbber(info.inProgress.replace('%ver', self.version));
|
||||
|
||||
|
@ -164,36 +165,36 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
token: info.token
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next batch and start processing it.
|
||||
*
|
||||
*
|
||||
* @param {Object} outData
|
||||
*/
|
||||
ContentUpgrade.prototype.nextBatch = function (outData) {
|
||||
var self = this;
|
||||
|
||||
|
||||
$.post(info.infoUrl, outData, function (inData) {
|
||||
if (!(inData instanceof Object)) {
|
||||
// Print errors from backend
|
||||
return self.setStatus(inData);
|
||||
}
|
||||
}
|
||||
if (inData.left === 0) {
|
||||
// Nothing left to process
|
||||
return self.setStatus(info.done);
|
||||
}
|
||||
|
||||
|
||||
self.left = inData.left;
|
||||
self.token = inData.token;
|
||||
|
||||
|
||||
// Start processing
|
||||
self.processBatch(inData.params);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set current status message.
|
||||
*
|
||||
*
|
||||
* @param {String} msg
|
||||
*/
|
||||
ContentUpgrade.prototype.setStatus = function (msg) {
|
||||
|
@ -202,28 +203,38 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
|
||||
/**
|
||||
* Process the given parameters.
|
||||
*
|
||||
*
|
||||
* @param {Object} parameters
|
||||
*/
|
||||
ContentUpgrade.prototype.processBatch = function (parameters) {
|
||||
var self = this;
|
||||
var upgraded = {}; // Track upgraded params
|
||||
|
||||
|
||||
var current = 0; // Track progress
|
||||
asyncSerial(parameters, function (id, params, next) {
|
||||
|
||||
// Make params possible to work with
|
||||
params = JSON.parse(params);
|
||||
|
||||
|
||||
try {
|
||||
// Make params possible to work with
|
||||
params = JSON.parse(params);
|
||||
if (!(params instanceof Object)) {
|
||||
throw true;
|
||||
}
|
||||
}
|
||||
catch (event) {
|
||||
return next(info.errorContent.replace('%id', id) + ' ' + info.errorParamsBroken);
|
||||
}
|
||||
|
||||
// Upgrade this content.
|
||||
self.upgrade(info.library.name, new Version(info.library.version), self.version, params, function (err, params) {
|
||||
if (!err) {
|
||||
upgraded[id] = JSON.stringify(params);
|
||||
|
||||
current++;
|
||||
self.throbber.setProgress(Math.round((info.total - self.left + current) / (info.total / 100)) + ' %');
|
||||
if (err) {
|
||||
return next(info.errorContent.replace('%id', id) + ' ' + err);
|
||||
}
|
||||
next(err);
|
||||
|
||||
upgraded[id] = JSON.stringify(params);
|
||||
|
||||
current++;
|
||||
self.throbber.setProgress(Math.round((info.total - self.left + current) / (info.total / 100)) + ' %');
|
||||
next();
|
||||
});
|
||||
|
||||
}, function (err) {
|
||||
|
@ -240,10 +251,10 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Upgade the given content.
|
||||
*
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Version} oldVersion
|
||||
* @param {Version} newVersion
|
||||
|
@ -253,19 +264,19 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
*/
|
||||
ContentUpgrade.prototype.upgrade = function (name, oldVersion, newVersion, params, next) {
|
||||
var self = this;
|
||||
|
||||
|
||||
// Load library details and upgrade routines
|
||||
self.loadLibrary(name, newVersion, function (err, library) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
|
||||
// Run upgrade routines on params
|
||||
self.processParams(library, oldVersion, newVersion, params, function (err, params) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
|
||||
// Check if any of the sub-libraries need upgrading
|
||||
asyncSerial(library.semantics, function (index, field, next) {
|
||||
self.processField(field, params[field.name], function (err, upgradedParams) {
|
||||
|
@ -280,24 +291,24 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Load library data needed for content upgrade.
|
||||
*
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Version} version
|
||||
* @param {Function} next
|
||||
*/
|
||||
ContentUpgrade.prototype.loadLibrary = function (name, version, next) {
|
||||
var self = this;
|
||||
|
||||
|
||||
var key = name + '/' + version.major + '/' + version.minor;
|
||||
if (librariesCache[key] !== undefined) {
|
||||
// Library has been loaded before. Return cache.
|
||||
next(null, librariesCache[key]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
cache: true,
|
||||
|
@ -306,7 +317,7 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
next(info.errorData.replace('%lib', name + ' ' + version));
|
||||
}).done(function (library) {
|
||||
librariesCache[key] = library;
|
||||
|
||||
|
||||
if (library.upgradesScript) {
|
||||
self.loadScript(library.upgradesScript, function (err) {
|
||||
if (err) {
|
||||
|
@ -320,10 +331,10 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Load script with upgrade hooks.
|
||||
*
|
||||
*
|
||||
* @param {String} url
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
@ -338,10 +349,10 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
next();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Run upgrade hooks on params.
|
||||
*
|
||||
*
|
||||
* @param {Object} library
|
||||
* @param {Version} oldVersion
|
||||
* @param {Version} newVersion
|
||||
|
@ -354,7 +365,7 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
// Upgrades script should be loaded so the upgrades should be here.
|
||||
return next(info.errorScript.replace('%lib', library.name + ' ' + newVersion));
|
||||
}
|
||||
|
||||
|
||||
// No upgrades script. Move on
|
||||
return next(null, params);
|
||||
}
|
||||
|
@ -362,7 +373,7 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
// Run upgrade hooks. Start by going through major versions
|
||||
asyncSerial(H5PUpgrades[library.name], function (major, minors, nextMajor) {
|
||||
if (major < oldVersion.major || major > newVersion.major) {
|
||||
// Older than the current version or newer than the selected
|
||||
// Older than the current version or newer than the selected
|
||||
nextMajor();
|
||||
}
|
||||
else {
|
||||
|
@ -374,14 +385,16 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
}
|
||||
else {
|
||||
// We found an upgrade hook, run it
|
||||
if (upgrade.contentUpgrade !== undefined && typeof upgrade.contentUpgrade === 'function') {
|
||||
upgrade.contentUpgrade(params, function (err, upgradedParams) {
|
||||
var unnecessaryWrapper = (upgrade.contentUpgrade !== undefined ? upgrade.contentUpgrade : upgrade);
|
||||
|
||||
try {
|
||||
unnecessaryWrapper(params, function (err, upgradedParams) {
|
||||
params = upgradedParams;
|
||||
nextMinor(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
nextMinor(info.errorScript.replace('%lib', library.name + ' ' + newVersion));
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
}, nextMajor);
|
||||
|
@ -390,27 +403,27 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
next(err, params);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Process parameter fields to find and upgrade sub-libraries.
|
||||
*
|
||||
*
|
||||
* @param {Object} field
|
||||
* @param {Object} params
|
||||
* @param {Function} next
|
||||
*/
|
||||
ContentUpgrade.prototype.processField = function (field, params, next) {
|
||||
var self = this;
|
||||
|
||||
|
||||
if (params === undefined) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
switch (field.type) {
|
||||
case 'library':
|
||||
if (params.library === undefined || params.params === undefined) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
// Look for available upgrades
|
||||
var usedLib = params.library.split(' ', 2);
|
||||
for (var i = 0; i < field.options.length; i++) {
|
||||
|
@ -419,14 +432,14 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
if (availableLib[1] === usedLib[1]) {
|
||||
return next(); // Same version
|
||||
}
|
||||
|
||||
|
||||
// We have different versions
|
||||
var usedVer = new Version(usedLib[1]);
|
||||
var availableVer = new Version(availableLib[1]);
|
||||
if (usedVer.major > availableVer.major || (usedVer.major === availableVer.major && usedVer.minor >= availableVer.minor)) {
|
||||
return next(); // Larger or same version that's available
|
||||
}
|
||||
|
||||
|
||||
// A newer version is available, upgrade params
|
||||
return self.upgrade(availableLib[0], usedVer, availableVer, params.params, function (err, upgraded) {
|
||||
if (!err) {
|
||||
|
@ -484,4 +497,4 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
}
|
||||
};
|
||||
|
||||
})(H5P.jQuery);
|
||||
})(H5P.jQuery);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/*jshint multistr: true */
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -5,7 +7,7 @@ var H5P = H5P || (function () {
|
|||
var head = document.getElementsByTagName('head')[0];
|
||||
var contentId = 0;
|
||||
var contents = {};
|
||||
|
||||
|
||||
/**
|
||||
* Wraps multiple content between a prefix and a suffix.
|
||||
*/
|
||||
|
@ -16,25 +18,25 @@ var H5P = H5P || (function () {
|
|||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
var loadContent = function (id, script) {
|
||||
var url = script.getAttribute('data-h5p');
|
||||
var data, callback = 'H5P' + id;
|
||||
|
||||
|
||||
// Prevent duplicate loading.
|
||||
script.removeAttribute('data-h5p');
|
||||
|
||||
|
||||
// Callback for when content data is loaded.
|
||||
window[callback] = function (content) {
|
||||
contents[id] = content;
|
||||
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
var parent = script.parentNode;
|
||||
parent.insertBefore(iframe, script);
|
||||
|
||||
|
||||
iframe.id = 'h5p-iframe-' + id;
|
||||
iframe.style.display = 'block';
|
||||
iframe.style.width = '100%';
|
||||
|
@ -59,19 +61,19 @@ var H5P = H5P || (function () {
|
|||
</body></html>');
|
||||
iframe.contentDocument.close();
|
||||
iframe.contentDocument.documentElement.style.overflow = 'hidden';
|
||||
|
||||
|
||||
// Clean up
|
||||
parent.removeChild(script);
|
||||
head.removeChild(data);
|
||||
delete window[callback];
|
||||
};
|
||||
|
||||
|
||||
// Create data script
|
||||
data = document.createElement('script');
|
||||
data.src = url + (url.indexOf('?') === -1 ? '?' : '&') + 'callback=' + callback;
|
||||
head.appendChild(data);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Go throught all script tags with the data-h5p attribute and load content.
|
||||
*/
|
||||
|
@ -89,7 +91,7 @@ var H5P = H5P || (function () {
|
|||
contentId++;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return integration object
|
||||
*/
|
||||
|
@ -124,14 +126,14 @@ var H5P = H5P || (function () {
|
|||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// Detect if we support fullscreen, and what prefix to use.
|
||||
var fullScreenBrowserPrefix, safariBrowser;
|
||||
if (document.documentElement.requestFullScreen) {
|
||||
fullScreenBrowserPrefix = '';
|
||||
}
|
||||
else if (document.documentElement.webkitRequestFullScreen
|
||||
&& navigator.userAgent.indexOf('Android') === -1 // Skip Android
|
||||
else if (document.documentElement.webkitRequestFullScreen &&
|
||||
navigator.userAgent.indexOf('Android') === -1 // Skip Android
|
||||
) {
|
||||
safariBrowser = navigator.userAgent.match(/Version\/(\d)/);
|
||||
safariBrowser = (safariBrowser === null ? 0 : parseInt(safariBrowser[1]));
|
||||
|
@ -155,10 +157,10 @@ var H5P = H5P || (function () {
|
|||
var iframe = document.getElementById('h5p-iframe-' + $element.parent().data('content-id'));
|
||||
var $classes = $element.add(body);
|
||||
var $body = $classes.eq(1);
|
||||
|
||||
|
||||
/**
|
||||
* Prepare for resize by setting the correct styles.
|
||||
*
|
||||
*
|
||||
* @param {String} classes CSS
|
||||
*/
|
||||
var before = function (classes) {
|
||||
|
@ -179,7 +181,7 @@ var H5P = H5P || (function () {
|
|||
/**
|
||||
* Gets called when fullscreen mode has been exited.
|
||||
* Resizes and sets focus on content.
|
||||
*
|
||||
*
|
||||
* @param {String} classes CSS
|
||||
*/
|
||||
var done = function (classes) {
|
||||
|
@ -201,10 +203,10 @@ var H5P = H5P || (function () {
|
|||
|
||||
before('h5p-semi-fullscreen');
|
||||
iframe.style.position = 'fixed';
|
||||
|
||||
|
||||
var $disable = $element.prepend('<a href="#" class="h5p-disable-fullscreen" title="Disable fullscreen"></a>').children(':first');
|
||||
var keyup, disableSemiFullscreen = function () {
|
||||
$disable.remove();
|
||||
$disable.remove();
|
||||
$body.unbind('keyup', keyup);
|
||||
iframe.style.position = 'static';
|
||||
done('h5p-semi-fullscreen');
|
||||
|
@ -247,7 +249,7 @@ var H5P = H5P || (function () {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return H5P;
|
||||
})();
|
||||
|
||||
|
|
|
@ -9,47 +9,47 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
H5PLibraryDetails.init = function () {
|
||||
H5PLibraryDetails.$adminContainer = H5PIntegration.getAdminContainer();
|
||||
H5PLibraryDetails.library = H5PIntegration.getLibraryInfo();
|
||||
|
||||
|
||||
// currentContent holds the current list if data (relevant for filtering)
|
||||
H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content;
|
||||
|
||||
|
||||
// The current page index (for pager)
|
||||
H5PLibraryDetails.currentPage = 0;
|
||||
|
||||
|
||||
// The current filter
|
||||
H5PLibraryDetails.currentFilter = '';
|
||||
|
||||
|
||||
// We cache the filtered results, so we don't have to do unneccessary searches
|
||||
H5PLibraryDetails.filterCache = [];
|
||||
|
||||
|
||||
// Append library info
|
||||
H5PLibraryDetails.$adminContainer.append(H5PLibraryDetails.createLibraryInfo());
|
||||
|
||||
|
||||
// Append node list
|
||||
H5PLibraryDetails.$adminContainer.append(H5PLibraryDetails.createContentElement());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create the library details view
|
||||
* Create the library details view
|
||||
*/
|
||||
H5PLibraryDetails.createLibraryInfo = function () {
|
||||
var $libraryInfo = $('<div class="h5p-library-info"></div>');
|
||||
|
||||
|
||||
$.each(H5PLibraryDetails.library.info, function (title, value) {
|
||||
$libraryInfo.append(H5PUtils.createLabeledField(title, value));
|
||||
});
|
||||
|
||||
|
||||
return $libraryInfo;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create the content list with searching and paging
|
||||
* Create the content list with searching and paging
|
||||
*/
|
||||
H5PLibraryDetails.createContentElement = function () {
|
||||
if (H5PLibraryDetails.library.notCached !== undefined) {
|
||||
return H5PUtils.getRebuildCache(H5PLibraryDetails.library.notCached);
|
||||
}
|
||||
|
||||
|
||||
if (H5PLibraryDetails.currentContent === undefined) {
|
||||
H5PLibraryDetails.$content = $('<div class="h5p-content empty">' + H5PLibraryDetails.library.translations.noContent + '</div>');
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
return H5PLibraryDetails.$content;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the content list
|
||||
*/
|
||||
|
@ -71,12 +71,12 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
if(H5PLibraryDetails.$contentTable) {
|
||||
H5PLibraryDetails.$contentTable.remove();
|
||||
}
|
||||
|
||||
|
||||
H5PLibraryDetails.$contentTable = H5PUtils.createTable();
|
||||
|
||||
|
||||
var i = (H5PLibraryDetails.currentPage*H5PLibraryDetails.PAGER_SIZE);
|
||||
var lastIndex = (i+H5PLibraryDetails.PAGER_SIZE);
|
||||
|
||||
|
||||
if(lastIndex > H5PLibraryDetails.currentContent.length) {
|
||||
lastIndex = H5PLibraryDetails.currentContent.length;
|
||||
}
|
||||
|
@ -84,48 +84,48 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
var content = H5PLibraryDetails.currentContent[i];
|
||||
H5PLibraryDetails.$contentTable.append(H5PUtils.createTableRow(['<a href="' + content.url + '">' + content.title + '</a>']));
|
||||
}
|
||||
|
||||
// Appends it to the browser DOM
|
||||
|
||||
// Appends it to the browser DOM
|
||||
H5PLibraryDetails.$contentTable.insertAfter(H5PLibraryDetails.$search);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the pager element on the bottom of the list
|
||||
*/
|
||||
H5PLibraryDetails.createPagerElement = function () {
|
||||
|
||||
|
||||
// Only create pager if needed:
|
||||
if(H5PLibraryDetails.currentContent.length > H5PLibraryDetails.PAGER_SIZE) {
|
||||
|
||||
|
||||
H5PLibraryDetails.$previous = $('<button type="button" class="previous h5p-admin"><</button>');
|
||||
H5PLibraryDetails.$next = $('<button type="button" class="next h5p-admin">></button>');
|
||||
|
||||
|
||||
H5PLibraryDetails.$previous.on('click', function () {
|
||||
if(H5PLibraryDetails.$previous.hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
H5PLibraryDetails.currentPage--;
|
||||
H5PLibraryDetails.updatePager();
|
||||
H5PLibraryDetails.createContentTable();
|
||||
});
|
||||
|
||||
|
||||
H5PLibraryDetails.$next.on('click', function () {
|
||||
if(H5PLibraryDetails.$next.hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
H5PLibraryDetails.currentPage++;
|
||||
H5PLibraryDetails.updatePager();
|
||||
H5PLibraryDetails.createContentTable();
|
||||
});
|
||||
|
||||
|
||||
// This is the Page x of y widget:
|
||||
H5PLibraryDetails.$pagerInfo = $('<span class="pager-info"></span>');
|
||||
|
||||
|
||||
H5PLibraryDetails.$pager = $('<div class="h5p-content-pager"></div>').append(H5PLibraryDetails.$previous, H5PLibraryDetails.$pagerInfo, H5PLibraryDetails.$next);
|
||||
H5PLibraryDetails.$content.append(H5PLibraryDetails.$pager);
|
||||
|
||||
|
||||
H5PLibraryDetails.$pagerInfo.on('click', function () {
|
||||
var width = H5PLibraryDetails.$pagerInfo.innerWidth();
|
||||
H5PLibraryDetails.$pagerInfo.hide();
|
||||
|
@ -134,24 +134,24 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
var pageNumerUpdated = function() {
|
||||
var newPageNum = $gotoInput.val()-1;
|
||||
var intRegex = /^\d+$/;
|
||||
|
||||
|
||||
$goto.remove();
|
||||
H5PLibraryDetails.$pagerInfo.css({display: 'inline-block'});
|
||||
|
||||
|
||||
// Check if input value is valid, and that it has actually changed
|
||||
if(!(intRegex.test(newPageNum) && newPageNum >= 0 && newPageNum < H5PLibraryDetails.getNumPages() && newPageNum != H5PLibraryDetails.currentPage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
H5PLibraryDetails.currentPage = newPageNum;
|
||||
H5PLibraryDetails.updatePager();
|
||||
H5PLibraryDetails.createContentTable();
|
||||
};
|
||||
|
||||
|
||||
// We create an input box where the user may type in the page number
|
||||
// he wants to be displayed.
|
||||
// Reson for doing this is when user has ten-thousands of elements in list,
|
||||
// this is the easiest way of getting to a specified page
|
||||
// this is the easiest way of getting to a specified page
|
||||
var $gotoInput = $('<input/>', {
|
||||
type: 'number',
|
||||
min : 1,
|
||||
|
@ -166,30 +166,30 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
}
|
||||
}
|
||||
}).css({width: width});
|
||||
var $goto = $('<span/>', {
|
||||
var $goto = $('<span/>', {
|
||||
'class': 'h5p-pager-goto'
|
||||
}).css({width: width}).append($gotoInput).insertAfter(H5PLibraryDetails.$pagerInfo);
|
||||
|
||||
|
||||
$gotoInput.focus();
|
||||
});
|
||||
|
||||
|
||||
H5PLibraryDetails.updatePager();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates number of pages
|
||||
*/
|
||||
H5PLibraryDetails.getNumPages = function () {
|
||||
return Math.ceil(H5PLibraryDetails.currentContent.length / H5PLibraryDetails.PAGER_SIZE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the pager text, and enables/disables the next and previous buttons as needed
|
||||
*/
|
||||
H5PLibraryDetails.updatePager = function () {
|
||||
H5PLibraryDetails.$pagerInfo.css({display: 'inline-block'});
|
||||
|
||||
|
||||
if(H5PLibraryDetails.getNumPages() > 0) {
|
||||
var message = H5PUtils.translateReplace(H5PLibraryDetails.library.translations.pageXOfY, {
|
||||
'$x': (H5PLibraryDetails.currentPage+1),
|
||||
|
@ -200,26 +200,26 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
else {
|
||||
H5PLibraryDetails.$pagerInfo.html('');
|
||||
}
|
||||
|
||||
|
||||
H5PLibraryDetails.$previous.toggleClass('disabled', H5PLibraryDetails.currentPage <= 0);
|
||||
H5PLibraryDetails.$next.toggleClass('disabled', H5PLibraryDetails.currentContent.length < (H5PLibraryDetails.currentPage+1)*H5PLibraryDetails.PAGER_SIZE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the search element
|
||||
*/
|
||||
H5PLibraryDetails.createSearchElement = function () {
|
||||
|
||||
|
||||
H5PLibraryDetails.$search = $('<div class="h5p-content-search"><input placeholder="' + H5PLibraryDetails.library.translations.filterPlaceholder + '" type="search"></div>');
|
||||
|
||||
|
||||
var performSeach = function () {
|
||||
var searchString = $('.h5p-content-search > input').val();
|
||||
|
||||
|
||||
// If search string same as previous, just do nothing
|
||||
if(H5PLibraryDetails.currentFilter === searchString) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (searchString.trim().length === 0) {
|
||||
// If empty search, use the complete list
|
||||
H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content;
|
||||
|
@ -230,7 +230,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
}
|
||||
else {
|
||||
var listToFilter = H5PLibraryDetails.library.content;
|
||||
|
||||
|
||||
// Check if we can filter the already filtered results (for performance)
|
||||
if(searchString.length > 1 && H5PLibraryDetails.currentFilter === searchString.substr(0, H5PLibraryDetails.currentFilter.length)) {
|
||||
listToFilter = H5PLibraryDetails.currentContent;
|
||||
|
@ -239,47 +239,47 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
return content.title && content.title.match(new RegExp(searchString, 'i'));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
H5PLibraryDetails.currentFilter = searchString;
|
||||
// Cache the current result
|
||||
H5PLibraryDetails.filterCache[searchString] = H5PLibraryDetails.currentContent;
|
||||
H5PLibraryDetails.currentPage = 0;
|
||||
H5PLibraryDetails.createContentTable();
|
||||
|
||||
|
||||
// Display search results:
|
||||
if (H5PLibraryDetails.$searchResults) {
|
||||
H5PLibraryDetails.$searchResults.remove();
|
||||
}
|
||||
if (searchString.trim().length > 0) {
|
||||
H5PLibraryDetails.$searchResults = $('<span class="h5p-admin-search-results">' + H5PLibraryDetails.currentContent.length + ' hits on ' + H5PLibraryDetails.currentFilter + '</span>');
|
||||
H5PLibraryDetails.$searchResults = $('<span class="h5p-admin-search-results">' + H5PLibraryDetails.currentContent.length + ' hits on ' + H5PLibraryDetails.currentFilter + '</span>');
|
||||
H5PLibraryDetails.$search.append(H5PLibraryDetails.$searchResults);
|
||||
}
|
||||
H5PLibraryDetails.updatePager();
|
||||
};
|
||||
|
||||
var inputTimer = undefined;
|
||||
|
||||
var inputTimer;
|
||||
$('input', H5PLibraryDetails.$search).on('change keypress paste input', function () {
|
||||
// Here we start the filtering
|
||||
// We wait at least 500 ms after last input to perform search
|
||||
if(inputTimer) {
|
||||
clearTimeout(inputTimer);
|
||||
}
|
||||
|
||||
|
||||
inputTimer = setTimeout( function () {
|
||||
performSeach();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
|
||||
H5PLibraryDetails.$content.append(H5PLibraryDetails.$search);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the page size selector
|
||||
*/
|
||||
H5PLibraryDetails.createPageSizeSelector = function () {
|
||||
H5PLibraryDetails.$search.append('<div class="h5p-admin-pager-size-selector">' + H5PLibraryDetails.library.translations.pageSizeSelectorLabel + ':<span data-page-size="10">10</span><span class="selected" data-page-size="20">20</span><span data-page-size="50">50</span><span data-page-size="100">100</span><span data-page-size="200">200</span></div>');
|
||||
|
||||
// Listen to clicks on the page size selector:
|
||||
|
||||
// Listen to clicks on the page size selector:
|
||||
$('.h5p-admin-pager-size-selector > span', H5PLibraryDetails.$search).on('click', function () {
|
||||
H5PLibraryDetails.PAGER_SIZE = $(this).data('page-size');
|
||||
$('.h5p-admin-pager-size-selector > span', H5PLibraryDetails.$search).removeClass('selected');
|
||||
|
@ -289,7 +289,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
H5PLibraryDetails.updatePager();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Initialize me:
|
||||
$(document).ready(function () {
|
||||
if (!H5PLibraryDetails.initialized) {
|
||||
|
@ -297,5 +297,5 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
H5PLibraryDetails.init();
|
||||
}
|
||||
});
|
||||
|
||||
})(H5P.jQuery);
|
||||
|
||||
})(H5P.jQuery);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var H5PLibraryList= H5PLibraryList || {};
|
||||
/*jshint multistr: true */
|
||||
var H5PLibraryList = H5PLibraryList || {};
|
||||
|
||||
(function ($) {
|
||||
|
||||
|
@ -7,53 +8,62 @@ var H5PLibraryList= H5PLibraryList || {};
|
|||
*/
|
||||
H5PLibraryList.init = function () {
|
||||
var $adminContainer = H5PIntegration.getAdminContainer();
|
||||
|
||||
|
||||
var libraryList = H5PIntegration.getLibraryList();
|
||||
if (libraryList.notCached) {
|
||||
$adminContainer.append(H5PUtils.getRebuildCache(libraryList.notCached));
|
||||
}
|
||||
|
||||
|
||||
// Create library list
|
||||
$adminContainer.append(H5PLibraryList.createLibraryList(H5PIntegration.getLibraryList()));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create the library list
|
||||
*
|
||||
*
|
||||
* @param {object} libraries List of libraries and headers
|
||||
*/
|
||||
H5PLibraryList.createLibraryList = function (libraries) {
|
||||
|
||||
var t = H5PIntegration.i18n.H5P;
|
||||
if(libraries.listData === undefined || libraries.listData.length === 0) {
|
||||
return;
|
||||
return $('<div>' + t.NA + '</div>');
|
||||
}
|
||||
|
||||
|
||||
// Create table
|
||||
var $table = H5PUtils.createTable(libraries.listHeaders);
|
||||
$table.addClass('libraries');
|
||||
|
||||
|
||||
// Add libraries
|
||||
var t = H5PIntegration.i18n.H5P;
|
||||
$.each (libraries.listData, function (index, library) {
|
||||
var $libraryRow = H5PUtils.createTableRow([
|
||||
library.title,
|
||||
'<input class="h5p-admin-restricted" type="checkbox"/>',
|
||||
library.numContent,
|
||||
library.numContentDependencies,
|
||||
library.numLibraryDependencies,
|
||||
{
|
||||
text: library.numContent,
|
||||
class: 'h5p-admin-center'
|
||||
},
|
||||
{
|
||||
text: library.numContentDependencies,
|
||||
class: 'h5p-admin-center'
|
||||
},
|
||||
{
|
||||
text: library.numLibraryDependencies,
|
||||
class: 'h5p-admin-center'
|
||||
},
|
||||
'<div class="h5p-admin-buttons-wrapper">\
|
||||
<button class="h5p-admin-upgrade-library"></button>\
|
||||
<button class="h5p-admin-view-library" title="' + t.viewLibrary + '"></button>\
|
||||
<button class="h5p-admin-delete-library"></button>\
|
||||
</div>'
|
||||
]);
|
||||
|
||||
|
||||
H5PLibraryList.addRestricted($('.h5p-admin-restricted', $libraryRow), library.restrictedUrl, library.restricted);
|
||||
|
||||
var hasContent = !(library.numContent === '' || library.numContent === 0);
|
||||
if (library.upgradeUrl === null) {
|
||||
$('.h5p-admin-upgrade-library', $libraryRow).remove();
|
||||
}
|
||||
else if (library.upgradeUrl === false || library.numContent === 0) {
|
||||
else if (library.upgradeUrl === false || !hasContent) {
|
||||
$('.h5p-admin-upgrade-library', $libraryRow).attr('disabled', true);
|
||||
}
|
||||
else {
|
||||
|
@ -61,14 +71,14 @@ var H5PLibraryList= H5PLibraryList || {};
|
|||
window.location.href = library.upgradeUrl;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Open details view when clicked
|
||||
$('.h5p-admin-view-library', $libraryRow).on('click', function () {
|
||||
window.location.href = library.detailsUrl;
|
||||
});
|
||||
|
||||
|
||||
var $deleteButton = $('.h5p-admin-delete-library', $libraryRow);
|
||||
if (libraries.notCached !== undefined || library.numContent !== 0 || (library.numContentDependencies !== '' && library.numContentDependencies !== 0) || (library.numLibraryDependencies !== '' && library.numLibraryDependencies !== 0)) {
|
||||
if (libraries.notCached !== undefined || hasContent || (library.numContentDependencies !== '' && library.numContentDependencies !== 0) || (library.numLibraryDependencies !== '' && library.numLibraryDependencies !== 0)) {
|
||||
// Disabled delete if content.
|
||||
$deleteButton.attr('disabled', true);
|
||||
}
|
||||
|
@ -81,10 +91,10 @@ var H5PLibraryList= H5PLibraryList || {};
|
|||
|
||||
$table.append($libraryRow);
|
||||
});
|
||||
|
||||
|
||||
return $table;
|
||||
};
|
||||
|
||||
|
||||
H5PLibraryList.addRestricted = function ($checkbox, url, selected) {
|
||||
if (selected === null) {
|
||||
$checkbox.remove();
|
||||
|
@ -121,5 +131,5 @@ var H5PLibraryList= H5PLibraryList || {};
|
|||
H5PLibraryList.init();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
})(H5P.jQuery);
|
||||
|
|
|
@ -3,7 +3,7 @@ var H5PUtils = H5PUtils || {};
|
|||
(function ($) {
|
||||
/**
|
||||
* Generic function for creating a table including the headers
|
||||
*
|
||||
*
|
||||
* @param {array} headers List of headers
|
||||
*/
|
||||
H5PUtils.createTable = function (headers) {
|
||||
|
@ -12,56 +12,62 @@ var H5PUtils = H5PUtils || {};
|
|||
if(headers) {
|
||||
var $thead = $('<thead></thead>');
|
||||
var $tr = $('<tr></tr>');
|
||||
|
||||
|
||||
$.each(headers, function (index, value) {
|
||||
if (!(value instanceof Object)) {
|
||||
value = {
|
||||
text: value
|
||||
html: value
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
$('<th/>', value).appendTo($tr);
|
||||
});
|
||||
|
||||
|
||||
$table.append($thead.append($tr));
|
||||
}
|
||||
|
||||
|
||||
return $table;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generic function for creating a table row
|
||||
*
|
||||
*
|
||||
* @param {array} rows Value list. Object name is used as class name in <TD>
|
||||
*/
|
||||
H5PUtils.createTableRow = function (rows) {
|
||||
var $tr = $('<tr></tr>');
|
||||
|
||||
|
||||
$.each(rows, function (index, value) {
|
||||
$tr.append('<td>' + value + '</td>');
|
||||
if (!(value instanceof Object)) {
|
||||
value = {
|
||||
html: value
|
||||
};
|
||||
}
|
||||
|
||||
$('<td/>', value).appendTo($tr);
|
||||
});
|
||||
|
||||
|
||||
return $tr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generic function for creating a field containing label and value
|
||||
*
|
||||
* @param {string} label The label displayed in front of the value
|
||||
*
|
||||
* @param {string} label The label displayed in front of the value
|
||||
* @param {string} value The value
|
||||
*/
|
||||
H5PUtils.createLabeledField = function (label, value) {
|
||||
var $field = $('<div class="h5p-labeled-field"></div>');
|
||||
|
||||
|
||||
$field.append('<div class="h5p-label">' + label + '</div>');
|
||||
$field.append('<div class="h5p-value">' + value + '</div>');
|
||||
|
||||
|
||||
return $field;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces placeholder fields in translation strings
|
||||
*
|
||||
*
|
||||
* @param {string} template The translation template string in the following format: "$name is a $sex"
|
||||
* @param {array} replacors An js object with key and values. Eg: {'$name': 'Frode', '$sex': 'male'}
|
||||
*/
|
||||
|
@ -71,10 +77,10 @@ var H5PUtils = H5PUtils || {};
|
|||
});
|
||||
return template;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get throbber with given text.
|
||||
*
|
||||
*
|
||||
* @param {String} text
|
||||
* @returns {$}
|
||||
*/
|
||||
|
@ -84,7 +90,7 @@ var H5PUtils = H5PUtils || {};
|
|||
text: text
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Makes it possbile to rebuild all content caches from admin UI.
|
||||
* @param {Object} notCached
|
||||
|
@ -101,7 +107,7 @@ var H5PUtils = H5PUtils || {};
|
|||
current++;
|
||||
if (current === parts.length) current = 0;
|
||||
}, 100);
|
||||
|
||||
|
||||
var $counter = $container.find('.progress');
|
||||
var build = function () {
|
||||
$.post(notCached.url, function (left) {
|
||||
|
@ -120,8 +126,8 @@ var H5PUtils = H5PUtils || {};
|
|||
};
|
||||
build();
|
||||
});
|
||||
|
||||
|
||||
return $container;
|
||||
};
|
||||
|
||||
})(H5P.jQuery);
|
||||
|
||||
})(H5P.jQuery);
|
||||
|
|
179
js/h5p.js
179
js/h5p.js
|
@ -1,3 +1,4 @@
|
|||
/*jshint multistr: true */
|
||||
// TODO: Should we split up the generic parts needed by the editor(and others), and the parts needed to "run" H5Ps?
|
||||
var H5P = H5P || {};
|
||||
|
||||
|
@ -11,12 +12,10 @@ H5P.$window = H5P.jQuery(window);
|
|||
if (document.documentElement.requestFullScreen) {
|
||||
H5P.fullScreenBrowserPrefix = '';
|
||||
}
|
||||
else if (document.documentElement.webkitRequestFullScreen
|
||||
&& navigator.userAgent.indexOf('Android') === -1 // Skip Android
|
||||
) {
|
||||
else if (document.documentElement.webkitRequestFullScreen) {
|
||||
H5P.safariBrowser = navigator.userAgent.match(/Version\/(\d)/);
|
||||
H5P.safariBrowser = (H5P.safariBrowser === null ? 0 : parseInt(H5P.safariBrowser[1]));
|
||||
|
||||
|
||||
// Do not allow fullscreen for safari < 7.
|
||||
if (H5P.safariBrowser === 0 || H5P.safariBrowser > 6) {
|
||||
H5P.fullScreenBrowserPrefix = 'webkit';
|
||||
|
@ -56,14 +55,14 @@ H5P.init = function () {
|
|||
|
||||
// Create new instance.
|
||||
var instance = H5P.newRunnable(library, contentId, $container, true);
|
||||
|
||||
|
||||
// Check if we should add and display a fullscreen button for this H5P.
|
||||
if (contentData.fullScreen == 1) {
|
||||
H5P.jQuery('<div class="h5p-content-controls"><div role="button" tabindex="1" class="h5p-enable-fullscreen" title="' + H5P.t('fullscreen') + '"></div></div>').prependTo($container).children().click(function () {
|
||||
H5P.fullScreen($container, instance);
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
var $actions = H5P.jQuery('<ul class="h5p-actions"></ul>');
|
||||
if (contentData.exportUrl !== '') {
|
||||
// Display export button
|
||||
|
@ -87,7 +86,7 @@ H5P.init = function () {
|
|||
H5P.jQuery('<li><a class="h5p-link" href="http://h5p.org" target="_blank" title="' + H5P.t('h5pDescription') + '"></a></li>').appendTo($actions);
|
||||
}
|
||||
$actions.insertAfter($container);
|
||||
|
||||
|
||||
if (H5P.isFramed) {
|
||||
// Make it possible to resize the iframe when the content changes size. This way we get no scrollbars.
|
||||
var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId);
|
||||
|
@ -95,10 +94,10 @@ H5P.init = function () {
|
|||
if (window.parent.H5P.isFullscreen) {
|
||||
return; // Skip if full screen.
|
||||
}
|
||||
|
||||
|
||||
// Retain parent size to avoid jumping/scrolling
|
||||
var parentHeight = iframe.parentElement.style.height;
|
||||
iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px';
|
||||
iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px';
|
||||
|
||||
// Reset iframe height, in case content has shrinked.
|
||||
iframe.style.height = '1px';
|
||||
|
@ -109,7 +108,7 @@ H5P.init = function () {
|
|||
// Free parent
|
||||
iframe.parentElement.style.height = parentHeight;
|
||||
};
|
||||
|
||||
|
||||
var resizeDelay;
|
||||
instance.$.on('resize', function () {
|
||||
// Use a delay to make sure iframe is resized to the correct size.
|
||||
|
@ -119,7 +118,7 @@ H5P.init = function () {
|
|||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Resize everything when window is resized.
|
||||
$window.resize(function () {
|
||||
if (window.parent.H5P.isFullscreen) {
|
||||
|
@ -130,7 +129,7 @@ H5P.init = function () {
|
|||
instance.$.trigger('resize');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Resize content.
|
||||
instance.$.trigger('resize');
|
||||
});
|
||||
|
@ -159,7 +158,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) {
|
|||
window.parent.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var $container = $element;
|
||||
var $classes, $iframe;
|
||||
if (body === undefined) {
|
||||
|
@ -173,23 +172,23 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) {
|
|||
$iframe = H5P.jQuery(iframeSelector);
|
||||
$element = $iframe.parent(); // Put iframe wrapper in fullscreen, not container.
|
||||
}
|
||||
|
||||
|
||||
$classes = $element.add(H5P.$body).add($classes);
|
||||
|
||||
|
||||
/**
|
||||
* Prepare for resize by setting the correct styles.
|
||||
*
|
||||
*
|
||||
* @param {String} classes CSS
|
||||
*/
|
||||
var before = function (classes) {
|
||||
$classes.addClass(classes);
|
||||
|
||||
|
||||
if ($iframe !== undefined) {
|
||||
// Set iframe to its default size(100%).
|
||||
$iframe.css('height', '');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets called when fullscreen mode has been entered.
|
||||
* Resizes and sets focus on content.
|
||||
|
@ -199,17 +198,17 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) {
|
|||
instance.$.trigger('resize');
|
||||
instance.$.trigger('focus');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets called when fullscreen mode has been exited.
|
||||
* Resizes and sets focus on content.
|
||||
*
|
||||
*
|
||||
* @param {String} classes CSS
|
||||
*/
|
||||
var done = function (classes) {
|
||||
H5P.isFullscreen = false;
|
||||
$classes.removeClass(classes);
|
||||
|
||||
|
||||
// Do not rely on window resize events.
|
||||
instance.$.trigger('resize');
|
||||
instance.$.trigger('focus');
|
||||
|
@ -222,11 +221,11 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) {
|
|||
H5P.isFullscreen = true;
|
||||
if (H5P.fullScreenBrowserPrefix === undefined) {
|
||||
// Create semi fullscreen.
|
||||
|
||||
|
||||
before('h5p-semi-fullscreen');
|
||||
var $disable = H5P.jQuery('<div role="button" tabindex="1" class="h5p-disable-fullscreen" title="' + H5P.t('disableFullscreen') + '"></div>').appendTo($container.find('.h5p-content-controls'));
|
||||
var keyup, disableSemiFullscreen = function () {
|
||||
$disable.remove();
|
||||
$disable.remove();
|
||||
$body.unbind('keyup', keyup);
|
||||
done('h5p-semi-fullscreen');
|
||||
};
|
||||
|
@ -241,7 +240,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) {
|
|||
}
|
||||
else {
|
||||
// Create real fullscreen.
|
||||
|
||||
|
||||
before('h5p-fullscreen');
|
||||
var first, eventName = (H5P.fullScreenBrowserPrefix === 'ms' ? 'MSFullscreenChange' : H5P.fullScreenBrowserPrefix + 'fullscreenchange');
|
||||
document.addEventListener(eventName, function () {
|
||||
|
@ -251,7 +250,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) {
|
|||
entered();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// We are exiting fullscreen
|
||||
done('h5p-fullscreen');
|
||||
document.removeEventListener(eventName, arguments.callee, false);
|
||||
|
@ -282,26 +281,26 @@ H5P.getPath = function (path, contentId) {
|
|||
var hasProtocol = function (path) {
|
||||
return path.match(/^[a-z0-9]+:\/\//i);
|
||||
};
|
||||
|
||||
|
||||
if (hasProtocol(path)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
if (contentId !== undefined) {
|
||||
prefix = H5PIntegration.getContentPath(contentId);
|
||||
}
|
||||
else if (window['H5PEditor'] !== undefined) {
|
||||
else if (window.H5PEditor !== undefined) {
|
||||
prefix = H5PEditor.filesPath;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!hasProtocol(prefix)) {
|
||||
prefix = window.parent.location.protocol + "//" + window.parent.location.host + prefix;
|
||||
}
|
||||
|
||||
return prefix + '/' + path;
|
||||
|
||||
return prefix + '/' + path;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -318,7 +317,7 @@ H5P.getContentPath = function (contentId) {
|
|||
|
||||
/**
|
||||
* Get library class constructor from H5P by classname.
|
||||
* Note that this class will only work for resolve "H5P.NameWithoutDot".
|
||||
* Note that this class will only work for resolve "H5P.NameWithoutDot".
|
||||
* Also check out: H5P.newRunnable
|
||||
*
|
||||
* Used from libraries to construct instances of other libraries' objects by name.
|
||||
|
@ -336,34 +335,36 @@ H5P.classFromName = function (name) {
|
|||
*
|
||||
* TODO: Should we check if version matches the library?
|
||||
* TODO: Dynamically try to load libraries currently not loaded? That will require a callback.
|
||||
*
|
||||
*
|
||||
* @param {Object} library Library/action object form params.
|
||||
* @param {Number} contentId
|
||||
* @param {Number} contentId
|
||||
* @param {jQuery} $attachTo An optional element to attach the instance to.
|
||||
* @param {Boolean} skipResize Optionally skip triggering of the resize event after attaching.
|
||||
* @return {Object} Instance.
|
||||
*/
|
||||
H5P.newRunnable = function (library, contentId, $attachTo, skipResize) {
|
||||
var nameSplit, versionSplit;
|
||||
try {
|
||||
var nameSplit = library.library.split(' ', 2);
|
||||
var versionSplit = nameSplit[1].split('.', 2);
|
||||
nameSplit = library.library.split(' ', 2);
|
||||
versionSplit = nameSplit[1].split('.', 2);
|
||||
}
|
||||
catch (err) {
|
||||
return H5P.error('Invalid library string: ' + library.library);
|
||||
}
|
||||
|
||||
|
||||
if ((library.params instanceof Object) !== true || (library.params instanceof Array) === true) {
|
||||
H5P.error('Invalid library params for: ' + library.library);
|
||||
return H5P.error(library.params);
|
||||
}
|
||||
|
||||
|
||||
// Find constructor function
|
||||
var constructor;
|
||||
try {
|
||||
nameSplit = nameSplit[0].split('.');
|
||||
var constructor = window;
|
||||
for (var i = 0; i < nameSplit.length; i++) {
|
||||
constructor = window;
|
||||
for (var i = 0; i < nameSplit.length; i++) {
|
||||
constructor = constructor[nameSplit[i]];
|
||||
};
|
||||
}
|
||||
if (typeof constructor !== 'function') {
|
||||
throw null;
|
||||
}
|
||||
|
@ -371,16 +372,16 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) {
|
|||
catch (err) {
|
||||
return H5P.error('Unable to find constructor for: ' + library.library);
|
||||
}
|
||||
|
||||
|
||||
var instance = new constructor(library.params, contentId);
|
||||
|
||||
|
||||
if (instance.$ === undefined) {
|
||||
instance.$ = H5P.jQuery(instance);
|
||||
}
|
||||
|
||||
|
||||
if ($attachTo !== undefined) {
|
||||
instance.attach($attachTo);
|
||||
|
||||
|
||||
if (skipResize === undefined || !skipResize) {
|
||||
// Resize content.
|
||||
instance.$.trigger('resize');
|
||||
|
@ -417,18 +418,18 @@ H5P.t = function (key, vars, ns) {
|
|||
if (H5PIntegration.i18n[ns] === undefined) {
|
||||
return '[Missing translation namespace "' + ns + '"]';
|
||||
}
|
||||
|
||||
|
||||
if (H5PIntegration.i18n[ns][key] === undefined) {
|
||||
return '[Missing translation "' + key + '" in "' + ns + '"]';
|
||||
}
|
||||
|
||||
var translation = H5PIntegration.i18n[ns][key];
|
||||
|
||||
|
||||
if (vars !== undefined) {
|
||||
// Replace placeholder with variables.
|
||||
for (var placeholder in vars) {
|
||||
translation = translation.replace(placeholder, vars[placeholder]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return translation;
|
||||
|
@ -457,7 +458,7 @@ H5P.Dialog = function (name, title, content, $element) {
|
|||
})
|
||||
.end()
|
||||
.end();
|
||||
|
||||
|
||||
this.open = function () {
|
||||
setTimeout(function () {
|
||||
$dialog.addClass('h5p-open'); // Fade in
|
||||
|
@ -465,7 +466,7 @@ H5P.Dialog = function (name, title, content, $element) {
|
|||
H5P.jQuery(self).trigger('dialog-opened', [$dialog]);
|
||||
}, 1);
|
||||
};
|
||||
|
||||
|
||||
this.close = function () {
|
||||
$dialog.removeClass('h5p-open'); // Fade out
|
||||
setTimeout(function () {
|
||||
|
@ -489,7 +490,7 @@ H5P.openCopyrightsDialog = function ($element, instance) {
|
|||
if (copyrights === undefined || copyrights === '') {
|
||||
copyrights = H5P.t('noCopyrights');
|
||||
}
|
||||
|
||||
|
||||
var dialog = new H5P.Dialog('copyrights', H5P.t('copyrightInformation'), copyrights, $element);
|
||||
dialog.open();
|
||||
};
|
||||
|
@ -503,12 +504,12 @@ H5P.openCopyrightsDialog = function ($element, instance) {
|
|||
*/
|
||||
H5P.openEmbedDialog = function ($element, embedCode) {
|
||||
var dialog = new H5P.Dialog('embed', H5P.t('embed'), '<textarea class="h5p-embed-code-container">' + embedCode + '</textarea>', $element);
|
||||
|
||||
|
||||
// Selecting embed code when dialog is opened
|
||||
H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) {
|
||||
$dialog.find('.h5p-embed-code-container').select();
|
||||
});
|
||||
|
||||
|
||||
dialog.open();
|
||||
};
|
||||
|
||||
|
@ -519,7 +520,7 @@ H5P.ContentCopyrights = function () {
|
|||
var label;
|
||||
var media = [];
|
||||
var content = [];
|
||||
|
||||
|
||||
/**
|
||||
* Public. Set label.
|
||||
*
|
||||
|
@ -528,7 +529,7 @@ H5P.ContentCopyrights = function () {
|
|||
this.setLabel = function (newLabel) {
|
||||
label = newLabel;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public. Add sub content.
|
||||
*
|
||||
|
@ -539,7 +540,7 @@ H5P.ContentCopyrights = function () {
|
|||
media.push(newMedia);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public. Add sub content.
|
||||
*
|
||||
|
@ -550,7 +551,7 @@ H5P.ContentCopyrights = function () {
|
|||
content.push(newContent);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public. Print content copyright.
|
||||
*
|
||||
|
@ -558,28 +559,28 @@ H5P.ContentCopyrights = function () {
|
|||
*/
|
||||
this.toString = function () {
|
||||
var html = '';
|
||||
|
||||
|
||||
// Add media rights
|
||||
for (var i = 0; i < media.length; i++) {
|
||||
html += media[i];
|
||||
}
|
||||
|
||||
|
||||
// Add sub content rights
|
||||
for (var i = 0; i < content.length; i++) {
|
||||
for (i = 0; i < content.length; i++) {
|
||||
html += content[i];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (html !== '') {
|
||||
// Add a label to this info
|
||||
if (label !== undefined) {
|
||||
html = '<h3>' + label + '</h3>' + html;
|
||||
}
|
||||
|
||||
|
||||
// Add wrapper
|
||||
html = '<div class="h5p-content-copyrights">' + html + '</div>';
|
||||
}
|
||||
|
||||
|
||||
return html;
|
||||
};
|
||||
};
|
||||
|
@ -595,35 +596,35 @@ H5P.ContentCopyrights = function () {
|
|||
H5P.MediaCopyright = function (copyright, labels, order, extraFields) {
|
||||
var thumbnail;
|
||||
var list = new H5P.DefinitionList();
|
||||
|
||||
|
||||
/**
|
||||
* Private. Get translated label for field.
|
||||
*
|
||||
* @param {String} fieldName
|
||||
* @return {String}
|
||||
* @return {String}
|
||||
*/
|
||||
var getLabel = function (fieldName) {
|
||||
if (labels === undefined || labels[fieldName] === undefined) {
|
||||
return H5P.t(fieldName);
|
||||
}
|
||||
|
||||
|
||||
return labels[fieldName];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private. Get humanized value for field.
|
||||
*
|
||||
* @param {String} fieldName
|
||||
* @return {String}
|
||||
* @return {String}
|
||||
*/
|
||||
var humanizeValue = function (fieldName, value) {
|
||||
if (fieldName === 'license') {
|
||||
return H5P.copyrightLicenses[value];
|
||||
}
|
||||
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
if (copyright !== undefined) {
|
||||
// Add the extra fields
|
||||
for (var field in extraFields) {
|
||||
|
@ -631,12 +632,12 @@ H5P.MediaCopyright = function (copyright, labels, order, extraFields) {
|
|||
copyright[field] = extraFields[field];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (order === undefined) {
|
||||
// Set default order
|
||||
order = ['title', 'author', 'year', 'source', 'license'];
|
||||
}
|
||||
|
||||
|
||||
for (var i = 0; i < order.length; i++) {
|
||||
var fieldName = order[i];
|
||||
if (copyright[fieldName] !== undefined) {
|
||||
|
@ -644,7 +645,7 @@ H5P.MediaCopyright = function (copyright, labels, order, extraFields) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Public. Set thumbnail.
|
||||
*
|
||||
|
@ -653,7 +654,7 @@ H5P.MediaCopyright = function (copyright, labels, order, extraFields) {
|
|||
this.setThumbnail = function (newThumbnail) {
|
||||
thumbnail = newThumbnail;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public. Checks if this copyright is undisclosed.
|
||||
* I.e. only has the license attribute set, and it's undisclosed.
|
||||
|
@ -669,7 +670,7 @@ H5P.MediaCopyright = function (copyright, labels, order, extraFields) {
|
|||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public. Print media copyright.
|
||||
*
|
||||
|
@ -677,20 +678,20 @@ H5P.MediaCopyright = function (copyright, labels, order, extraFields) {
|
|||
*/
|
||||
this.toString = function () {
|
||||
var html = '';
|
||||
|
||||
|
||||
if (this.undisclosed()) {
|
||||
return html; // No need to print a copyright with a single undisclosed license.
|
||||
}
|
||||
|
||||
|
||||
if (thumbnail !== undefined) {
|
||||
html += thumbnail;
|
||||
}
|
||||
html += list;
|
||||
|
||||
|
||||
if (html !== '') {
|
||||
html = '<div class="h5p-media-copyright">' + html + '</div>';
|
||||
}
|
||||
|
||||
|
||||
return html;
|
||||
};
|
||||
};
|
||||
|
@ -742,16 +743,16 @@ H5P.Field = function (label, value) {
|
|||
* Public. Get field label.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
*/
|
||||
this.getLabel = function () {
|
||||
return label;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public. Get field value.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
*/
|
||||
this.getValue = function () {
|
||||
return value;
|
||||
};
|
||||
|
@ -762,7 +763,7 @@ H5P.Field = function (label, value) {
|
|||
*/
|
||||
H5P.DefinitionList = function () {
|
||||
var fields = [];
|
||||
|
||||
|
||||
/**
|
||||
* Public. Add field to list.
|
||||
*
|
||||
|
@ -771,7 +772,7 @@ H5P.DefinitionList = function () {
|
|||
this.add = function (field) {
|
||||
fields.push(field);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public. Get Number of fields.
|
||||
*
|
||||
|
@ -780,7 +781,7 @@ H5P.DefinitionList = function () {
|
|||
this.size = function () {
|
||||
return fields.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public. Get field at given index.
|
||||
*
|
||||
|
@ -790,7 +791,7 @@ H5P.DefinitionList = function () {
|
|||
this.get = function (index) {
|
||||
return fields[index];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public. Print definition list.
|
||||
*
|
||||
|
@ -879,7 +880,7 @@ H5P.getLibraryPath = function (library) {
|
|||
|
||||
/**
|
||||
* Recursivly clone the given object.
|
||||
* TODO: Consider if this needs to be in core. Doesn't $.extend do the same?
|
||||
* TODO: Consider if this needs to be in core. Doesn't $.extend do the same?
|
||||
*
|
||||
* @param {object} object Object to clone.
|
||||
* @param {type} recursive
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -244,4 +244,7 @@ button.h5p-admin.disabled:hover {
|
|||
padding: 0 0.5em;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
#h5p-admin-container .h5p-admin-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue