From 9cf3f4aa7f3e28197b3aa366fed3a3d0971a7848 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 22 Jan 2019 15:27:27 +0100 Subject: [PATCH 01/18] JI-942 Add restict save when a higher version is available --- ...ntent-outdated-library-exception.class.php | 3 + h5p-default-storage.class.php | 18 ++++++ h5p-file-storage.interface.php | 10 ++++ h5p.classes.php | 55 ++++++++++++++++--- js/h5p-version.js | 21 +++++-- 5 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 exceptions/h5p-save-content-outdated-library-exception.class.php diff --git a/exceptions/h5p-save-content-outdated-library-exception.class.php b/exceptions/h5p-save-content-outdated-library-exception.class.php new file mode 100644 index 0000000..dab8ed2 --- /dev/null +++ b/exceptions/h5p-save-content-outdated-library-exception.class.php @@ -0,0 +1,3 @@ +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.classes.php b/h5p.classes.php index d23394e..d2a7d27 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -614,6 +614,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 +927,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) { + 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; @@ -1394,15 +1418,23 @@ class H5PStorage { if (isset($options['disable'])) { $content['disable'] = $options['disable']; } - $content['id'] = $this->h5pC->saveContent($content, $contentMainId); - $this->contentId = $content['id']; try { + // Store content in database + $content['id'] = $this->h5pC->saveContent($content, $contentMainId); + $this->contentId = $content['id']; + // Save content folder contents $this->h5pC->fs->saveContent($current_path, $content); } catch (Exception $e) { - $this->h5pF->setErrorMessage($e->getMessage(), 'save-content-failed'); + if ($e instanceof H5PSaveContentOutdatedLibraryException) { + $message = $this->h5pF->t("You're trying to upload content of an older version of H5P. Please upgrade the content on the server it originated from and try to upload again or turn on the H5P Hub to have this server upgrade it for your automaticall."); + } + else { + $message = $e->getMessage(); + } + $this->h5pF->setErrorMessage($message, 'save-content-failed'); } // Remove temp content folder @@ -1922,8 +1954,6 @@ class H5PCore { $this->relativePathRegExp = '/^((\.\.\/){1,2})(.*content\/)?(\d+|editor)\/(.+)$/'; } - - /** * Save content and clear cache. * @@ -1932,6 +1962,13 @@ class H5PCore { * @return int Content ID */ public function saveContent($content, $contentMainId = NULL) { + + // Check that this is the latest version of the content type we have + if ($this->h5pF->libraryHasUpgrade($content['library'])) { + // We do not allow storing old content due to security concerns + throw new \H5PSaveContentOutdatedLibraryException($this->h5pF->t('Something unexpected happened. We were unable to save this content.')); + } + if (isset($content['id'])) { $this->h5pF->updateContent($content, $contentMainId); } 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. From c9e1ac934714b50167f1884d21e235fed8aa5e01 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 28 Jan 2019 16:01:08 +0100 Subject: [PATCH 02/18] JI-942 Add better error handling for Content Upgrade --- h5p.classes.php | 3 +- js/h5p-content-upgrade-process.js | 14 +++++++-- js/h5p-content-upgrade.js | 48 +++++++++++++++++++++---------- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index d2a7d27..8ea9e7a 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. diff --git a/js/h5p-content-upgrade-process.js b/js/h5p-content-upgrade-process.js index e683902..6954622 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); } @@ -176,7 +177,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 +197,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..168c694 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 = $('

' + info.message + '

').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,25 @@ 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 '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); From f96d04cc27d08ea7675251fc1055c979f99cedb1 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 1 Feb 2019 13:03:27 +0100 Subject: [PATCH 03/18] JI-942 Add missing library error message --- js/h5p-content-upgrade-process.js | 6 ++++++ js/h5p-content-upgrade.js | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/js/h5p-content-upgrade-process.js b/js/h5p-content-upgrade-process.js index 6954622..cab1dfd 100644 --- a/js/h5p-content-upgrade-process.js +++ b/js/h5p-content-upgrade-process.js @@ -54,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) { diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js index 168c694..9dc066c 100644 --- a/js/h5p-content-upgrade.js +++ b/js/h5p-content-upgrade.js @@ -422,6 +422,10 @@ 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; From d1dd47be6f93424c03ecfe6a79c51aa212dbd1ce Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 5 Feb 2019 13:15:50 +0100 Subject: [PATCH 04/18] JI-942 Fix auto content upgrade to work with Drupal module --- ...ntent-outdated-library-exception.class.php | 3 --- h5p.classes.php | 21 +++---------------- styles/h5p-admin.css | 3 +++ 3 files changed, 6 insertions(+), 21 deletions(-) delete mode 100644 exceptions/h5p-save-content-outdated-library-exception.class.php diff --git a/exceptions/h5p-save-content-outdated-library-exception.class.php b/exceptions/h5p-save-content-outdated-library-exception.class.php deleted file mode 100644 index dab8ed2..0000000 --- a/exceptions/h5p-save-content-outdated-library-exception.class.php +++ /dev/null @@ -1,3 +0,0 @@ -h5pC->saveContent($content, $contentMainId); + $this->contentId = $content['id']; try { - // Store content in database - $content['id'] = $this->h5pC->saveContent($content, $contentMainId); - $this->contentId = $content['id']; - // Save content folder contents $this->h5pC->fs->saveContent($current_path, $content); } catch (Exception $e) { - if ($e instanceof H5PSaveContentOutdatedLibraryException) { - $message = $this->h5pF->t("You're trying to upload content of an older version of H5P. Please upgrade the content on the server it originated from and try to upload again or turn on the H5P Hub to have this server upgrade it for your automaticall."); - } - else { - $message = $e->getMessage(); - } - $this->h5pF->setErrorMessage($message, 'save-content-failed'); + $this->h5pF->setErrorMessage($e->getMessage(), 'save-content-failed'); } // Remove temp content folder @@ -1963,13 +1955,6 @@ class H5PCore { * @return int Content ID */ public function saveContent($content, $contentMainId = NULL) { - - // Check that this is the latest version of the content type we have - if ($this->h5pF->libraryHasUpgrade($content['library'])) { - // We do not allow storing old content due to security concerns - throw new \H5PSaveContentOutdatedLibraryException($this->h5pF->t('Something unexpected happened. We were unable to save this content.')); - } - if (isset($content['id'])) { $this->h5pF->updateContent($content, $contentMainId); } 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; +} From 415e1010646df7a99a9ed1cddcf009e8beb48a29 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 5 Feb 2019 17:14:34 +0100 Subject: [PATCH 05/18] JI-942 Add extra check for library upload --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 491ea50..d3e7ee1 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -930,7 +930,7 @@ class H5PValidator { if (!empty($missingLibraries)) { // We still have missing libraries, check if our main library has an upgrade (BUT only if we has content) $mainDependency = NULL; - if (!$skipContent) { + if (!$skipContent && !empty($mainH5PData)) { foreach ($mainH5PData['preloadedDependencies'] as $dep) { if ($dep['machineName'] === $mainH5PData['mainLibrary']) { $mainDependency = $dep; From 2b2184fa300040c2d2f258df1977d0ae21b817f9 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 11 Feb 2019 16:18:57 +0100 Subject: [PATCH 06/18] Fix closing of aria-attribute --- js/h5p.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 439ee41..e2fb91f 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -140,7 +140,7 @@ H5P.init = function (target) { '
    ' + '
    ' + '') From 32b0840ca6d4517a3ae3c7055320da30f9f95130 Mon Sep 17 00:00:00 2001 From: Thomas Marstrander Date: Thu, 21 Feb 2019 13:42:00 +0100 Subject: [PATCH 07/18] HFP-2654 Add default language as part of metadata --- h5p-metadata.class.php | 5 +++++ h5p.classes.php | 5 +++++ 2 files changed, 10 insertions(+) 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 d3e7ee1..4fac2c4 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -4568,6 +4568,11 @@ class H5PContentValidator { 'type' => 'text', 'widget' => 'none' ), + (object) array( + 'name' => 'defaultLanguage', + 'type' => 'text', + 'widget' => 'none' + ) ); return $semantics; From 801f3f33c3d9c9b3808dd4eb55fdf00616a1e8e9 Mon Sep 17 00:00:00 2001 From: Thomas Marstrander Date: Thu, 21 Feb 2019 15:28:04 +0100 Subject: [PATCH 08/18] HFP-2654 Add defaultLanguage to exported fields --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 4fac2c4..1868daf 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1671,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)); From 01550e7f1156d877102d6101bc55f89df99a3f97 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 22 Feb 2019 15:12:43 +0100 Subject: [PATCH 09/18] HFP-2650 Add new fonts! --- fonts/h5p-core-19.eot | Bin 7408 -> 0 bytes fonts/h5p-core-19.ttf | Bin 7228 -> 0 bytes fonts/h5p-core-19.woff | Bin 7304 -> 0 bytes fonts/h5p-core-20.eot | Bin 0 -> 7764 bytes fonts/{h5p-core-19.svg => h5p-core-20.svg} | 58 +++++++++++---------- fonts/h5p-core-20.ttf | Bin 0 -> 7584 bytes fonts/h5p-core-20.woff | Bin 0 -> 7660 bytes styles/h5p.css | 10 ++-- 8 files changed, 35 insertions(+), 33 deletions(-) delete mode 100644 fonts/h5p-core-19.eot delete mode 100644 fonts/h5p-core-19.ttf delete mode 100644 fonts/h5p-core-19.woff create mode 100644 fonts/h5p-core-20.eot rename fonts/{h5p-core-19.svg => h5p-core-20.svg} (60%) create mode 100644 fonts/h5p-core-20.ttf create mode 100644 fonts/h5p-core-20.woff diff --git a/fonts/h5p-core-19.eot b/fonts/h5p-core-19.eot deleted file mode 100644 index 2348e291db555f2af7998c5b81e4f08dcc9383cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7408 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 diff --git a/fonts/h5p-core-19.ttf b/fonts/h5p-core-19.ttf deleted file mode 100644 index 729aea457e0ccf764dd3b5e51c77dc0e23046273..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7228 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 diff --git a/fonts/h5p-core-19.woff b/fonts/h5p-core-19.woff deleted file mode 100644 index be9ead2878e3737baf88c149e9a7a5bcf2916456..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7304 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 F{|m*p3|#;K diff --git a/fonts/h5p-core-20.eot b/fonts/h5p-core-20.eot new file mode 100644 index 0000000000000000000000000000000000000000..a01133dd1ae453ce1ff12dd81caca1e08ce243fb GIT binary patch literal 7764 zcma)Bd5|1ed4KQq>uY9uW_ph9Id*nurgvww*PJ^uT4}YrvZPtbk|Rr4w!G+C8)=1g ztdOkuKqj)KC_cd|8;mgtlt{P>iV8>m5EYf90=q&&Vo(K0Vkfx3wp^5}AQU7)qx@d? zNLm}mre|LF_rB|0-|=4eO}&JCIztF0jJ)}x?I9y zh>Vik$pSe{b_1%CrC(GYxkLZ-%abKA^`L%oe(uO2Jj1Ae8c%J0|2=zdeDTGLsDGZ2 z#1HrGo_k*`aJE25>KN$Fy+EiZrI%6vI_mv<58Qe8he+xlQU4L@5AI*MeeRZ1n=C@o zyHIZ*n7jKBDbon*&_}Wd=MLsKsbRtP_(8q~P2z#1+ z515a5Px?BkQyuli7e%+ZKl|{{$*T_n|KZn&i>H@gPJJqM zcS^G#uV z!9#7GwrqBO;xZ9r`fUJ$AG!kc#O05%``AT{RfWZcNR-$lP5OvKie#9SAzhCN4JTiiqLb9FSXJ9nhH1Rnnxw5}u}~~g+!$F} zsmw%UEF4-|D2{ITN&FEWP~}Hd#kajVJk<2*0o_il8XOvBN8<5y)#}pn@X%^IpC@oKyC zw_|7OKPeMNIoXCj%whE!u$*GE)huAl_$jLUvoJ$ettHA*T1A(NcnstVjj?9EX2oNQ zQ{xZ(jvd!Mu`n<)aTk* zmWuvOQ4Ei&vej&~#tM#_SE{zE#QLZp6(z;0+qGt+I98~t#X>$r1!({Z)R5?fK7;v3rKUi|<1e}L(=640?{FF=7XpnQ;ypbht|$3 zU{v{DPV-kT>9VX4Y_MJ=|RxYKYU{LnDP7jlxE2kVMko_Ek>)u099bouWFIFjq}Eqx2s6<`t8n|g;tWxzQfOc(mAt?x zo}V}CaC%Hr&8i|SRBBaJm(i*{&=fPG;%+G_=1{~8cLE_AyfPyt%%K-GEIY5nt1!A| z%bP7hfTKXn{fky6Xw{uakPvh{QWX=r2wzZ@@QE+$A4%*Kbu8jez z$EoDCRH@YkXI`{4M5{1zvGhQpt1YEzSEZAY0ZWhQT0mE-^Hn9{M9iUx5#X|>_pR>J zZJld z0t*{75NLyb`LlM-N|#fN8%bSp+R(K@EposxB3!VF@fcR!iew_XUzbB%4`@Nf(D?Uz4aE?XBug9} zWgNm7lg_wLmucU$1Efwl6N8hfg_inn5IC+e4v};tg@NPBnmokq9$@W0%F_{DL7Sp4 z0o@GgjA8hii8YY*Xz*_|#0fMPl7kuRf^KkR_bWLgO{V|9jNv7Ws1-wu%OfI+TVV__ zP~4ieKIAss1leY!RVlZTMPf6Ra-&hMAXCI*?Q-R2L}NLmZ`{bkdQO&a+Q@mHhqddw z^^F_ZLOecGh77Vx92c?+a#8zG~izGb7Qa~-zNKJry^NIJrHA+}p2 zPe#bN8{6MYc9HjCce6NHx85aMM@1rlGyH*xAR5m#ax%*I5QFz4)X4LzCuNOC5ONuF zWBY!YUXAVRMGD}mUzuNXEimWYz>MHNDgrZvr5L_O}NyaAEQJVF(I-z5}0M*1w=?G?BJ@{4jtme0^$S$;-l z>d(978A@eco|M@q@u;CZBP*!t@*!E0Z@&XpeP|J#`>xWlbRzb?P9Bl6Y@3_6SCH$4 zohn`$#!}^MB#vQVGPI6Z8KQ1L!yXKgO7z(yAt&c*P4pLuTwc7}p!2$-KP|`Pr#aO- zXMYmX+j{7aH^8ZGK+(7i?{>jsJgDm|!VOJ-pz~KoIBbYQpV#%X?mzHndMKnncE84V z$U#{Nz{_@UP1hfj4cvH8ITSX6;c(CpM-EC8QzEY6$EXC$G8F@*86Lhe=RL%Cy>e8Xw}O@ ziiqRlk0j(}t`a<4q#Eqmnk_HKxLF;&6lkP0irMt?bwQycW4i{2`+STmGY4k4CiV7| zddji2A6P5<)X~AgmC+cbnx3jBG}G@lRV||X{HaD78*KVSIuc2xP}m7G4wri z*hc;?yw}h|t`dp*e7^cXVKi2ZrfMloMb0;Mf6a`*v-+L(cF8Dl|9%`ej(C7Wpt+A%ppt!St zZ!K*tpirM~Ea~B@L>aa6h8<({3WZ6szf_PEPAScf26Z2vuzu{EhqH__rW&f%>(ewg zGBUh%WMqU=zt0%z0|_aXryM^dfZjSX{91iaU6Lg&7Vt+TMrk;d9Z>1iu_>l0zD&$8 zv2v7#3^l0eA;nK6=1ci{m-Q4^rX&S4M$Mtr$lRD5k^!eD(#`kgp^viUnxSN~C+LKR zkr^}1)WA5l#w?IDU<+7tvzoCh2{ovy0kx-*avC9Y+ejGY%S zTVdI+7)c|Z^;xTj7-Qi~Fj>T|z?3u|>@lJ}e%Y^T@qp`Zglv%r+v56LOdQI@EsqF> zNFl=5aDcO<)^hD9RDl&yPrwp#-D}zmhWP4yZ~9eTk3X;R_5u6o_=bMAvn`7>Gmy z^th4n4IfjaJzSAGFP@haCf(XQ3+-6S`h&}{^%6?=DaryC6WS*pRDFIQatvY8DJE!Q1qN)n|e~#^QoLDK_<>>OSfN<$mC8aE&lL6(Y7_TJVjdfGVx5!dy9s3pD z+T_S2*?y@rY9h)vlsbUEZ7_d0(h!ak;DoLOr zwt);*5s%%&P819^3tLB|6YvW)7gj&Ks{WzzBD;CGdH>kVBdgHhUaK!%ZB;}qBTFSnfN z*-WZHll^+O^E_RiQH!0I3#lZ%%}OgGA4|PfXF|(F4TYz0aVZ$hsPy^HzHo%cqR|)+ zhJA)a{~{Wf4D8#DfE0^%b|^tuv)Dr&M_dteG)C6IW@M)r6fs>yeH+VO$%`O^cLa@= zccjDN4G{o_Q5~qu@OJR1mLja9Ws3tfeB74q>O91M5{`rw9gb>JiAvL)N!;|QeqSVf zhsrp;o720n-{fcbpUlpFke`$M68`5nJ-@IfR?F!5FhoyF;N%jMPOA~!h^m>Tsa{D% z_PFbViiiQ{om$P=H%+C9eHe>GzC`+^E$sUwgB`*x7>)N{>n-n(ywiYFHS-uq5jKt` zY~kIF>&Wq#7`ft@?1yh%>VHN&`S+sn|$h)D9}5xTYMBRjP4R+yikPuham) z4HvN2xKP3CH{3l9mDpQopj>OUGS+Q1UvcOUwqlFrUK)2n93GMG9z^jKUW=JF_Df!ztTbDg4M(oz9n2esi+2Y&TV0K-;;iMmffpbb#n={R z)Q!E^h{a9%@cL@IRtOk5`y^%k3EIl)`eiKgClX;ZVLg{k{I8ue z0;9Q4=SsT4ekfwz&}3kZnh1+PNLix)U@DhOd9kovrM=a4MiQb4Go0*f6`_*mf+;mu zD&^Evu=A4Z>leszX_#F=?y6&~Vmz+3$Lm*oz_RfUOqJmAc+b@mZ?#gZL$;VBj$?1!BTx|U;DcoUh*DqC9)5cns#DSvpHEB9Gr+mhWlr}yS_6!+z*km zOKDA(2bZjfnCKqXWBT&z(m81#JUL4y#Cd+8ZaUF-a>aLYVhQht=?`XSN4Ia zlbKt29Yj{%Cd6-B3GwSz&SiS&FJ>_gDoXMb9qU+mXK3Qr#1C7;^`nn|RlJ?=J}-Yv zdKM$a_a?+(?}D8B-3AjOF6miUlSlLJd5_{%gc#};4Y#3&MSD^PRJRQ zCs01;;mB9PtN060Pb5T$GhcAz?}yC9Ey;ec6;C>G17}&&@${e zc9AcVvs9sL>Bm@weVu3dNx4@(EdQHgDW}vY)$eFqwKIB2Kj$m>KJTCOe>0#2?tm6C zS9yHI5_?>cjeh?MlCN~;Xo_CZr%yQpU3ykgYGxaIn;OK^-mVB z_wT`do%_A?25<`A(#^XMA9?@6!ECiuefy83@7kYtW!SuX{^!*Bj7`>s-0CF%DU Iaxv!r0K=bUb^rhX literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5103dc25bb75a94dfcb48c27bbcd99341c34f675 GIT binary patch literal 7584 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 literal 0 HcmV?d00001 diff --git a/fonts/h5p-core-20.woff b/fonts/h5p-core-20.woff new file mode 100644 index 0000000000000000000000000000000000000000..81982fcd3ef6ad42951ddbb71470b721145d35a9 GIT binary patch literal 7660 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 f7LFa5KSH)b{Q^0LtK)fc Date: Wed, 27 Feb 2019 15:03:58 +0100 Subject: [PATCH 10/18] HFP-2541 Add reuse dialog instead of download button --- h5p.classes.php | 6 ++++ js/h5p-action-bar.js | 4 +-- js/h5p.js | 49 ++++++++++++++++++++++++++++++-- styles/h5p.css | 66 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 118 insertions(+), 7 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 1868daf..2292471 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1840,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 { @@ -1910,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( @@ -2898,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; } @@ -3311,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.'), 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.js b/js/h5p.js index e2fb91f..e0ffeef 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -176,9 +176,9 @@ 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); @@ -1129,6 +1129,49 @@ 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.find('.h5p-copy-button').click(function () { + const item = new H5P.ClipboardItem(library); + item.contentId = contentId; + H5P.setClipboard(item); + instance.triggerXAPI('copied'); + }); + }); + + dialog.open(); +}; + /** * Display a dialog containing the embed code. * diff --git a/styles/h5p.css b/styles/h5p.css index 102cf5d..d24a8f1 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -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"; @@ -297,6 +297,11 @@ div.h5p-fullscreen { padding: 0.325em 0.5em 0.25em; line-height: 1.25em; border-bottom: 1px solid #ccc; + z-index: 2; +} +.h5p-popup-dialog .h5p-inner > h2 > a { + font-size: 12px; + margin-left: 1em; } .h5p-embed-dialog .h5p-inner { width: 300px; @@ -344,6 +349,7 @@ div.h5p-fullscreen { overflow-x: hidden; overflow-y: auto; color: #555555; + z-index: 1; } .h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar { width: 8px; @@ -357,7 +363,6 @@ div.h5p-fullscreen { } .h5p-popup-dialog .h5p-close { cursor: pointer; - outline:none } .h5p-popup-dialog .h5p-close:after { font-family: 'H5P'; @@ -372,6 +377,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 +461,62 @@ 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; +} /* This is loaded as part of Core and not Editor since this needs to be outside the editor iframe */ .h5peditor-semi-fullscreen { From b64292af094f23e32da59bb0b3e9adda3717ab0d Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 27 Feb 2019 16:04:37 +0100 Subject: [PATCH 11/18] HFP-2541 Fix Dialog scrollbar on small dialogs --- js/h5p.js | 30 +++++++++++++++++------------- styles/h5p.css | 17 +++++++++++------ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index e0ffeef..cc3a55b 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -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(); } @@ -182,14 +185,14 @@ H5P.init = function (target) { }); 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'); }); @@ -952,7 +955,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 +973,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); }; }; @@ -1167,6 +1174,9 @@ H5P.openReuseDialog = function ($element, contentData, library, instance, conten H5P.setClipboard(item); instance.triggerXAPI('copied'); }); + H5P.trigger(instance, 'resize'); + }).on('dialog-closed', function () { + H5P.trigger(instance, 'resize'); }); dialog.open(); @@ -1186,7 +1196,7 @@ H5P.openReuseDialog = function ($element, contentData, library, instance, conten * @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); @@ -1196,15 +1206,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 @@ -1256,6 +1258,8 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { expand.apply(this); } }); + }).on('dialog-closed', function () { + H5P.trigger(instance, 'resize'); }); dialog.open(); diff --git a/styles/h5p.css b/styles/h5p.css index d24a8f1..e17523e 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -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; @@ -303,12 +303,13 @@ div.h5p-fullscreen { font-size: 12px; margin-left: 1em; } -.h5p-embed-dialog .h5p-inner { +.h5p-embed-dialog .h5p-inner, +.h5p-reuse-dialog .h5p-inner, +.h5p-content-user-data-reset-dialog .h5p-inner { width: 300px; left: 50%; top: 50%; margin: 0 0 0 -150px; - transition: margin 250ms linear 100ms; } .h5p-embed-dialog .h5p-embed-code-container, .h5p-embed-size { @@ -344,12 +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; - z-index: 1; + height: 100%; } .h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar { width: 8px; @@ -516,6 +519,8 @@ div.h5p-fullscreen { padding: 0.5em; position: absolute; top: -1em; + left: 50%; + transform: translateX(-50%); } /* This is loaded as part of Core and not Editor since this needs to be outside the editor iframe */ From a5f1b49f6b35365bd60d62870320e24286fa86f6 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 27 Feb 2019 16:21:11 +0100 Subject: [PATCH 12/18] HFP-2541 Fix small dialogs larger --- styles/h5p.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/h5p.css b/styles/h5p.css index e17523e..ef0d054 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -306,10 +306,10 @@ div.h5p-fullscreen { .h5p-embed-dialog .h5p-inner, .h5p-reuse-dialog .h5p-inner, .h5p-content-user-data-reset-dialog .h5p-inner { - width: 300px; + width: 400px; left: 50%; top: 50%; - margin: 0 0 0 -150px; + margin: 0 0 0 -200px; } .h5p-embed-dialog .h5p-embed-code-container, .h5p-embed-size { From 840f5dcb12d939f5636151952eacd8324ab0a6e2 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 1 Mar 2019 15:24:46 +0100 Subject: [PATCH 13/18] HFP-2541 Fix UX improvements when reusing content (Moved Toast from Editor to Core) --- h5p.classes.php | 1 + js/h5p.js | 220 +++++++++++++++++++++++++++++++++++++++++++++++- styles/h5p.css | 21 +++++ 3 files changed, 241 insertions(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 2292471..c628fbc 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -3354,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'), ); } } diff --git a/js/h5p.js b/js/h5p.js index cc3a55b..6ba349f 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1161,18 +1161,31 @@ H5P.openReuseDialog = function ($element, contentData, library, instance, conten // Selecting embed code when dialog is opened H5P.jQuery(dialog).on('dialog-opened', function (e, $dialog) { - H5P.jQuery('More Info').click(function (e) { + 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 () { @@ -1265,6 +1278,211 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size, instance) 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. * diff --git a/styles/h5p.css b/styles/h5p.css index ef0d054..11d15d0 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -522,6 +522,27 @@ div.h5p-fullscreen { 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 { From fc044630bccdb469d171cab251c38dde7dd1d6e0 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 10 Oct 2018 14:43:28 +0200 Subject: [PATCH 14/18] JI-853 Add option to disable fullscreen on load --- js/h5p.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 6ba349f..5594f72 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 = !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 From 53adda67c23fccb794196d629c5fd8e065b763f6 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 7 Mar 2019 12:57:45 +0100 Subject: [PATCH 15/18] JI-1010 Add disable fullscreen feature for embeds --- js/h5p.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 5594f72..00a241a 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.fullscreenDisabled && (!(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 From e9d08b973a33932c2479ffa29022106bcf6407e3 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 7 Mar 2019 14:33:01 +0100 Subject: [PATCH 16/18] Remove old libraryUrl due to conflict with Content Upgrade --- js/h5p.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 00a241a..e5a4262 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1932,7 +1932,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; }; /** From 68e56dd8fd583e0e2f91b01e0dc01d3a108b4101 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 7 Mar 2019 15:01:49 +0100 Subject: [PATCH 17/18] Fix reuse dialog not 100% visible on iPhone --- styles/h5p.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/h5p.css b/styles/h5p.css index 11d15d0..3b3e1fe 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -306,10 +306,10 @@ div.h5p-fullscreen { .h5p-embed-dialog .h5p-inner, .h5p-reuse-dialog .h5p-inner, .h5p-content-user-data-reset-dialog .h5p-inner { - width: 400px; + width: 316px; left: 50%; top: 50%; - margin: 0 0 0 -200px; + margin: 0 0 0 -158px; } .h5p-embed-dialog .h5p-embed-code-container, .h5p-embed-size { From 1a09b1a30e61fc9f6454f1e79a0f8f35290413fd Mon Sep 17 00:00:00 2001 From: Thomas Marstrander Date: Thu, 7 Mar 2019 15:48:21 +0100 Subject: [PATCH 18/18] Fix flickering for embedded content on iPads see https://github.com/h5p/h5p-moodle-plugin/issues/237 --- js/h5p.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/js/h5p.js b/js/h5p.js index e5a4262..92283f3 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -263,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';