From 7f7c3ca8520c9cfb387946ff8c57ab1fc62463b3 Mon Sep 17 00:00:00 2001 From: rhenck <richard.henck@iqb.hu-berlin.de> Date: Mon, 14 Mar 2022 15:41:48 +0100 Subject: [PATCH] Add sanatization of cloze children --- projects/common/util/element.factory.ts | 25 ++++++- .../common/util/unit-definition-sanitizer.ts | 66 ++++++++++++------- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/projects/common/util/element.factory.ts b/projects/common/util/element.factory.ts index faaa9f80b..382ef5d07 100644 --- a/projects/common/util/element.factory.ts +++ b/projects/common/util/element.factory.ts @@ -36,6 +36,7 @@ import { UIElement, UIElementType, UIElementValue, VideoElement } from '../interfaces/elements'; +import { ClozeDocument, ClozeDocumentParagraph, ClozeDocumentParagraphPart } from '../interfaces/cloze'; export abstract class ElementFactory { static createElement(element: Partial<UIElement>): UIElement { @@ -238,7 +239,29 @@ export abstract class ElementFactory { return { ...ElementFactory.initElement({ width: 450, height: 200, ...element }), type: 'cloze', - document: element.document !== undefined ? element.document : { type: 'doc', content: [] }, + document: element.document !== undefined ? + { + ...element.document, + content: element.document.content + .map((paragraph: ClozeDocumentParagraph) => ({ + ...paragraph, + content: paragraph.content + .map((paraPart: ClozeDocumentParagraphPart) => ( + ['TextField', 'DropList', 'ToggleButton'].includes(paraPart.type) ? + { + ...paraPart, + attrs: { + ...paraPart.attrs, + model: ElementFactory.createElement(paraPart.attrs!.model as InputElement) + } + } : + { + ...paraPart + } + )) + })) + } as ClozeDocument : + { type: 'doc', content: [] }, position: ElementFactory.initPositionProps(element.position), styling: { ...ElementFactory.initBasicStyles(element.styling), diff --git a/projects/common/util/unit-definition-sanitizer.ts b/projects/common/util/unit-definition-sanitizer.ts index fd99cff9c..5564fb8c9 100644 --- a/projects/common/util/unit-definition-sanitizer.ts +++ b/projects/common/util/unit-definition-sanitizer.ts @@ -17,6 +17,7 @@ import ToggleButtonExtension from '../tiptap-editor-extensions/toggle-button'; import DropListExtension from '../tiptap-editor-extensions/drop-list'; import TextFieldExtension from '../tiptap-editor-extensions/text-field'; import { ClozeDocument, ClozeDocumentParagraph, ClozeDocumentParagraphPart } from '../interfaces/cloze'; +import { ClozeUtils } from './cloze'; export abstract class UnitDefinitionSanitizer { private static expectedUnitVersion: [number, number, number] = @@ -103,6 +104,7 @@ export abstract class UnitDefinitionSanitizer { .includes(newElement.type as string)) { newElement = UnitDefinitionSanitizer.handlePlusOne(newElement as InputElement); } + return newElement as unknown as UIElement; } @@ -185,31 +187,26 @@ export abstract class UnitDefinitionSanitizer { Afterwards element models are added to the JSON. */ private static handleClozeElement(element: Record<string, UIElementValue>): ClozeElement { - if (!element.parts || !element.text) throw Error('Can\'t read Cloze Element'); - const uiElementParts = (element.parts as any[]) - .map((el: any) => el - .filter((el2: { type: string; }) => ['text-field', 'drop-list', 'toggle-button'].includes(el2.type))) - .flat(); + if (!element.document && (!element.parts || !element.text)) throw Error('Can\'t read Cloze Element'); - const replacedText = (element.text as string).replace(/\\i|\\z|\\r/g, (match: string) => { - switch (match) { - case '\\i': - return '<aspect-nodeview-text-field></aspect-nodeview-text-field>'; - case '\\z': - return '<aspect-nodeview-drop-list></aspect-nodeview-drop-list>'; - case '\\r': - return '<aspect-nodeview-toggle-button></aspect-nodeview-toggle-button>'; - default: - throw Error('error in match'); - } - }); + // Version 2.0.0 needs to be sanatized as well because child elements were not sanatized before + if (UnitDefinitionSanitizer.unitDefinitionVersion && UnitDefinitionSanitizer.unitDefinitionVersion[0] >= 3) { + return element as ClozeElement; + } - const editor = new Editor({ - extensions: [StarterKit, ToggleButtonExtension, DropListExtension, TextFieldExtension], - content: replacedText - }); - const doc: { type: string, content: ClozeDocumentParagraph[] } = - editor.getJSON() as { type: string, content: ClozeDocumentParagraph[] }; + let childElements: UIElement[]; + let doc: ClozeDocument; + + if (element.document) { + childElements = ClozeUtils.getClozeChildElements((element as ClozeElement).document); + doc = element.document as ClozeDocument; + } else { + childElements = (element.parts as any[]) + .map((el: any) => el + .filter((el2: { type: string; }) => ['text-field', 'drop-list', 'toggle-button'].includes(el2.type)).value) + .flat(); + doc = UnitDefinitionSanitizer.createClozeDocument(element); + } return { ...element, @@ -225,7 +222,7 @@ export abstract class UnitDefinitionSanitizer { ...paraPart, attrs: { ...paraPart.attrs, - model: UnitDefinitionSanitizer.sanatizeElement(uiElementParts.shift().value) + model: UnitDefinitionSanitizer.sanatizeElement(childElements.shift()!) } } : { @@ -237,6 +234,27 @@ export abstract class UnitDefinitionSanitizer { } as ClozeElement; } + private static createClozeDocument(element: Record<string, UIElementValue>): ClozeDocument { + const replacedText = (element.text as string).replace(/\\i|\\z|\\r/g, (match: string) => { + switch (match) { + case '\\i': + return '<aspect-nodeview-text-field></aspect-nodeview-text-field>'; + case '\\z': + return '<aspect-nodeview-drop-list></aspect-nodeview-drop-list>'; + case '\\r': + return '<aspect-nodeview-toggle-button></aspect-nodeview-toggle-button>'; + default: + throw Error('error in match'); + } + }); + + const editor = new Editor({ + extensions: [StarterKit, ToggleButtonExtension, DropListExtension, TextFieldExtension], + content: replacedText + }); + return editor.getJSON() as ClozeDocument; + } + private static handleDropListElement(element: Record<string, UIElementValue>): DropListElement { const newElement = element; if (newElement.options) { -- GitLab