From 97be7f5150ca089806810cd474e9ad884eeae5f8 Mon Sep 17 00:00:00 2001
From: rhenck <richard.henck@iqb.hu-berlin.de>
Date: Wed, 5 Jan 2022 12:55:56 +0100
Subject: [PATCH] [editor] Add TextEditor NodeView extensions and improve file
 structure

The NodeViews are for the TextEditor to render Angular components, which
now serve as preview instead of the old backslash escaped characters.

All extensions are now placed in a specialized directory. Except the
NodeView stuff which is kept together.
---
 projects/editor/src/app/app.module.ts         |  7 +++
 .../bullet-list.ts}                           |  2 +-
 .../font-size.ts}                             |  2 +-
 .../{ => extensions}/hanging-indent.ts        |  0
 .../text-editor/{ => extensions}/indent.ts    |  0
 .../{ => extensions}/orderedList-extension.ts |  2 +-
 .../{ => extensions}/paragraph-extension.ts   |  2 +-
 .../drop-list-component-extension.ts          | 33 ++++++++++++++
 .../nodeview-drop-list.component.ts           | 24 ++++++++++
 .../nodeview-text-field.component.ts          | 18 ++++++++
 .../nodeview-toggle-button.component.ts       | 18 ++++++++
 .../text-field-component-extension.ts         | 33 ++++++++++++++
 .../toggle-button-component-extension.ts      | 33 ++++++++++++++
 .../rich-text-editor.component.html           | 12 ++---
 .../text-editor/rich-text-editor.component.ts | 45 ++++++++++++++-----
 15 files changed, 209 insertions(+), 22 deletions(-)
 rename projects/editor/src/app/text-editor/{bulletList-extension.ts => extensions/bullet-list.ts} (96%)
 rename projects/editor/src/app/text-editor/{font-size-extension.ts => extensions/font-size.ts} (92%)
 rename projects/editor/src/app/text-editor/{ => extensions}/hanging-indent.ts (100%)
 rename projects/editor/src/app/text-editor/{ => extensions}/indent.ts (100%)
 rename projects/editor/src/app/text-editor/{ => extensions}/orderedList-extension.ts (96%)
 rename projects/editor/src/app/text-editor/{ => extensions}/paragraph-extension.ts (96%)
 create mode 100644 projects/editor/src/app/text-editor/node-views/drop-list-component-extension.ts
 create mode 100644 projects/editor/src/app/text-editor/node-views/nodeview-drop-list.component.ts
 create mode 100644 projects/editor/src/app/text-editor/node-views/nodeview-text-field.component.ts
 create mode 100644 projects/editor/src/app/text-editor/node-views/nodeview-toggle-button.component.ts
 create mode 100644 projects/editor/src/app/text-editor/node-views/text-field-component-extension.ts
 create mode 100644 projects/editor/src/app/text-editor/node-views/toggle-button-component-extension.ts

diff --git a/projects/editor/src/app/app.module.ts b/projects/editor/src/app/app.module.ts
index 60aea9f31..5621dc791 100644
--- a/projects/editor/src/app/app.module.ts
+++ b/projects/editor/src/app/app.module.ts
@@ -53,6 +53,10 @@ import { ElementModelPropertiesComponent } from
   './components/unit-view/page-view/properties-panel/element-model-properties.component';
 import { DropListOptionEditDialogComponent } from './components/dialogs/drop-list-option-edit-dialog.component';
 
