From 60918a3339a0320737eddbc99d43ae6ebef4ac12 Mon Sep 17 00:00:00 2001
From: jojohoch <joachim.hoch@iqb.hu-berlin.de>
Date: Wed, 30 Nov 2022 15:53:42 +0100
Subject: [PATCH] Enable adding images with surrounding text in the TipTap
 editor

---
 .../app/text-editor/extensions/block-image.ts | 51 +++++++++++++++++++
 .../text-editor/extensions/inline-image.ts    | 37 ++++++++++++++
 .../rich-text-editor.component.html           | 14 ++++-
 .../text-editor/rich-text-editor.component.ts | 32 ++++++++----
 4 files changed, 123 insertions(+), 11 deletions(-)
 create mode 100644 projects/editor/src/app/text-editor/extensions/block-image.ts
 create mode 100644 projects/editor/src/app/text-editor/extensions/inline-image.ts

diff --git a/projects/editor/src/app/text-editor/extensions/block-image.ts b/projects/editor/src/app/text-editor/extensions/block-image.ts
new file mode 100644
index 000000000..7397997cc
--- /dev/null
+++ b/projects/editor/src/app/text-editor/extensions/block-image.ts
@@ -0,0 +1,51 @@
+import { Image } from '@tiptap/extension-image';
+
+declare module '@tiptap/core' {
+  interface Commands<ReturnType> {
+    blockImage: {
+      insertBlockImage: (options: { src: string, alt?: string, title?: string, style?: string }) => ReturnType,
+    }
+  }
+}
+
+export const BlockImage = Image.extend({
+  name: 'blockImage',
+  addOptions() {
+    return {
+      ...this.parent?.(),
+      allowBase64: true
+    };
+  },
+  addAttributes() {
+    return {
+      ...this.parent?.(),
+      style: {
+        default: null,
+        renderHTML: attributes => {
+          if (attributes.style && !this.options.inline) {
+            return {
+              'data-style': attributes.style,
+              style: attributes.style
+            };
+          }
+          return {};
+        }
+      }
+    };
+  },
+  parseHTML() {
+    return [
+      {
+        tag: 'img',
+        getAttrs: node => (node as HTMLElement).style.verticalAlign !== 'middle' && !this.options.inline && null
+      }
+    ];
+  },
+  addCommands() {
+    return {
+      insertBlockImage: (
+        options: { src: string, alt?: string, title?: string, style?: string }
+      ) => ({ commands }) => commands.insertContent({ type: this.name, attrs: options })
+    };
+  }
+});
diff --git a/projects/editor/src/app/text-editor/extensions/inline-image.ts b/projects/editor/src/app/text-editor/extensions/inline-image.ts
new file mode 100644
index 000000000..69032bfda
--- /dev/null
+++ b/projects/editor/src/app/text-editor/extensions/inline-image.ts
@@ -0,0 +1,37 @@
+import { Image } from '@tiptap/extension-image';
+
+declare module '@tiptap/core' {
+  interface Commands<ReturnType> {
+    inlineImage: {
+      insertInlineImage: (options: { src: string, alt?: string, title?: string }) => ReturnType,
+    }
+  }
+}
+
+export const InlineImage = Image.extend({
+  name: 'inlineImage',
+  addOptions() {
+    return {
+      inline: true,
+      allowBase64: true,
+      HTMLAttributes: {
+        style: 'display: inline-block; height: 1em; vertical-align: middle'
+      }
+    };
+  },
+  parseHTML() {
+    return [
+      {
+        tag: 'img',
+        getAttrs: node => (node as HTMLElement).style.verticalAlign === 'middle' && this.options.inline && null
+      }
+    ];
+  },
+  addCommands() {
+    return {
+      insertInlineImage: (
+        options: { src: string, alt?: string, title?: string }
+      ) => ({ commands }) => commands.insertContent({ type: this.name, attrs: options })
+    };
+  }
+});
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 0ebce8d6f..c0a704e72 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
@@ -253,10 +253,22 @@
         <button mat-button (click)="insertSpecialChar('&hearts;')">&hearts;</button>
         <button mat-button (click)="insertSpecialChar('&diams;')">&diams;</button>
       </mat-menu>
