diff --git a/scripts/langindex.json b/scripts/langindex.json index ac33a29f89b..5c2eb522717 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1906,6 +1906,7 @@ "core.h5p.authorrole": "h5p", "core.h5p.back": "h5p", "core.h5p.by": "h5p", + "core.h5p.cancelCrop": "h5p", "core.h5p.cancelPublishConfirmationDialogCancelButtonText": "h5p", "core.h5p.cancelPublishConfirmationDialogConfirmButtonText": "h5p", "core.h5p.cancelPublishConfirmationDialogDescription": "h5p", @@ -1924,6 +1925,7 @@ "core.h5p.changeplaceholder": "h5p", "core.h5p.city": "h5p", "core.h5p.close": "h5p", + "core.h5p.confirmCrop": "h5p", "core.h5p.confirmdialogbody": "h5p", "core.h5p.confirmdialogheader": "h5p", "core.h5p.confirmlabel": "h5p", @@ -1940,6 +1942,7 @@ "core.h5p.copyrighttitle": "h5p", "core.h5p.country": "h5p", "core.h5p.creativecommons": "h5p", + "core.h5p.cropImage": "h5p", "core.h5p.currentStep": "h5p", "core.h5p.date": "h5p", "core.h5p.description": "h5p", @@ -2034,6 +2037,8 @@ "core.h5p.reviewAndShare": "h5p", "core.h5p.reviewInfo": "h5p", "core.h5p.reviewMessage": "h5p", + "core.h5p.rotateLeft": "h5p", + "core.h5p.rotateRight": "h5p", "core.h5p.saveChanges": "h5p", "core.h5p.screenshots": "h5p", "core.h5p.screenshotsDescription": "h5p", diff --git a/src/core/features/h5p/assets/fonts/h5p-core-28.eot b/src/core/features/h5p/assets/fonts/h5p-core-28.eot deleted file mode 100644 index 4b2bf455c30..00000000000 Binary files a/src/core/features/h5p/assets/fonts/h5p-core-28.eot and /dev/null differ diff --git a/src/core/features/h5p/assets/fonts/h5p-core-28.svg b/src/core/features/h5p/assets/fonts/h5p-core-28.svg deleted file mode 100644 index 8150d8ba7f0..00000000000 --- a/src/core/features/h5p/assets/fonts/h5p-core-28.svg +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - -{ - "fontFamily": "h5p-core-27", - "description": "Font generated by IcoMoon.", - "copyright": "H5P", - "majorVersion": 1, - "minorVersion": 1, - "version": "Version 1.1", - "fontId": "h5p-core-27", - "psName": "h5p-core-27", - "subFamily": "Regular", - "fullName": "h5p-core-27" -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/core/features/h5p/assets/fonts/h5p-core-28.ttf b/src/core/features/h5p/assets/fonts/h5p-core-28.ttf deleted file mode 100644 index 520db3a9fa5..00000000000 Binary files a/src/core/features/h5p/assets/fonts/h5p-core-28.ttf and /dev/null differ diff --git a/src/core/features/h5p/assets/fonts/h5p-core-28.woff b/src/core/features/h5p/assets/fonts/h5p-core-28.woff deleted file mode 100644 index 6ba910961f4..00000000000 Binary files a/src/core/features/h5p/assets/fonts/h5p-core-28.woff and /dev/null differ diff --git a/src/core/features/h5p/assets/fonts/h5p-core-30.eot b/src/core/features/h5p/assets/fonts/h5p-core-30.eot new file mode 100644 index 00000000000..0ec19be77b7 Binary files /dev/null and b/src/core/features/h5p/assets/fonts/h5p-core-30.eot differ diff --git a/src/core/features/h5p/assets/fonts/h5p-core-30.svg b/src/core/features/h5p/assets/fonts/h5p-core-30.svg new file mode 100644 index 00000000000..8be85fa677b --- /dev/null +++ b/src/core/features/h5p/assets/fonts/h5p-core-30.svg @@ -0,0 +1,123 @@ + + + + + + +{ + "fontFamily": "h5p-core-30", + "description": "Font generated by IcoMoon.", + "copyright": "H5P", + "majorVersion": 1, + "minorVersion": 9, + "version": "Version 1.9", + "fontId": "h5p-core-30", + "psName": "h5p-core-30", + "subFamily": "Regular", + "fullName": "h5p-core-30" +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/core/features/h5p/assets/fonts/h5p-core-30.ttf b/src/core/features/h5p/assets/fonts/h5p-core-30.ttf new file mode 100644 index 00000000000..58ffb7803a1 Binary files /dev/null and b/src/core/features/h5p/assets/fonts/h5p-core-30.ttf differ diff --git a/src/core/features/h5p/assets/fonts/h5p-core-30.woff b/src/core/features/h5p/assets/fonts/h5p-core-30.woff new file mode 100644 index 00000000000..d597b59cb56 Binary files /dev/null and b/src/core/features/h5p/assets/fonts/h5p-core-30.woff differ diff --git a/src/core/features/h5p/assets/fonts/h5p-core-30.woff2 b/src/core/features/h5p/assets/fonts/h5p-core-30.woff2 new file mode 100644 index 00000000000..4e82ff90a57 Binary files /dev/null and b/src/core/features/h5p/assets/fonts/h5p-core-30.woff2 differ diff --git a/src/core/features/h5p/assets/fonts/h5p-hub-publish.svg b/src/core/features/h5p/assets/fonts/h5p-hub-publish.svg index eec568bca03..e32badd40c3 100644 --- a/src/core/features/h5p/assets/fonts/h5p-hub-publish.svg +++ b/src/core/features/h5p/assets/fonts/h5p-hub-publish.svg @@ -1,6 +1,6 @@ - + diff --git a/src/core/features/h5p/assets/js/h5p.js b/src/core/features/h5p/assets/js/h5p.js index a2007ff3d7a..6a41e1f01d6 100644 --- a/src/core/features/h5p/assets/js/h5p.js +++ b/src/core/features/h5p/assets/js/h5p.js @@ -212,7 +212,7 @@ H5P.init = function (target) { instance.triggerXAPI('accessed-reuse'); }); actionBar.on('copyrights', function () { - var dialog = new H5P.Dialog('copyrights', H5P.t('copyrightInformation'), copyrights, $container, $actions.find('.h5p-copyrights')[0]); + var dialog = new H5P.Dialog('copyrights', H5P.t('copyrightInformation'), copyrights, $container); dialog.open(true); instance.triggerXAPI('accessed-copyright'); }); @@ -1093,6 +1093,7 @@ H5P.t = function (key, vars, ns) { H5P.Dialog = function (name, title, content, $element, $returnElement) { /** @alias H5P.Dialog# */ var self = this; + this.activeElement = document.activeElement; var $dialog = H5P.jQuery('<div class="h5p-popup-dialog h5p-' + name + '-dialog" aria-labelledby="' + name + '-dialog-header" aria-modal="true" role="dialog" tabindex="-1">\ <div class="h5p-inner">\ <h2 id="' + name + '-dialog-header">' + title + '</h2>\ @@ -1157,6 +1158,9 @@ H5P.Dialog = function (name, title, content, $element, $returnElement) { if ($returnElement) { $returnElement.focus(); } + else if(self.activeElement) { + self.activeElement.focus(); + } else { $element.focus(); } diff --git a/src/core/features/h5p/assets/styles/h5p-table.css b/src/core/features/h5p/assets/styles/h5p-table.css new file mode 100644 index 00000000000..e781bdce389 --- /dev/null +++ b/src/core/features/h5p/assets/styles/h5p-table.css @@ -0,0 +1,56 @@ +/* Table styling for content types to imitate ckeditor 5 */ +.h5p-iframe { + /* The figure around the table */ + figure.table { + display: table; + table-layout: fixed; + margin: 0 auto; + padding: 0; + float: left; + + /* The actual table */ + table { + border-collapse: collapse; + height: 100%; + width: 100%; + border-spacing: 0; + border-width: 1px; + border-color: #494949; + + td, th { + padding: 1px; + border-color: #494949; + border-bottom-style: solid; + } + + td { + border-width: 0.083em; + } + + th { + text-align: left; + border-width: .167em; + } + + tr:last-child > td { + border-bottom-style: none; + } + } + + figcaption { + background-color: transparent; + caption-side: top; + color: #333; + display: table-caption; + font-size: .75em; + outline-offset: -1px; + padding: .6em; + text-align: center; + word-break: break-word; + } + } + + .table-overflow-protection { + clear: both; + } +} diff --git a/src/core/features/h5p/assets/styles/h5p.css b/src/core/features/h5p/assets/styles/h5p.css index c0a18a54ab4..b4f65182049 100644 --- a/src/core/features/h5p/assets/styles/h5p.css +++ b/src/core/features/h5p/assets/styles/h5p.css @@ -4,11 +4,11 @@ /* Custom H5P font to use for icons. */ @font-face { font-family: 'h5p'; - src: url('../fonts/h5p-core-28.eot?h1atjl'); - src: url('../fonts/h5p-core-28.eot?h1atjl#iefix') format('embedded-opentype'), - url('../fonts/h5p-core-28.ttf?h1atjl') format('truetype'), - url('../fonts/h5p-core-28.woff?h1atjl') format('woff'), - url('../fonts/h5p-core-28.svg?h1atjl#h5p-core-28') format('svg'); + src: url('../fonts/h5p-core-30.eot?h1atjl'); + src: url('../fonts/h5p-core-30.eot?h1atjl#iefix') format('embedded-opentype'), + url('../fonts/h5p-core-30.ttf?h1atjl') format('truetype'), + url('../fonts/h5p-core-30.woff?h1atjl') format('woff'), + url('../fonts/h5p-core-30.svg?h1atjl#h5p-core-30') format('svg'); font-weight: normal; font-style: normal; } diff --git a/src/core/features/h5p/classes/content-validator.ts b/src/core/features/h5p/classes/content-validator.ts index e7b923a9481..25fa5bb6beb 100644 --- a/src/core/features/h5p/classes/content-validator.ts +++ b/src/core/features/h5p/classes/content-validator.ts @@ -19,7 +19,7 @@ import { Translate } from '@singletons'; import { CoreH5PCore, CoreH5PLibraryData, CoreH5PLibraryAddonData, CoreH5PContentDepsTreeDependency } from './core'; import { CoreArray } from '@singletons/array'; -const ALLOWED_STYLEABLE_TAGS = ['span', 'p', 'div', 'h1', 'h2', 'h3', 'td']; +const ALLOWED_STYLEABLE_TAGS = ['span', 'p', 'div', 'h1', 'h2', 'h3', 'table', 'col', 'figure', 'td', 'th', 'li']; /** * Equivalent to H5P's H5PContentValidator, but without some of the validations. @@ -117,7 +117,7 @@ export class CoreH5PContentValidator { // Add related tags for table etc. if (tags.indexOf('table') != -1) { - tags = tags.concat(['tr', 'td', 'th', 'colgroup', 'thead', 'tbody', 'tfoot']); + tags = tags.concat(['tr', 'td', 'th', 'colgroup', 'col', 'thead', 'tbody', 'tfoot', 'figure', 'figcaption']); } if (tags.indexOf('b') != -1) { tags.push('strong'); @@ -142,13 +142,15 @@ export class CoreH5PContentValidator { stylePatterns.push(/^font-size: *[0-9.]+(em|px|%) *;?$/i); } if (semantics.font.family) { - stylePatterns.push(/^font-family: *[-a-z0-9," ]+;?$/i); + stylePatterns.push(/^font-family: *[-a-z0-9,'&; ]+;?$/i); } if (semantics.font.color) { - stylePatterns.push(/^color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)) *;?$/i); + stylePatterns.push(/^color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)|hsla?\([0-9,.% ]+\)) *;?$/i); } if (semantics.font.background) { - stylePatterns.push(/^background-color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)) *;?$/i); + stylePatterns.push( + /^background-color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)|hsla?\([0-9,.% ]+\)) *;?$/i, + ); } if (semantics.font.spacing) { stylePatterns.push(/^letter-spacing: *[0-9.]+(em|px|%) *;?$/i); @@ -158,6 +160,26 @@ export class CoreH5PContentValidator { } } + // Allow styling of tables if they are allowed + if (semantics.tags?.indexOf('table') != -1) { + // CKEditor outputs border as width style color + // eslint-disable-next-line max-len + stylePatterns.push(/^border: *[0-9.]+(em|px|%|) *(none|solid|dotted|dashed|double|groove|ridge|inset|outset) *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)|hsla?\([0-9,.% ]+\)) *;?$/i); + stylePatterns.push(/^border-style: *(none|solid|dotted|dashed|double|groove|ridge|inset|outset) *;?$/i); + stylePatterns.push(/^border-width: *[0-9.]+(em|px|%|) *;?$/i); + stylePatterns.push(/^border-color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)|hsla?\([0-9,.% ]+\)) *;?$/i); + stylePatterns.push(/^vertical-align: *(middle|top|bottom);?$/i); + stylePatterns.push(/^padding: *[0-9.]+(em|px|%|) *;?$/i); + stylePatterns.push(/^width: *[0-9.]+(em|px|%|) *;?$/i); + stylePatterns.push(/^height: *[0-9.]+(em|px|%|) *;?$/i); + stylePatterns.push(/^float: *(right|left|none) *;?$/i); + // Needed for backwards compatibility + stylePatterns.push(/^border-collapse: *collapse *;?$/i); + // Table can have background color when font bgcolor is disabled + // Double entry of bgcolor in stylePatterns shouldn't matter + stylePatterns.push(/^background-color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)|hsla?\([0-9,.% ]+\)) *;?$/i); + } + // Alignment is allowed for all wysiwyg texts stylePatterns.push(/^text-align: *(center|left|right);?$/i); @@ -770,13 +792,26 @@ export class CoreH5PContentValidator { if (matches && matches.length > 1) { if (allowedStyles && attrName === 'style') { // Allow certain styles. + + // Prevent font family from getting split wrong because of the ; in &quot; + if (matches[1].includes('font-family')) { + matches[1] = matches[1].replace(/&quot;/g, '\''); + } + + const validatedStyles: string[] = []; + const styles = matches[1].split(';'); + for (const pattern of allowedStyles) { - if (matches[1].match(pattern)) { - // All patterns are start to end patterns, and CKEditor adds one span per style. - attrArray.push('style="' + matches[1] + '"'); - break; + for (let style of styles) { + style = style.trim(); + if (style.match(pattern)) { + validatedStyles.push(style); + break; + } } } + + attrArray.push('style="' + validatedStyles.join(';') + ';"'); break; } diff --git a/src/core/features/h5p/classes/core.ts b/src/core/features/h5p/classes/core.ts index bae63e9fc2b..59396bbefb2 100644 --- a/src/core/features/h5p/classes/core.ts +++ b/src/core/features/h5p/classes/core.ts @@ -33,7 +33,7 @@ export class CoreH5PCore { static readonly API_VERSION = { majorVersion: 1, - minorVersion: 26, + minorVersion: 27, }; static readonly STYLES = [ @@ -41,6 +41,7 @@ export class CoreH5PCore { 'styles/h5p-confirmation-dialog.css', 'styles/h5p-core-button.css', 'styles/h5p-tooltip.css', + 'styles/h5p-table.css', ]; static readonly SCRIPTS = [ @@ -817,6 +818,11 @@ export class CoreH5PCore { someKeywordsExits: Translate.instant('core.h5p.someKeywordsExits'), width: Translate.instant('core.h5p.width'), height: Translate.instant('core.h5p.height'), + rotateLeft: Translate.instant('core.h5p.rotateLeft'), + rotateRight: Translate.instant('core.h5p.rotateRight'), + cropImage: Translate.instant('core.h5p.cropImage'), + confirmCrop: Translate.instant('core.h5p.confirmCrop'), + cancelCrop: Translate.instant('core.h5p.cancelCrop'), }; } diff --git a/src/core/features/h5p/lang.json b/src/core/features/h5p/lang.json index 40d96721a56..be18051995b 100644 --- a/src/core/features/h5p/lang.json +++ b/src/core/features/h5p/lang.json @@ -15,6 +15,7 @@ "authorrole": "Author's role", "back": "Back", "by": "by", + "cancelCrop": "Cancel crop", "cancelPublishConfirmationDialogCancelButtonText": "No", "cancelPublishConfirmationDialogConfirmButtonText": "Yes", "cancelPublishConfirmationDialogDescription": "Are you sure you want to cancel the sharing process?", @@ -33,6 +34,7 @@ "changeplaceholder": "Photo cropped, text changed, etc.", "city": "City", "close": "Close", + "confirmCrop": "Confirm crop", "confirmdialogbody": "Please confirm that you wish to proceed. This action cannot be undone.", "confirmdialogheader": "Confirm action", "confirmlabel": "Confirm", @@ -49,6 +51,7 @@ "copyrighttitle": "View copyright information for this content.", "country": "Country", "creativecommons": "Creative Commons", + "cropImage": "Crop image", "currentStep": "Step :step of :total", "date": "Date", "description": "Description", @@ -143,6 +146,8 @@ "reviewAndShare": "Review & Share", "reviewInfo": "Review info", "reviewMessage": "Please review the info below before you share", + "rotateLeft": "Rotate left", + "rotateRight": "Rotate right", "saveChanges": "Save changes", "screenshots": "Screenshots", "screenshotsDescription": "Add up to five screenshots of your content",