+import { NodeviewToggleButtonComponent } from './text-editor/node-views/nodeview-toggle-button.component';
+import { NodeviewTextFieldComponent } from './text-editor/node-views/nodeview-text-field.component';
+import { NodeviewDropListComponent } from './text-editor/node-views/nodeview-drop-list.component';
+
 @NgModule({
   declarations: [
     AppComponent,
@@ -68,6 +72,9 @@ import { DropListOptionEditDialogComponent } from './components/dialogs/drop-lis
     SectionStaticComponent,
     SectionDynamicComponent,
     RichTextEditorComponent,
+    NodeviewToggleButtonComponent,
+    NodeviewTextFieldComponent,
+    NodeviewDropListComponent,
     ElementStylePropertiesComponent,
     ElementSizingPropertiesComponent,
     ConfirmationDialogComponent,
diff --git a/projects/editor/src/app/text-editor/bulletList-extension.ts b/projects/editor/src/app/text-editor/extensions/bullet-list.ts
similarity index 96%
rename from projects/editor/src/app/text-editor/bulletList-extension.ts
rename to projects/editor/src/app/text-editor/extensions/bullet-list.ts
index cb37f6fdf..59c8679e2 100644
--- a/projects/editor/src/app/text-editor/bulletList-extension.ts
+++ b/projects/editor/src/app/text-editor/extensions/bullet-list.ts
@@ -10,7 +10,7 @@ declare module '@tiptap/core' {
   }
 }
 
-export const bulletListExtension = BulletList.extend({
+export const BulletListExtension = BulletList.extend({
   addAttributes() {
     return {
       listStyle: {
diff --git a/projects/editor/src/app/text-editor/font-size-extension.ts b/projects/editor/src/app/text-editor/extensions/font-size.ts
similarity index 92%
rename from projects/editor/src/app/text-editor/font-size-extension.ts
rename to projects/editor/src/app/text-editor/extensions/font-size.ts
index 94bcdaeb7..6a65bfcce 100644
--- a/projects/editor/src/app/text-editor/font-size-extension.ts
+++ b/projects/editor/src/app/text-editor/extensions/font-size.ts
@@ -9,7 +9,7 @@ declare module '@tiptap/core' {
   }
 }
 
-export const fontSizeExtension = TextStyle.extend({
+export const FontSizeExtension = TextStyle.extend({
   addAttributes() {
     return {
       fontSize: {
diff --git a/projects/editor/src/app/text-editor/hanging-indent.ts b/projects/editor/src/app/text-editor/extensions/hanging-indent.ts
similarity index 100%
rename from projects/editor/src/app/text-editor/hanging-indent.ts
rename to projects/editor/src/app/text-editor/extensions/hanging-indent.ts
diff --git a/projects/editor/src/app/text-editor/indent.ts b/projects/editor/src/app/text-editor/extensions/indent.ts
similarity index 100%
rename from projects/editor/src/app/text-editor/indent.ts
rename to projects/editor/src/app/text-editor/extensions/indent.ts
diff --git a/projects/editor/src/app/text-editor/orderedList-extension.ts b/projects/editor/src/app/text-editor/extensions/orderedList-extension.ts
similarity index 96%
rename from projects/editor/src/app/text-editor/orderedList-extension.ts
rename to projects/editor/src/app/text-editor/extensions/orderedList-extension.ts
index cffc604f3..b3785201e 100644
--- a/projects/editor/src/app/text-editor/orderedList-extension.ts
+++ b/projects/editor/src/app/text-editor/extensions/orderedList-extension.ts
@@ -10,7 +10,7 @@ declare module '@tiptap/core' {
   }
 }
 
-export const orderedListExtension = OrderedList.extend({
+export const OrderedListExtension = OrderedList.extend({
   addAttributes() {
     return {
       listStyle: {
diff --git a/projects/editor/src/app/text-editor/paragraph-extension.ts b/projects/editor/src/app/text-editor/extensions/paragraph-extension.ts
similarity index 96%
rename from projects/editor/src/app/text-editor/paragraph-extension.ts
rename to projects/editor/src/app/text-editor/extensions/paragraph-extension.ts
index 52415f312..a3c0f4b3e 100644
--- a/projects/editor/src/app/text-editor/paragraph-extension.ts
+++ b/projects/editor/src/app/text-editor/extensions/paragraph-extension.ts
@@ -10,7 +10,7 @@ declare module '@tiptap/core' {
   }
 }
 
-export const customParagraph = Paragraph.extend({
+export const paragraphExtension = Paragraph.extend({
   addAttributes() {
     return {
       margin: {
diff --git a/projects/editor/src/app/text-editor/node-views/drop-list-component-extension.ts b/projects/editor/src/app/text-editor/node-views/drop-list-component-extension.ts
new file mode 100644
index 000000000..46d01fce5
--- /dev/null
+++ b/projects/editor/src/app/text-editor/node-views/drop-list-component-extension.ts
@@ -0,0 +1,33 @@
+import { Injector } from '@angular/core';
+import { Node, mergeAttributes } from '@tiptap/core';
+import { AngularNodeViewRenderer } from 'ngx-tiptap';
+
+import { NodeviewDropListComponent } from './nodeview-drop-list.component';
+
+const DropListComponentExtension = (injector: Injector): Node => {
+  return Node.create({
+    group: 'inline',
+    inline: true,
+    name: 'DropList',
+
+    addAttributes() {
+      return {
+        id: {
+          default: 'will be generated'
+        }
+      };
+    },
+
+    parseHTML() {
+      return [{ tag: 'app-nodeview-drop-list' }];
+    },
+    renderHTML({ HTMLAttributes }) {
+      return ['app-nodeview-drop-list', mergeAttributes(HTMLAttributes)];
+    },
+    addNodeView() {
+      return AngularNodeViewRenderer(NodeviewDropListComponent, { injector });
+    }
+  });
+};
+
+export default DropListComponentExtension;
diff --git a/projects/editor/src/app/text-editor/node-views/nodeview-drop-list.component.ts b/projects/editor/src/app/text-editor/node-views/nodeview-drop-list.component.ts
new file mode 100644
index 000000000..32dc0dc50
--- /dev/null
+++ b/projects/editor/src/app/text-editor/node-views/nodeview-drop-list.component.ts
@@ -0,0 +1,24 @@
+import { Component } from '@angular/core';
+import { AngularNodeViewComponent } from 'ngx-tiptap';
+import { DropListSimpleElement } from '../../../../../common/ui-elements/drop-list-simple/drop-list-simple';
+
+@Component({
+  selector: 'app-nodeview-drop-list',
+  template: `
+    <div [style.display]="'inline-block'"
+         [style.vertical-align]="'middle'"
+         [style.width.px]="model.width"
+         [style.height.px]="model.height">
+      <app-drop-list-simple [elementModel]="model"
+                            [matTooltip]="'ID: ' + node.attrs.id">
+      </app-drop-list-simple>
+    </div>
+  `
+})
+export class NodeviewDropListComponent extends AngularNodeViewComponent {
+  model: DropListSimpleElement = new DropListSimpleElement({
+    type: 'drop-list',
+    height: 25,
+    width: 100
+  });
+}
diff --git a/projects/editor/src/app/text-editor/node-views/nodeview-text-field.component.ts b/projects/editor/src/app/text-editor/node-views/nodeview-text-field.component.ts
new file mode 100644
index 000000000..9c57cdfd5
--- /dev/null
+++ b/projects/editor/src/app/text-editor/node-views/nodeview-text-field.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { AngularNodeViewComponent } from 'ngx-tiptap';
+import { TextFieldSimpleElement } from '../../../../../common/ui-elements/textfield-simple/text-field-simple-element';
+
+@Component({
+  selector: 'app-nodeview-text-field',
+  template: `
+    <app-text-field-simple [style.display]="'inline-block'"
+                           [elementModel]="model"
+                           [matTooltip]="'ID: ' + node.attrs.id">
+    </app-text-field-simple>
+  `
+})
+export class NodeviewTextFieldComponent extends AngularNodeViewComponent {
+  model: TextFieldSimpleElement = new TextFieldSimpleElement({
+    type: 'text-field'
+  });
+}
diff --git a/projects/editor/src/app/text-editor/node-views/nodeview-toggle-button.component.ts b/projects/editor/src/app/text-editor/node-views/nodeview-toggle-button.component.ts
new file mode 100644
index 000000000..91be96e9b
--- /dev/null
+++ b/projects/editor/src/app/text-editor/node-views/nodeview-toggle-button.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { AngularNodeViewComponent } from 'ngx-tiptap';
+import { ToggleButtonElement } from '../../../../../common/ui-elements/toggle-button/toggle-button';
+
+@Component({
+  selector: 'app-nodeview-toggle-button',
+  template: `
+    <app-toggle-button [style.display]="'inline-block'"
+                       [elementModel]="model"
+                       [matTooltip]="'ID: ' + node.attrs.id">
+    </app-toggle-button>
+  `
+})
+export class NodeviewToggleButtonComponent extends AngularNodeViewComponent {
+  model: ToggleButtonElement = new ToggleButtonElement({
+    type: 'toggle-button'
+  });
+}
diff --git a/projects/editor/src/app/text-editor/node-views/text-field-component-extension.ts b/projects/editor/src/app/text-editor/node-views/text-field-component-extension.ts
new file mode 100644
index 000000000..8597a2d36
--- /dev/null
+++ b/projects/editor/src/app/text-editor/node-views/text-field-component-extension.ts
@@ -0,0 +1,33 @@
+import { Injector } from '@angular/core';
+import { Node, mergeAttributes } from '@tiptap/core';
+import { AngularNodeViewRenderer } from 'ngx-tiptap';
+
+import { NodeviewTextFieldComponent } from './nodeview-text-field.component';
+
+const TextFieldComponentExtension = (injector: Injector): Node => {
+  return Node.create({
+    group: 'inline',
+    inline: true,
+    name: 'TextField',
+
+    addAttributes() {
+      return {
+        id: {
+          default: 'will be generated'
+        }
+      };
+    },
+
+    parseHTML() {
+      return [{ tag: 'app-nodeview-text-field' }];
+    },
+    renderHTML({ HTMLAttributes }) {
+      return ['app-nodeview-text-field', mergeAttributes(HTMLAttributes)];
+    },
+    addNodeView() {
+      return AngularNodeViewRenderer(NodeviewTextFieldComponent, { injector });
+    }
+  });
+};
+
+export default TextFieldComponentExtension;
diff --git a/projects/editor/src/app/text-editor/node-views/toggle-button-component-extension.ts b/projects/editor/src/app/text-editor/node-views/toggle-button-component-extension.ts
new file mode 100644
index 000000000..8c049208b
--- /dev/null
+++ b/projects/editor/src/app/text-editor/node-views/toggle-button-component-extension.ts
@@ -0,0 +1,33 @@
+import { Injector } from '@angular/core';
+import { Node, mergeAttributes } from '@tiptap/core';
+import { AngularNodeViewRenderer } from 'ngx-tiptap';
+
+import { NodeviewToggleButtonComponent } from './nodeview-toggle-button.component';
+
+const ToggleButtonComponentExtension = (injector: Injector): Node => {
+  return Node.create({
+    group: 'inline',
+    inline: true,
+    name: 'ToggleButton',
+
+    addAttributes() {
+      return {
+        id: {
+          default: 'will be generated'
+        }
+      };
+    },
+
+    parseHTML() {
+      return [{ tag: 'app-nodeview-toggle-button' }];
+    },
+    renderHTML({ HTMLAttributes }) {
+      return ['app-nodeview-toggle-button', mergeAttributes(HTMLAttributes)];
+    },
+    addNodeView() {
+      return AngularNodeViewRenderer(NodeviewToggleButtonComponent, { injector });
+    }
+  });
+};
+
+export default ToggleButtonComponentExtension;
diff --git a/projects/editor/src/app/text-editor/rich-text-editor.component.html b/projects/editor/src/app/text-editor/rich-text-editor.component.html
index 837b41ce0..869c43e66 100644
--- a/projects/editor/src/app/text-editor/rich-text-editor.component.html
+++ b/projects/editor/src/app/text-editor/rich-text-editor.component.html
@@ -245,16 +245,16 @@
     </button>
   </div>
   <div *ngIf="showCloseElements" fxLayout="row" fxLayoutAlign="space-around center" >
-    <button mat-icon-button matTooltip="Eingabefeld (\i)" [matTooltipShowDelay]="300"
-            (click)="insertSpecialChar('\\i')">
+    <button mat-icon-button matTooltip="Eingabefeld" [matTooltipShowDelay]="300"
+            (click)="insertTextField()">
       <mat-icon>text_fields</mat-icon>
     </button>
-    <button mat-icon-button matTooltip="Ablegefeld (\z)" [matTooltipShowDelay]="300"
-            (click)="insertSpecialChar('\\z')">
+    <button mat-icon-button matTooltip="Ablegefeld" [matTooltipShowDelay]="300"
+            (click)="insertDropList()">
       <mat-icon>drag_indicator</mat-icon>
     </button>
-    <button mat-icon-button matTooltip="Optionsfeld (\r)" [matTooltipShowDelay]="300"
-            (click)="insertSpecialChar('\\r')">
+    <button mat-icon-button matTooltip="Optionsfeld" [matTooltipShowDelay]="300"
+            (click)="insertToggleButton()">
       <mat-icon>radio_button_checked</mat-icon>
     </button>
   </div>
diff --git a/projects/editor/src/app/text-editor/rich-text-editor.component.ts b/projects/editor/src/app/text-editor/rich-text-editor.component.ts
index e0038ceb3..4c97b21e3 100644
--- a/projects/editor/src/app/text-editor/rich-text-editor.component.ts
+++ b/projects/editor/src/app/text-editor/rich-text-editor.component.ts
@@ -1,6 +1,6 @@
 import {
   Component, EventEmitter, Input, Output, ViewEncapsulation,
-  AfterViewInit
+  AfterViewInit, Injector
 } from '@angular/core';
 import { Editor } from '@tiptap/core';
 import StarterKit from '@tiptap/starter-kit';
@@ -14,14 +14,18 @@ import { TextAlign } from '@tiptap/extension-text-align';
 import { Heading } from '@tiptap/extension-heading';
 import { Image } from '@tiptap/extension-image';
 import { Blockquote } from '@tiptap/extension-blockquote';
-import { Indent } from './indent';
-import { HangingIndent } from './hanging-indent';
-import { customParagraph } from './paragraph-extension';
-import { fontSizeExtension } from './font-size-extension';
-import { bulletListExtension } from './bulletList-extension';
-import { orderedListExtension } from './orderedList-extension';
+import { Indent } from './extensions/indent';
+import { HangingIndent } from './extensions/hanging-indent';
+import { paragraphExtension } from './extensions/paragraph-extension';
+import { FontSizeExtension } from './extensions/font-size';
+import { BulletListExtension } from './extensions/bullet-list';
+import { OrderedListExtension } from './extensions/orderedList-extension';
 import { FileService } from '../services/file.service';
 
+import ToggleButtonComponentExtension from './node-views/toggle-button-component-extension';
+import DropListComponentExtension from './node-views/drop-list-component-extension';
+import TextFieldComponentExtension from './node-views/text-field-component-extension';
+
 @Component({
   selector: 'app-rich-text-editor',
   templateUrl: './rich-text-editor.component.html',
@@ -58,10 +62,10 @@ export class RichTextEditorComponent implements AfterViewInit {
       Heading.configure({
         levels: [1, 2, 3, 4]
       }),
-      customParagraph,
-      fontSizeExtension,
-      bulletListExtension,
-      orderedListExtension,
+      paragraphExtension,
+      FontSizeExtension,
+      BulletListExtension,
+      OrderedListExtension,
       HangingIndent,
       Image.configure({
         inline: true,
@@ -69,10 +73,15 @@ export class RichTextEditorComponent implements AfterViewInit {
           style: 'display: inline-block; height: 1em; vertical-align: middle'
         }
       }),
-      Blockquote
+      Blockquote,
+      ToggleButtonComponentExtension(this.injector),
+      DropListComponentExtension(this.injector),
+      TextFieldComponentExtension(this.injector)
     ]
   });
 
+  constructor(private injector: Injector) { }
+
   ngAfterViewInit(): void {
     this.editor.commands.focus();
   }
@@ -186,4 +195,16 @@ export class RichTextEditorComponent implements AfterViewInit {
   toggleBlockquote(): void {
     this.editor.commands.toggleBlockquote();
   }
+
+  insertToggleButton(): void {
+    this.editor.commands.insertContent('<app-nodeview-toggle-button></app-nodeview-toggle-button>');
+  }
+
+  insertDropList(): void {
+    this.editor.commands.insertContent('<app-nodeview-drop-list></app-nodeview-drop-list>');
+  }
+
+  insertTextField(): void {
+    this.editor.commands.insertContent('<app-nodeview-text-field></app-nodeview-text-field>');
+  }
 }
-- 
GitLab