diff --git a/fonts/h5p-core-19.eot b/fonts/h5p-core-19.eot
deleted file mode 100644
index 2348e29..0000000
Binary files a/fonts/h5p-core-19.eot and /dev/null differ
diff --git a/fonts/h5p-core-19.ttf b/fonts/h5p-core-19.ttf
deleted file mode 100644
index 729aea4..0000000
Binary files a/fonts/h5p-core-19.ttf and /dev/null differ
diff --git a/fonts/h5p-core-19.woff b/fonts/h5p-core-19.woff
deleted file mode 100644
index be9ead2..0000000
Binary files a/fonts/h5p-core-19.woff and /dev/null differ
diff --git a/fonts/h5p-core-20.eot b/fonts/h5p-core-20.eot
new file mode 100644
index 0000000..a01133d
Binary files /dev/null and b/fonts/h5p-core-20.eot differ
diff --git a/fonts/h5p-core-19.svg b/fonts/h5p-core-20.svg
similarity index 60%
rename from fonts/h5p-core-19.svg
rename to fonts/h5p-core-20.svg
index a808aa6..92a9eea 100644
--- a/fonts/h5p-core-19.svg
+++ b/fonts/h5p-core-20.svg
@@ -23,32 +23,34 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fonts/h5p-core-20.ttf b/fonts/h5p-core-20.ttf
new file mode 100644
index 0000000..5103dc2
Binary files /dev/null and b/fonts/h5p-core-20.ttf differ
diff --git a/fonts/h5p-core-20.woff b/fonts/h5p-core-20.woff
new file mode 100644
index 0000000..81982fc
Binary files /dev/null and b/fonts/h5p-core-20.woff differ
diff --git a/h5p-default-storage.class.php b/h5p-default-storage.class.php
index 56f0e46..485cbdc 100644
--- a/h5p-default-storage.class.php
+++ b/h5p-default-storage.class.php
@@ -458,6 +458,24 @@ class H5PDefaultStorage implements \H5PFileStorage {
return file_exists($filePath);
}
+ /**
+ * Check if upgrades script exist for library.
+ *
+ * @param string $machineName
+ * @param int $majorVersion
+ * @param int $minorVersion
+ * @return string Relative path
+ */
+ public function getUpgradeScript($machineName, $majorVersion, $minorVersion) {
+ $upgrades = "/libraries/{$machineName}-{$majorVersion}.{$minorVersion}/upgrades.js";
+ if (file_exists($this->path . $upgrades)) {
+ return $upgrades;
+ }
+ else {
+ return NULL;
+ }
+ }
+
/**
* Recursive function for copying directories.
*
diff --git a/h5p-file-storage.interface.php b/h5p-file-storage.interface.php
index 4dbdbc6..4bb1368 100644
--- a/h5p-file-storage.interface.php
+++ b/h5p-file-storage.interface.php
@@ -199,4 +199,14 @@ interface H5PFileStorage {
* @return bool
*/
public function hasPresave($libraryName, $developmentPath = null);
+
+ /**
+ * Check if upgrades script exist for library.
+ *
+ * @param string $machineName
+ * @param int $majorVersion
+ * @param int $minorVersion
+ * @return string Relative path
+ */
+ public function getUpgradeScript($machineName, $majorVersion, $minorVersion);
}
diff --git a/h5p-metadata.class.php b/h5p-metadata.class.php
index 73223b1..830e9dc 100644
--- a/h5p-metadata.class.php
+++ b/h5p-metadata.class.php
@@ -40,6 +40,10 @@ abstract class H5PMetadata {
),
'yearTo' => array(
'type' => 'int'
+ ),
+ 'defaultLanguage' => array(
+ 'type' => 'text',
+ 'maxLength' => 32,
)
);
@@ -61,6 +65,7 @@ abstract class H5PMetadata {
',"yearFrom":' . (isset($content->year_from) ? $content->year_from : 'null') .
',"yearTo":' . (isset($content->year_to) ? $content->year_to : 'null') .
',"changes":' . (isset($content->changes) ? $content->changes : 'null') .
+ ',"defaultLanguage":' . (isset($content->default_language) ? $content->default_language : 'null') .
',"authorComments":' . (isset($content->author_comments) ? json_encode($content->author_comments) : 'null') . '}';
}
diff --git a/h5p.classes.php b/h5p.classes.php
index d23394e..c628fbc 100644
--- a/h5p.classes.php
+++ b/h5p.classes.php
@@ -537,9 +537,10 @@ interface H5PFrameworkInterface {
* Get number of contents using library as main library.
*
* @param int $libraryId
+ * @param array $skip
* @return int
*/
- public function getNumContent($libraryId);
+ public function getNumContent($libraryId, $skip = NULL);
/**
* Determines if content slug is used.
@@ -614,6 +615,14 @@ interface H5PFrameworkInterface {
* containing the new content type cache that should replace the old one.
*/
public function replaceContentTypeCache($contentTypeCache);
+
+ /**
+ * Checks if the given library has a higher version.
+ *
+ * @param array $library
+ * @return boolean
+ */
+ public function libraryHasUpgrade($library);
}
/**
@@ -919,11 +928,27 @@ class H5PValidator {
}
if (!empty($missingLibraries)) {
- foreach ($missingLibraries as $libString => $library) {
- $this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => $libString)), 'missing-required-library');
+ // We still have missing libraries, check if our main library has an upgrade (BUT only if we has content)
+ $mainDependency = NULL;
+ if (!$skipContent && !empty($mainH5PData)) {
+ foreach ($mainH5PData['preloadedDependencies'] as $dep) {
+ if ($dep['machineName'] === $mainH5PData['mainLibrary']) {
+ $mainDependency = $dep;
+ }
+ }
}
- if (!$this->h5pC->mayUpdateLibraries()) {
- $this->h5pF->setInfoMessage($this->h5pF->t("Note that the libraries may exist in the file you uploaded, but you're not allowed to upload new libraries. Contact the site administrator about this."));
+
+ if ($skipContent || !$mainDependency || !$this->h5pF->libraryHasUpgrade(array(
+ 'machineName' => $mainDependency['mainLibrary'],
+ 'majorVersion' => $mainDependency['majorVersion'],
+ 'minorVersion' => $mainDependency['minorVersion']
+ ))) {
+ foreach ($missingLibraries as $libString => $library) {
+ $this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => $libString)), 'missing-required-library');
+ }
+ if (!$this->h5pC->mayUpdateLibraries()) {
+ $this->h5pF->setInfoMessage($this->h5pF->t("Note that the libraries may exist in the file you uploaded, but you're not allowed to upload new libraries. Contact the site administrator about this."));
+ }
}
}
$valid = empty($missingLibraries) && $valid;
@@ -1646,7 +1671,7 @@ Class H5PExport {
'embedTypes' => $embedTypes
);
- foreach(array('authors', 'source', 'license', 'licenseVersion', 'licenseExtras' ,'yearFrom', 'yearTo', 'changes', 'authorComments') as $field) {
+ foreach(array('authors', 'source', 'license', 'licenseVersion', 'licenseExtras' ,'yearFrom', 'yearTo', 'changes', 'authorComments', 'defaultLanguage') as $field) {
if (isset($content['metadata'][$field]) && $content['metadata'][$field] !== '') {
if (($field !== 'authors' && $field !== 'changes') || (count($content['metadata'][$field]) > 0)) {
$h5pJson[$field] = json_decode(json_encode($content['metadata'][$field], TRUE));
@@ -1815,6 +1840,7 @@ abstract class H5PPermission {
const CREATE_RESTRICTED = 2;
const UPDATE_LIBRARIES = 3;
const INSTALL_RECOMMENDED = 4;
+ const COPY_H5P = 8;
}
abstract class H5PDisplayOptionBehaviour {
@@ -1885,6 +1911,7 @@ class H5PCore {
const DISPLAY_OPTION_EMBED = 'embed';
const DISPLAY_OPTION_COPYRIGHT = 'copyright';
const DISPLAY_OPTION_ABOUT = 'icon';
+ const DISPLAY_OPTION_COPY = 'copy';
// Map flags to string
public static $disable = array(
@@ -1922,8 +1949,6 @@ class H5PCore {
$this->relativePathRegExp = '/^((\.\.\/){1,2})(.*content\/)?(\d+|editor)\/(.+)$/';
}
-
-
/**
* Save content and clear cache.
*
@@ -2875,6 +2900,7 @@ class H5PCore {
$display_options[self::DISPLAY_OPTION_COPYRIGHT] = false;
}
}
+ $display_options[self::DISPLAY_OPTION_COPY] = $this->h5pF->hasPermission(H5PPermission::COPY_H5P, $id);
return $display_options;
}
@@ -3288,6 +3314,9 @@ class H5PCore {
'license' => $this->h5pF->t('License'),
'thumbnail' => $this->h5pF->t('Thumbnail'),
'noCopyrights' => $this->h5pF->t('No copyright information available for this content.'),
+ 'reuse' => $this->h5pF->t('Reuse'),
+ 'reuseContent' => $this->h5pF->t('Reuse Content'),
+ 'reuseDescription' => $this->h5pF->t('Reuse this content.'),
'downloadDescription' => $this->h5pF->t('Download this content as a H5P file.'),
'copyrightsDescription' => $this->h5pF->t('View copyright information for this content.'),
'embedDescription' => $this->h5pF->t('View the embed code for this content.'),
@@ -3325,6 +3354,7 @@ class H5PCore {
'contentType' => $this->h5pF->t('Content Type'),
'licenseExtras' => $this->h5pF->t('License Extras'),
'changes' => $this->h5pF->t('Changelog'),
+ 'contentCopied' => $this->h5pF->t('Content is copied to the clipboard'),
);
}
}
@@ -4545,6 +4575,11 @@ class H5PContentValidator {
'type' => 'text',
'widget' => 'none'
),
+ (object) array(
+ 'name' => 'defaultLanguage',
+ 'type' => 'text',
+ 'widget' => 'none'
+ )
);
return $semantics;
diff --git a/js/h5p-action-bar.js b/js/h5p-action-bar.js
index 3af1a43..608a848 100644
--- a/js/h5p-action-bar.js
+++ b/js/h5p-action-bar.js
@@ -57,9 +57,9 @@ H5P.ActionBar = (function ($, EventDispatcher) {
};
// Register action bar buttons
- if (displayOptions.export) {
+ if (displayOptions.export || displayOptions.copy) {
// Add export button
- addActionButton('download', 'export');
+ addActionButton('reuse', 'export');
}
if (displayOptions.copyright) {
addActionButton('copyrights');
diff --git a/js/h5p-content-upgrade-process.js b/js/h5p-content-upgrade-process.js
index e683902..cab1dfd 100644
--- a/js/h5p-content-upgrade-process.js
+++ b/js/h5p-content-upgrade-process.js
@@ -27,6 +27,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
self.loadLibrary = loadLibrary;
self.upgrade(name, oldVersion, newVersion, params.params, params.metadata, function (err, upgradedParams, upgradedMetadata) {
if (err) {
+ err.id = id;
return done(err);
}
@@ -53,6 +54,12 @@ H5P.ContentUpgradeProcess = (function (Version) {
if (err) {
return done(err);
}
+ if (library.semantics === null) {
+ return done({
+ type: 'libraryMissing',
+ library: library.name + ' ' + library.version.major + '.' + library.version.minor
+ });
+ }
// Run upgrade routines on params
self.processParams(library, oldVersion, newVersion, params, metadata, function (err, params, metadata) {
@@ -176,7 +183,11 @@ H5P.ContentUpgradeProcess = (function (Version) {
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 done(); // Larger or same version that's available
+ return done({
+ type: 'errorTooHighVersion',
+ used: usedLib[0] + ' ' + usedVer,
+ supported: availableLib[0] + ' ' + availableVer
+ }); // Larger or same version that's available
}
// A newer version is available, upgrade params
@@ -192,7 +203,12 @@ H5P.ContentUpgradeProcess = (function (Version) {
});
}
}
- done();
+
+ // Content type was not supporte by the higher version
+ done({
+ type: 'errorNotSupported',
+ used: usedLib[0] + ' ' + usedVer
+ });
break;
case 'group':
diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js
index bb5244a..9dc066c 100644
--- a/js/h5p-content-upgrade.js
+++ b/js/h5p-content-upgrade.js
@@ -1,7 +1,7 @@
/* global H5PAdminIntegration H5PUtils */
(function ($, Version) {
- var info, $container, librariesCache = {}, scriptsCache = {};
+ var info, $log, $container, librariesCache = {}, scriptsCache = {};
// Initialize
$(document).ready(function () {
@@ -9,7 +9,9 @@
info = H5PAdminIntegration.libraryInfo;
// Get and reset container
- $container = $('#h5p-admin-container').html('
' + info.message + '
');
+ const $wrapper = $('#h5p-admin-container').html('');
+ $log = $('').appendTo($wrapper);
+ $container = $('').appendTo($wrapper);
// Make it possible to select version
var $version = $(getVersionSelect(info.versions)).appendTo($container);
@@ -120,9 +122,7 @@
},
error: function (error) {
self.printError(error.err);
-
- // Stop everything
- self.terminate();
+ self.workDone(error.id, null, this);
},
loadLibrary: function (details) {
var worker = this;
@@ -184,7 +184,7 @@
self.token = inData.token;
// Start processing
- self.processBatch(inData.params);
+ self.processBatch(inData.params, inData.skipped);
});
};
@@ -202,11 +202,12 @@
*
* @param {Object} parameters
*/
- ContentUpgrade.prototype.processBatch = function (parameters) {
+ ContentUpgrade.prototype.processBatch = function (parameters, skipped) {
var self = this;
// Track upgraded params
self.upgraded = {};
+ self.skipped = skipped;
// Track current batch
self.parameters = parameters;
@@ -276,7 +277,7 @@
}, function done(err, result) {
if (err) {
self.printError(err);
- return ;
+ result = null;
}
self.workDone(id, result);
@@ -291,7 +292,12 @@
var self = this;
self.working--;
- self.upgraded[id] = result;
+ if (result === null) {
+ self.skipped.push(id);
+ }
+ else {
+ self.upgraded[id] = result;
+ }
// Update progress message
self.throbber.setProgress(Math.round((info.total - self.left + self.current) / (info.total / 100)) + ' %');
@@ -302,6 +308,7 @@
self.nextBatch({
libraryId: self.version.libraryId,
token: self.token,
+ skipped: JSON.stringify(self.skipped),
params: JSON.stringify(self.upgraded)
});
}
@@ -410,14 +417,29 @@
ContentUpgrade.prototype.printError = function (error) {
var self = this;
- if (error.type === 'errorParamsBroken') {
- error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken;
- }
- else if (error.type === 'scriptMissing') {
- error = info.errorScript.replace('%lib', error.library);
+ switch (error.type) {
+ case 'errorParamsBroken':
+ error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken;
+ break;
+
+ case 'libraryMissing':
+ error = info.errorLibrary.replace('%lib', error.library);
+ break;
+
+ case 'scriptMissing':
+ error = info.errorScript.replace('%lib', error.library);
+ break;
+
+ case 'errorTooHighVersion':
+ error = info.errorContent.replace('%id', error.id) + ' ' + info.errorTooHighVersion.replace('%used', error.used).replace('%supported', error.supported);
+ break;
+
+ case 'errorNotSupported':
+ error = info.errorContent.replace('%id', error.id) + ' ' + info.errorNotSupported.replace('%used', error.used);
+ break;
}
- self.setStatus('' + info.error + '
' + error + '
');
+ $('' + info.error + '
' + error + '').appendTo($log);
};
})(H5P.jQuery, H5P.Version);
diff --git a/js/h5p-version.js b/js/h5p-version.js
index 7089b5a..8457341 100644
--- a/js/h5p-version.js
+++ b/js/h5p-version.js
@@ -7,11 +7,24 @@ H5P.Version = (function () {
* @param {String} version
*/
function Version(version) {
- var versionSplit = version.split('.', 3);
- // Public
- this.major =+ versionSplit[0];
- this.minor =+ versionSplit[1];
+ if (typeof version === 'string') {
+ // Name version string (used by content upgrade)
+ var versionSplit = version.split('.', 3);
+ this.major =+ versionSplit[0];
+ this.minor =+ versionSplit[1];
+ }
+ else {
+ // Library objects (used by editor)
+ if (version.localMajorVersion !== undefined) {
+ this.major =+ version.localMajorVersion;
+ this.minor =+ version.localMinorVersion;
+ }
+ else {
+ this.major =+ version.majorVersion;
+ this.minor =+ version.minorVersion;
+ }
+ }
/**
* Public. Custom string for this object.
diff --git a/js/h5p.js b/js/h5p.js
index e2fb91f..92283f3 100644
--- a/js/h5p.js
+++ b/js/h5p.js
@@ -74,7 +74,7 @@ H5P.init = function (target) {
* fullscreen, and the semi-fullscreen solution doesn't work when embedded.
* @type {boolean}
*/
- H5P.fullscreenSupported = !(H5P.isFramed && H5P.externalEmbed !== false) || !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled);
+ H5P.fullscreenSupported = !H5PIntegration.fullscreenDisabled && !H5P.fullscreenDisabled && (!(H5P.isFramed && H5P.externalEmbed !== false) || !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled));
// -We should consider document.msFullscreenEnabled when they get their
// -element sizing corrected. Ref. https://connect.microsoft.com/IE/feedback/details/838286/ie-11-incorrectly-reports-dom-element-sizes-in-fullscreen-mode-when-fullscreened-element-is-within-an-iframe
// Update: Seems to be no need as they've moved on to Webkit
@@ -124,6 +124,9 @@ H5P.init = function (target) {
};
$dialog.find('.h5p-dialog-ok-button').click(closeDialog).keypress(closeDialog);
+ H5P.trigger(instance, 'resize');
+ }).on('dialog-closed', function () {
+ H5P.trigger(instance, 'resize');
});
dialog.open();
}
@@ -176,20 +179,20 @@ H5P.init = function (target) {
var actionBar = new H5P.ActionBar(displayOptions);
var $actions = actionBar.getDOMElement();
- actionBar.on('download', function () {
- window.location.href = contentData.exportUrl;
- instance.triggerXAPI('downloaded');
+ actionBar.on('reuse', function () {
+ H5P.openReuseDialog($actions, contentData, library, instance, contentId);
+ instance.triggerXAPI('accessed-reuse');
});
actionBar.on('copyrights', function () {
var dialog = new H5P.Dialog('copyrights', H5P.t('copyrightInformation'), copyrights, $container);
- dialog.open();
+ dialog.open(true);
instance.triggerXAPI('accessed-copyright');
});
actionBar.on('embed', function () {
H5P.openEmbedDialog($actions, contentData.embedCode, contentData.resizeCode, {
width: $element.width(),
height: $element.height()
- });
+ }, instance);
instance.triggerXAPI('accessed-embed');
});
@@ -260,6 +263,11 @@ H5P.init = function (target) {
var parentHeight = iframe.parentElement.style.height;
iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px';
+ // Note: Force layout reflow
+ // This fixes a flickering bug for embedded content on iPads
+ // @see https://github.com/h5p/h5p-moodle-plugin/issues/237
+ iframe.getBoundingClientRect();
+
// Reset iframe height, in case content has shrinked.
iframe.style.height = '1px';
@@ -952,7 +960,10 @@ H5P.Dialog = function (name, title, content, $element) {
/**
* Opens the dialog.
*/
- self.open = function () {
+ self.open = function (scrollbar) {
+ if (scrollbar) {
+ $dialog.css('height', '100%');
+ }
setTimeout(function () {
$dialog.addClass('h5p-open'); // Fade in
// Triggering an event, in case something has to be done after dialog has been opened.
@@ -967,6 +978,7 @@ H5P.Dialog = function (name, title, content, $element) {
$dialog.removeClass('h5p-open'); // Fade out
setTimeout(function () {
$dialog.remove();
+ H5P.jQuery(self).trigger('dialog-closed', [$dialog]);
}, 200);
};
};
@@ -1129,6 +1141,65 @@ H5P.buildMetadataCopyrights = function (metadata) {
}
};
+/**
+ * Display a dialog containing the download button and copy button.
+ *
+ * @param {H5P.jQuery} $element
+ * @param {Object} contentData
+ * @param {Object} library
+ * @param {Object} instance
+ * @param {number} contentId
+ */
+H5P.openReuseDialog = function ($element, contentData, library, instance, contentId) {
+ let html = '';
+ if (contentData.displayOptions.export) {
+ html += '';
+ }
+ if (contentData.displayOptions.export && contentData.displayOptions.copy) {
+ html += 'or
';
+ }
+ if (contentData.displayOptions.copy) {
+ html += '';
+ }
+
+ const dialog = new H5P.Dialog('reuse', H5P.t('reuseContent'), html, $element);
+
+ // Selecting embed code when dialog is opened
+ H5P.jQuery(dialog).on('dialog-opened', function (e, $dialog) {
+ H5P.jQuery('More Info').click(function (e) {
+ e.stopPropagation();
+ }).appendTo($dialog.find('h2'));
+ $dialog.find('.h5p-download-button').click(function () {
+ window.location.href = contentData.exportUrl;
+ instance.triggerXAPI('downloaded');
+ dialog.close();
+ });
+ $dialog.find('.h5p-copy-button').click(function () {
+ const item = new H5P.ClipboardItem(library);
+ item.contentId = contentId;
+ H5P.setClipboard(item);
+ instance.triggerXAPI('copied');
+ dialog.close();
+ H5P.attachToastTo(
+ H5P.jQuery('.h5p-content:first')[0],
+ H5P.t('contentCopied'),
+ {
+ position: {
+ horizontal: 'centered',
+ vertical: 'centered',
+ noOverflowX: true
+ }
+ }
+ );
+ });
+ H5P.trigger(instance, 'resize');
+ }).on('dialog-closed', function () {
+ H5P.trigger(instance, 'resize');
+ });
+
+ dialog.open();
+};
+
/**
* Display a dialog containing the embed code.
*
@@ -1143,7 +1214,7 @@ H5P.buildMetadataCopyrights = function (metadata) {
* @param {number} size.width
* @param {number} size.height
*/
-H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) {
+H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size, instance) {
var fullEmbedCode = embedCode + resizeCode;
var dialog = new H5P.Dialog('embed', H5P.t('embed'), '' + H5P.t('size') + ': × px
' + H5P.t('showAdvanced') + '
' + H5P.t('advancedHelp') + '
', $element);
@@ -1153,15 +1224,7 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) {
var $scroll = $inner.find('.h5p-scroll-content');
var diff = $scroll.outerHeight() - $scroll.innerHeight();
var positionInner = function () {
- var height = $inner.height();
- if ($scroll[0].scrollHeight + diff > height) {
- $inner.css('height', ''); // 100%
- }
- else {
- $inner.css('height', 'auto');
- height = $inner.height();
- }
- $inner.css('marginTop', '-' + (height / 2) + 'px');
+ H5P.trigger(instance, 'resize');
};
// Handle changing of width/height
@@ -1213,11 +1276,218 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) {
expand.apply(this);
}
});
+ }).on('dialog-closed', function () {
+ H5P.trigger(instance, 'resize');
});
dialog.open();
};
+/**
+ * Show a toast message.
+ *
+ * The reference element could be dom elements the toast should be attached to,
+ * or e.g. the document body for general toast messages.
+ *
+ * @param {DOM} element Reference element to show toast message for.
+ * @param {string} message Message to show.
+ * @param {object} [config] Configuration.
+ * @param {string} [config.style=h5p-toast] Style name for the tooltip.
+ * @param {number} [config.duration=3000] Toast message length in ms.
+ * @param {object} [config.position] Relative positioning of the toast.
+ * @param {string} [config.position.horizontal=centered] [before|left|centered|right|after].
+ * @param {string} [config.position.vertical=below] [above|top|centered|bottom|below].
+ * @param {number} [config.position.offsetHorizontal=0] Extra horizontal offset.
+ * @param {number} [config.position.offsetVertical=0] Extra vetical offset.
+ * @param {boolean} [config.position.noOverflowLeft=false] True to prevent overflow left.
+ * @param {boolean} [config.position.noOverflowRight=false] True to prevent overflow right.
+ * @param {boolean} [config.position.noOverflowTop=false] True to prevent overflow top.
+ * @param {boolean} [config.position.noOverflowBottom=false] True to prevent overflow bottom.
+ * @param {boolean} [config.position.noOverflowX=false] True to prevent overflow left and right.
+ * @param {boolean} [config.position.noOverflowY=false] True to prevent overflow top and bottom.
+ * @param {object} [config.position.overflowReference=document.body] DOM reference for overflow.
+ */
+H5P.attachToastTo = function (element, message, config) {
+ if (element === undefined || message === undefined) {
+ return;
+ }
+
+ const eventPath = function (evt) {
+ var path = (evt.composedPath && evt.composedPath()) || evt.path;
+ var target = evt.target;
+
+ if (path != null) {
+ // Safari doesn't include Window, but it should.
+ return (path.indexOf(window) < 0) ? path.concat(window) : path;
+ }
+
+ if (target === window) {
+ return [window];
+ }
+
+ function getParents(node, memo) {
+ memo = memo || [];
+ var parentNode = node.parentNode;
+
+ if (!parentNode) {
+ return memo;
+ }
+ else {
+ return getParents(parentNode, memo.concat(parentNode));
+ }
+ }
+
+ return [target].concat(getParents(target), window);
+ };
+
+ /**
+ * Handle click while toast is showing.
+ */
+ const clickHandler = function (event) {
+ /*
+ * A common use case will be to attach toasts to buttons that are clicked.
+ * The click would remove the toast message instantly without this check.
+ * Children of the clicked element are also ignored.
+ */
+ var path = eventPath(event);
+ if (path.indexOf(element) !== -1) {
+ return;
+ }
+ clearTimeout(timer);
+ removeToast();
+ };
+
+
+
+ /**
+ * Remove the toast message.
+ */
+ const removeToast = function () {
+ document.removeEventListener('click', clickHandler);
+ if (toast.parentNode) {
+ toast.parentNode.removeChild(toast);
+ }
+ };
+
+ /**
+ * Get absolute coordinates for the toast.
+ *
+ * @param {DOM} element Reference element to show toast message for.
+ * @param {DOM} toast Toast element.
+ * @param {object} [position={}] Relative positioning of the toast message.
+ * @param {string} [position.horizontal=centered] [before|left|centered|right|after].
+ * @param {string} [position.vertical=below] [above|top|centered|bottom|below].
+ * @param {number} [position.offsetHorizontal=0] Extra horizontal offset.
+ * @param {number} [position.offsetVertical=0] Extra vetical offset.
+ * @param {boolean} [position.noOverflowLeft=false] True to prevent overflow left.
+ * @param {boolean} [position.noOverflowRight=false] True to prevent overflow right.
+ * @param {boolean} [position.noOverflowTop=false] True to prevent overflow top.
+ * @param {boolean} [position.noOverflowBottom=false] True to prevent overflow bottom.
+ * @param {boolean} [position.noOverflowX=false] True to prevent overflow left and right.
+ * @param {boolean} [position.noOverflowY=false] True to prevent overflow top and bottom.
+ * @return {object}
+ */
+ const getToastCoordinates = function (element, toast, position) {
+ position = position || {};
+ position.offsetHorizontal = position.offsetHorizontal || 0;
+ position.offsetVertical = position.offsetVertical || 0;
+
+ const toastRect = toast.getBoundingClientRect();
+ const elementRect = element.getBoundingClientRect();
+
+ let left = 0;
+ let top = 0;
+
+ // Compute horizontal position
+ switch (position.horizontal) {
+ case 'before':
+ left = elementRect.left - toastRect.width - position.offsetHorizontal;
+ break;
+ case 'after':
+ left = elementRect.left + elementRect.width + position.offsetHorizontal;
+ break;
+ case 'left':
+ left = elementRect.left + position.offsetHorizontal;
+ break;
+ case 'right':
+ left = elementRect.left + elementRect.width - toastRect.width - position.offsetHorizontal;
+ break;
+ case 'centered':
+ left = elementRect.left + elementRect.width / 2 - toastRect.width / 2 + position.offsetHorizontal;
+ break;
+ default:
+ left = elementRect.left + elementRect.width / 2 - toastRect.width / 2 + position.offsetHorizontal;
+ }
+
+ // Compute vertical position
+ switch (position.vertical) {
+ case 'above':
+ top = elementRect.top - toastRect.height - position.offsetVertical;
+ break;
+ case 'below':
+ top = elementRect.top + elementRect.height + position.offsetVertical;
+ break;
+ case 'top':
+ top = elementRect.top + position.offsetVertical;
+ break;
+ case 'bottom':
+ top = elementRect.top + elementRect.height - toastRect.height - position.offsetVertical;
+ break;
+ case 'centered':
+ top = elementRect.top + elementRect.height / 2 - toastRect.height / 2 + position.offsetVertical;
+ break;
+ default:
+ top = elementRect.top + elementRect.height + position.offsetVertical;
+ }
+
+ // Prevent overflow
+ const overflowElement = document.body;
+ const bounds = overflowElement.getBoundingClientRect();
+ if ((position.noOverflowLeft || position.noOverflowX) && (left < bounds.x)) {
+ left = bounds.x;
+ }
+ if ((position.noOverflowRight || position.noOverflowX) && ((left + toastRect.width) > (bounds.x + bounds.width))) {
+ left = bounds.x + bounds.width - toastRect.width;
+ }
+ if ((position.noOverflowTop || position.noOverflowY) && (top < bounds.y)) {
+ top = bounds.y;
+ }
+ if ((position.noOverflowBottom || position.noOverflowY) && ((top + toastRect.height) > (bounds.y + bounds.height))) {
+ left = bounds.y + bounds.height - toastRect.height;
+ }
+
+ return {left: left, top: top};
+ };
+
+ // Sanitization
+ config = config || {};
+ config.style = config.style || 'h5p-toast';
+ config.duration = config.duration || 3000;
+
+ // Build toast
+ const toast = document.createElement('div');
+ toast.setAttribute('id', config.style);
+ toast.classList.add('h5p-toast-disabled');
+ toast.classList.add(config.style);
+
+ const msg = document.createElement('span');
+ msg.innerHTML = message;
+ toast.appendChild(msg);
+
+ document.body.appendChild(toast);
+
+ // The message has to be set before getting the coordinates
+ const coordinates = getToastCoordinates(element, toast, config.position);
+ toast.style.left = Math.round(coordinates.left) + 'px';
+ toast.style.top = Math.round(coordinates.top) + 'px';
+
+ toast.classList.remove('h5p-toast-disabled');
+ const timer = setTimeout(removeToast, config.duration);
+
+ // The toast can also be removed by clicking somewhere
+ document.addEventListener('click', clickHandler);
+};
+
/**
* Copyrights for a H5P Content Library.
*
@@ -1667,7 +1937,7 @@ H5P.libraryFromString = function (library) {
* The full path to the library.
*/
H5P.getLibraryPath = function (library) {
- return (H5PIntegration.libraryUrl !== undefined ? H5PIntegration.libraryUrl + '/' : H5PIntegration.url + '/libraries/') + library;
+ return H5PIntegration.url + '/libraries/' + library;
};
/**
diff --git a/styles/h5p-admin.css b/styles/h5p-admin.css
index 100dfea..372da79 100644
--- a/styles/h5p-admin.css
+++ b/styles/h5p-admin.css
@@ -339,3 +339,6 @@ button.h5p-admin.disabled:hover {
.h5p-data-view .h5p-facet-tag > span:active {
color: #d20000;
}
+.content-upgrade-log {
+ color: red;
+}
diff --git a/styles/h5p.css b/styles/h5p.css
index c8beedc..3b3e1fe 100644
--- a/styles/h5p.css
+++ b/styles/h5p.css
@@ -3,11 +3,11 @@
/* Custom H5P font to use for icons. */
@font-face {
font-family: 'h5p';
- src: url('../fonts/h5p-core-19.eot?cb8kvi');
- src: url('../fonts/h5p-core-19.eot?cb8kvi#iefix') format('embedded-opentype'),
- url('../fonts/h5p-core-19.ttf?cb8kvi') format('truetype'),
- url('../fonts/h5p-core-19.woff?cb8kvi') format('woff'),
- url('../fonts/h5p-core-19.svg?cb8kvi#h5p') format('svg');
+ src: url('../fonts/h5p-core-20.eot?cb8kvi');
+ src: url('../fonts/h5p-core-20.eot?cb8kvi#iefix') format('embedded-opentype'),
+ url('../fonts/h5p-core-20.ttf?cb8kvi') format('truetype'),
+ url('../fonts/h5p-core-20.woff?cb8kvi') format('woff'),
+ url('../fonts/h5p-core-20.svg?cb8kvi#h5p') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -228,7 +228,7 @@ div.h5p-fullscreen {
padding-right: 0;
}
.h5p-actions > .h5p-button.h5p-export:before {
- content: "\e893";
+ content: "\e90b";
}
.h5p-actions > .h5p-button.h5p-copyrights:before {
content: "\e88f";
@@ -260,7 +260,7 @@ div.h5p-fullscreen {
top: 0;
left: 0;
width: 100%;
- height: 100%;
+ min-height: 100%;
z-index: 100;
padding: 2em;
box-sizing: border-box;
@@ -297,13 +297,19 @@ div.h5p-fullscreen {
padding: 0.325em 0.5em 0.25em;
line-height: 1.25em;
border-bottom: 1px solid #ccc;
+ z-index: 2;
}
-.h5p-embed-dialog .h5p-inner {
- width: 300px;
+.h5p-popup-dialog .h5p-inner > h2 > a {
+ font-size: 12px;
+ margin-left: 1em;
+}
+.h5p-embed-dialog .h5p-inner,
+.h5p-reuse-dialog .h5p-inner,
+.h5p-content-user-data-reset-dialog .h5p-inner {
+ width: 316px;
left: 50%;
top: 50%;
- margin: 0 0 0 -150px;
- transition: margin 250ms linear 100ms;
+ margin: 0 0 0 -158px;
}
.h5p-embed-dialog .h5p-embed-code-container,
.h5p-embed-size {
@@ -339,11 +345,14 @@ div.h5p-fullscreen {
padding: 1em;
box-sizing: border-box;
-moz-box-sizing: border-box;
- height: 100%;
+ color: #555555;
+ z-index: 1;
+}
+.h5p-popup-dialog.h5p-open .h5p-scroll-content {
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
- color: #555555;
+ height: 100%;
}
.h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar {
width: 8px;
@@ -357,7 +366,6 @@ div.h5p-fullscreen {
}
.h5p-popup-dialog .h5p-close {
cursor: pointer;
- outline:none
}
.h5p-popup-dialog .h5p-close:after {
font-family: 'H5P';
@@ -372,6 +380,7 @@ div.h5p-fullscreen {
color: #656565;
cursor: pointer;
text-indent: -0.065em;
+ z-index: 3
}
.h5p-popup-dialog .h5p-close:hover:after,
.h5p-popup-dialog .h5p-close:focus:after {
@@ -455,6 +464,85 @@ div.h5p-fullscreen {
.h5p-dialog-ok-button:active {
background: #eeffee;
}
+.h5p-big-button {
+ line-height: 1.25;
+ display: block;
+ position: relative;
+ cursor: pointer;
+ width: 100%;
+ padding: 1em 1em 1em 3.75em;
+ text-align: left;
+ border: 1px solid #dedede;
+ background: linear-gradient(#ffffff, #f1f1f2);
+ border-radius: 0.25em;
+}
+.h5p-big-button:before {
+ font-family: 'h5p';
+ content: "\e893";
+ line-height: 1;
+ font-size: 3em;
+ color: #2747f7;
+ position: absolute;
+ left: 0.125em;
+ top: 0.125em;
+}
+.h5p-copy-button:before {
+ content: "\e905";
+}
+.h5p-big-button:hover {
+ border: 1px solid #2747f7;
+ background: #eff1fe;
+}
+.h5p-big-button:active {
+ border: 1px solid #dedede;
+ background: #dfe4fe;
+}
+.h5p-button-title {
+ color: #2747f7;
+ font-size: 15px;
+ font-weight: bold;
+ margin-bottom: 0.5em;
+}
+.h5p-button-description {
+ color: #757575;
+}
+.h5p-horizontal-line-text {
+ border-top: 1px solid #dadada;
+ line-height: 1;
+ color: #474747;
+ text-align: center;
+ position: relative;
+ margin: 1.25em 0;
+}
+.h5p-horizontal-line-text > span {
+ background: white;
+ padding: 0.5em;
+ position: absolute;
+ top: -1em;
+ left: 50%;
+ transform: translateX(-50%);
+}
+.h5p-toast {
+ font-size: 0.75em;
+ background-color: rgba(0, 0, 0, 0.9);
+ color: #fff;
+ z-index: 110;
+ position: absolute;
+ padding: 0 0.5em;
+ line-height: 2;
+ border-radius: 4px;
+ white-space: nowrap;
+ pointer-events: none;
+ top: 0;
+ opacity: 1;
+ visibility: visible;
+ transition: opacity 1s;
+}
+.h5p-toast-disabled {
+ opacity: 0;
+ visibility: hidden;
+}
+
/* This is loaded as part of Core and not Editor since this needs to be outside the editor iframe */
.h5peditor-semi-fullscreen {