From 89befccda8f1a6fa83070d06d68400f5ce149731 Mon Sep 17 00:00:00 2001 From: rhenck <richard.henck@iqb.hu-berlin.de> Date: Wed, 1 Jun 2022 20:45:07 +0200 Subject: [PATCH] Refactor cloze element's document handling - Move methods from cloze parser helper library to cloze class. - Cloze children are set up with a special placeholder which is then recognized and replaced by a new ID. And the element is recreated (like before) to have a proper element model class, which the TipTap editor doesn't provide. --- .../elements/compound-elements/cloze/cloze.ts | 66 +++++++++++++++---- .../editor/src/app/services/unit.service.ts | 2 - .../drop-list-component-extension.ts | 9 +-- .../text-field-component-extension.ts | 3 +- .../toggle-button-component-extension.ts | 8 ++- projects/editor/src/app/util/cloze-parser.ts | 62 ----------------- 6 files changed, 64 insertions(+), 86 deletions(-) delete mode 100644 projects/editor/src/app/util/cloze-parser.ts diff --git a/projects/common/models/elements/compound-elements/cloze/cloze.ts b/projects/common/models/elements/compound-elements/cloze/cloze.ts index b129ac832..4d13bf342 100644 --- a/projects/common/models/elements/compound-elements/cloze/cloze.ts +++ b/projects/common/models/elements/compound-elements/cloze/cloze.ts @@ -4,7 +4,7 @@ import { InputElement, PositionedUIElement, PositionProperties, - UIElement + UIElement, UIElementValue } from 'common/models/elements/element'; import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; @@ -17,6 +17,7 @@ import { DropListSimpleElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; import { ToggleButtonElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button'; +import { IDManager } from 'common/util/id-manager'; export class ClozeElement extends CompoundElement implements PositionedUIElement { document: ClozeDocument = { type: 'doc', content: [] }; @@ -26,17 +27,53 @@ export class ClozeElement extends CompoundElement implements PositionedUIElement lineHeight: number; }; - constructor(element: Partial<ClozeElement>, ...args: unknown[]) { - super({ height: 200, ...element }, ...args); + constructor(element: Partial<ClozeElement>, idManager?: IDManager) { + super({ height: 200, ...element }, idManager); if (element.columnCount) this.columnCount = element.columnCount; - this.document = this.initDocument(element); + this.document = this.initDocument(element, idManager); this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps({ lineHeight: 150, ...element.styling }) }; } - private initDocument(element: Partial<ClozeElement>): ClozeDocument { + setProperty(property: string, value: UIElementValue): void { + if (property === 'document') { + this.document = value as ClozeDocument; + + this.document.content.forEach((node: any) => { + if (node.type === 'paragraph' || node.type === 'heading') { + ClozeElement.createSubNodeElements(node); + } else if (node.type === 'bulletList' || node.type === 'orderedList') { + node.content.forEach((listItem: any) => { + listItem.content.forEach((listItemParagraph: any) => { + ClozeElement.createSubNodeElements(listItemParagraph); + }); + }); + } else if (node.type === 'blockquote') { + node.content.forEach((blockQuoteItem: any) => { + ClozeElement.createSubNodeElements(blockQuoteItem); + }); + } + }); + + } else { + super.setProperty(property, value); + } + } + + private static createSubNodeElements(node: any) { + node.content?.forEach((subNode: any) => { + if (['ToggleButton', 'DropList', 'TextField'].includes(subNode.type) && + subNode.attrs.model.id === 'cloze-child-id-placeholder') { + const newID = IDManager.getInstance().getNewID(subNode.attrs.model.type); + subNode.attrs.model = + ClozeElement.createChildElement({ ...subNode.attrs.model, id: newID }, IDManager.getInstance()); + } + }); + } + + private initDocument(element: Partial<ClozeElement>, idManager?: IDManager): ClozeDocument { return { ...element.document, content: element.document?.content ? element.document.content @@ -49,7 +86,7 @@ export class ClozeElement extends CompoundElement implements PositionedUIElement ...paraPart, attrs: { ...paraPart.attrs, - model: ClozeElement.createChildElement(paraPart.attrs?.model as InputElement) + model: ClozeElement.createChildElement(paraPart.attrs?.model as InputElement, idManager) } } : { @@ -65,8 +102,12 @@ export class ClozeElement extends CompoundElement implements PositionedUIElement } getChildElements(): UIElement[] { - if (!this.document) return []; - const clozeDocument: ClozeDocument = this.document; + return ClozeElement.getDocumentChildElements(this.documnent); + } + + static getDocumentChildElements(document: ClozeDocument): UIElement[] { + if (!document) return []; + const clozeDocument: ClozeDocument = document; const elementList: InputElement[] = []; clozeDocument.content.forEach((documentPart: ClozeDocumentParagraph) => { if (documentPart.type === 'paragraph' || documentPart.type === 'heading') { @@ -86,21 +127,22 @@ export class ClozeElement extends CompoundElement implements PositionedUIElement return elementList; } - private static createChildElement(elementModel: Partial<UIElement>): InputElement { + private static createChildElement(elementModel: Partial<UIElement>, idManager?: IDManager): InputElement { let newElement: InputElement; switch (elementModel.type) { case 'text-field-simple': - newElement = new TextFieldSimpleElement({ type: elementModel.type, elementModel }) as InputElement; + newElement = new TextFieldSimpleElement(elementModel as TextFieldSimpleElement, idManager); break; case 'drop-list-simple': - newElement = new DropListSimpleElement({ type: elementModel.type, elementModel }) as InputElement; + newElement = new DropListSimpleElement(elementModel as DropListSimpleElement, idManager); break; case 'toggle-button': - newElement = new ToggleButtonElement({ type: elementModel.type, elementModel }) as InputElement; + newElement = new ToggleButtonElement(elementModel as ToggleButtonElement, idManager); break; default: throw new Error(`ElementType ${elementModel.type} not found!`); } + // console.log('newElement', newElement); return newElement; } diff --git a/projects/editor/src/app/services/unit.service.ts b/projects/editor/src/app/services/unit.service.ts index 8aa11ba95..7d68ba2c9 100644 --- a/projects/editor/src/app/services/unit.service.ts +++ b/projects/editor/src/app/services/unit.service.ts @@ -231,8 +231,6 @@ export class UnitService { IDManager.getInstance().addID(value as string); element.id = value as string; } - } else if (property === 'document') { - element[property] = ClozeParser.setMissingIDs(value as ClozeDocument); } else if (element.type === 'likert' && property === 'columns') { (element as LikertElement).rows.forEach(row => { row.columnCount = (element as LikertElement).columns.length; diff --git a/projects/editor/src/app/text-editor/angular-node-views/drop-list-component-extension.ts b/projects/editor/src/app/text-editor/angular-node-views/drop-list-component-extension.ts index b9ae078e1..cf7df19a0 100644 --- a/projects/editor/src/app/text-editor/angular-node-views/drop-list-component-extension.ts +++ b/projects/editor/src/app/text-editor/angular-node-views/drop-list-component-extension.ts @@ -2,11 +2,8 @@ import { Injector } from '@angular/core'; import { Node, mergeAttributes } from '@tiptap/core'; import { AngularNodeViewRenderer } from 'ngx-tiptap'; import { DropListNodeviewComponent } from './drop-list-nodeview.component'; - -import { ElementFactory } from 'common/util/element.factory'; -import { - DropListSimpleElement -} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; +import { DropListSimpleElement } + from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; const DropListComponentExtension = (injector: Injector): Node => { return Node.create({ @@ -17,7 +14,7 @@ const DropListComponentExtension = (injector: Injector): Node => { addAttributes() { return { model: { - default: new DropListSimpleElement({ type: 'drop-list-simple' }) + default: new DropListSimpleElement({ type: 'drop-list-simple', id: 'cloze-child-id-placeholder' }) } }; }, diff --git a/projects/editor/src/app/text-editor/angular-node-views/text-field-component-extension.ts b/projects/editor/src/app/text-editor/angular-node-views/text-field-component-extension.ts index 44a7bc45d..9e57277b8 100644 --- a/projects/editor/src/app/text-editor/angular-node-views/text-field-component-extension.ts +++ b/projects/editor/src/app/text-editor/angular-node-views/text-field-component-extension.ts @@ -2,7 +2,6 @@ import { Injector } from '@angular/core'; import { Node, mergeAttributes } from '@tiptap/core'; import { AngularNodeViewRenderer } from 'ngx-tiptap'; import { TextFieldNodeviewComponent } from './text-field-nodeview.component'; -import { ElementFactory } from 'common/util/element.factory'; import { TextFieldSimpleElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple'; @@ -16,7 +15,7 @@ const TextFieldComponentExtension = (injector: Injector): Node => { addAttributes() { return { model: { - default: new TextFieldSimpleElement({ type: 'text-field-simple' }) + default: new TextFieldSimpleElement({ type: 'text-field-simple', id: 'cloze-child-id-placeholder' }) } }; }, diff --git a/projects/editor/src/app/text-editor/angular-node-views/toggle-button-component-extension.ts b/projects/editor/src/app/text-editor/angular-node-views/toggle-button-component-extension.ts index 52b4b381e..ca5dc0d9e 100644 --- a/projects/editor/src/app/text-editor/angular-node-views/toggle-button-component-extension.ts +++ b/projects/editor/src/app/text-editor/angular-node-views/toggle-button-component-extension.ts @@ -3,7 +3,6 @@ import { Node, mergeAttributes } from '@tiptap/core'; import { AngularNodeViewRenderer } from 'ngx-tiptap'; import { ToggleButtonNodeviewComponent } from './toggle-button-nodeview.component'; -import { ElementFactory } from 'common/util/element.factory'; import { ToggleButtonElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button'; const ToggleButtonComponentExtension = (injector: Injector): Node => { @@ -15,7 +14,12 @@ const ToggleButtonComponentExtension = (injector: Injector): Node => { addAttributes() { return { model: { - default: new ToggleButtonElement({ type: 'toggle-button', height: 25, width: 100 }) + default: new ToggleButtonElement({ + type: 'toggle-button', + height: 25, + width: 100, + id: 'cloze-child-id-placeholder' + }) } }; }, diff --git a/projects/editor/src/app/util/cloze-parser.ts b/projects/editor/src/app/util/cloze-parser.ts deleted file mode 100644 index 0fd1c876b..000000000 --- a/projects/editor/src/app/util/cloze-parser.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { InputElement, UIElement } from 'common/models/elements/element'; -import { ClozeDocument } from 'common/models/elements/compound-elements/cloze/cloze'; -import { ElementFactory } from 'common/util/element.factory'; -import { IDManager } from 'common/util/id-manager'; -import { - TextFieldSimpleElement -} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple'; -import { - DropListSimpleElement -} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; -import { ToggleButtonElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button'; - -export abstract class ClozeParser { - static setMissingIDs(clozeJSON: ClozeDocument): ClozeDocument { - clozeJSON.content.forEach((node: any) => { - if (node.type === 'paragraph' || node.type === 'heading') { - ClozeParser.createSubNodeElements(node); - } else if (node.type === 'bulletList' || node.type === 'orderedList') { - node.content.forEach((listItem: any) => { - listItem.content.forEach((listItemParagraph: any) => { - ClozeParser.createSubNodeElements(listItemParagraph); - }); - }); - } else if (node.type === 'blockquote') { - node.content.forEach((blockQuoteItem: any) => { - ClozeParser.createSubNodeElements(blockQuoteItem); - }); - } - }); - return clozeJSON; - } - - // create element anew because the TextEditor can't create multiple element instances - private static createSubNodeElements(node: any) { - const idService = IDManager.getInstance(); - node.content?.forEach((subNode: any) => { - if (['ToggleButton', 'DropList', 'TextField'].includes(subNode.type) && - subNode.attrs.model.id === 'id_placeholder') { - subNode.attrs.model = ClozeParser.createElement(subNode.attrs.model); - subNode.attrs.model.id = idService.getNewID(subNode.attrs.model.type); - } - }); - } - - private static createElement(elementModel: Partial<UIElement>): InputElement { - let newElement: InputElement; - switch (elementModel.type) { - case 'text-field-simple': - newElement = new TextFieldSimpleElement({ type: elementModel.type, elementModel }) as InputElement; - break; - case 'drop-list-simple': - newElement = new DropListSimpleElement({ type: elementModel.type, elementModel }) as InputElement; - break; - case 'toggle-button': - newElement = new ToggleButtonElement({ type: elementModel.type, elementModel }) as InputElement; - break; - default: - throw new Error(`ElementType ${elementModel.type} not found!`); - } - return newElement; - } -} -- GitLab