\
\
\
@@ -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 000000000..e781bdce3
--- /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 c0a18a54a..b4f651820 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 e7b923a94..25fa5bb6b 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 "
+ if (matches[1].includes('font-family')) {
+ matches[1] = matches[1].replace(/"/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 bae63e9fc..59396bbef 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 40d96721a..be1805199 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",