-      <button mat-icon-button matTooltip="Bild" [matTooltipShowDelay]="300"
+      <button mat-icon-button matTooltip="Bild in Zeile einfügen" [matTooltipShowDelay]="300"
               (click)="insertImage()">
+        <mat-icon>burst_mode</mat-icon>
+      </button>
+      <button mat-icon-button matTooltip="Bild in eigenem Absatz einfügen" [matTooltipShowDelay]="300"
+              (click)="insertBlockImage('none')">
         <mat-icon>image</mat-icon>
       </button>
+      <button mat-icon-button matTooltip="Bild rechts einfügen" [matTooltipShowDelay]="300"
+              (click)="insertBlockImage('right')">
+        <mat-icon style="transform: scaleX(-1);">art_track</mat-icon>
+      </button>
+      <button mat-icon-button matTooltip="Bild links einfügen" [matTooltipShowDelay]="300"
+              (click)="insertBlockImage('left')">
+        <mat-icon>art_track</mat-icon>
+      </button>
       <button mat-icon-button matTooltip="Zitat" [matTooltipShowDelay]="300"
               [class.active]="editor.isActive('blockquote')"
               (click)="toggleBlockquote()">
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 84fc5d46e..384b9d652 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
@@ -11,7 +11,6 @@ import { Color } from '@tiptap/extension-color';
 import { Highlight } from '@tiptap/extension-highlight';
 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 { Document } from '@tiptap/extension-document';
 import { Text } from '@tiptap/extension-text';
@@ -21,6 +20,8 @@ import { Italic } from '@tiptap/extension-italic';
 import { Strike } from '@tiptap/extension-strike';
 import { FileService } from 'common/services/file.service';
 import ButtonComponentExtension from 'editor/src/app/text-editor/angular-node-views/button-component-extension';
+import { BlockImage } from 'editor/src/app/text-editor/extensions/block-image';
+import { InlineImage } from 'editor/src/app/text-editor/extensions/inline-image';
 import { AnchorId } from './extensions/anchorId';
 import { Indent } from './extensions/indent';
 import { HangingIndent } from './extensions/hanging-indent';
@@ -75,13 +76,8 @@ export class RichTextEditorComponent implements OnInit, AfterViewInit {
     BulletListExtension,
     OrderedListExtension,
     HangingIndent,
-    Image.configure({
-      inline: true,
-      allowBase64: true,
-      HTMLAttributes: {
-        style: 'display: inline-block; height: 1em; vertical-align: middle'
-      }
-    }),
+    InlineImage,
+    BlockImage,
     Blockquote
   ];
 
@@ -223,7 +219,24 @@ export class RichTextEditorComponent implements OnInit, AfterViewInit {
 
   async insertImage(): Promise<void> {
     const mediaSrc = await FileService.loadImage();
-    this.editor.commands.setImage({ src: mediaSrc });
+    this.editor.commands.insertInlineImage({ src: mediaSrc });
+  }
+
+  async insertBlockImage(alignment: 'none' | 'right' | 'left'): Promise<void> {
+    const mediaSrc = await FileService.loadImage();
+    switch (alignment) {
+      case 'left': {
+        this.editor.commands.insertBlockImage({ src: mediaSrc, style: 'float: left; margin-right: 10px;' });
+        break;
+      }
+      case 'right': {
+        this.editor.commands.insertBlockImage({ src: mediaSrc, style: 'float: right; margin-left: 10px' });
+        break;
+      }
+      default: {
+        this.editor.commands.insertBlockImage({ src: mediaSrc });
+      }
+    }
   }
 
   toggleBlockquote(): void {
@@ -246,7 +259,6 @@ export class RichTextEditorComponent implements OnInit, AfterViewInit {
   }
 
   insertButton() {
-    console.log('inserting button');
     this.editor.commands.insertContent('<aspect-nodeview-button></aspect-nodeview-button>');
     this.editor.commands.focus();
   }
-- 
GitLab