From 040ca53620212d94cf102af44cbb6512e3d19b0a Mon Sep 17 00:00:00 2001
From: rhenck <richard.henck@iqb.hu-berlin.de>
Date: Mon, 25 Oct 2021 17:28:17 +0200
Subject: [PATCH] [editor] TextEditor: Add extensions for list styles

Before it was done with a dysfunctional hack. Now with proper extensions
to existing (tiptap-node-)extensions.
Also reads existing style and sets select-element accordingly.
---
 .../app/text-editor/bulletList-extension.ts   | 51 ++++++++++++++++++
 .../app/text-editor/orderedList-extension.ts  | 53 +++++++++++++++++++
 .../rich-text-editor.component.html           |  6 ++-
 .../text-editor/rich-text-editor.component.ts | 18 ++++---
 4 files changed, 119 insertions(+), 9 deletions(-)
 create mode 100644 projects/editor/src/app/text-editor/bulletList-extension.ts
 create mode 100644 projects/editor/src/app/text-editor/orderedList-extension.ts

diff --git a/projects/editor/src/app/text-editor/bulletList-extension.ts b/projects/editor/src/app/text-editor/bulletList-extension.ts
new file mode 100644
index 000000000..27b866053
--- /dev/null
+++ b/projects/editor/src/app/text-editor/bulletList-extension.ts
@@ -0,0 +1,51 @@
+import { Command } from '@tiptap/core';
+import { Transaction } from 'prosemirror-state';
+import { BulletList } from '@tiptap/extension-bullet-list';
+
+declare module '@tiptap/core' {
+  interface Commands<ReturnType> {
+    bulletListExtension: {
+      setBulletListStyle: (newStyle: string) => ReturnType;
+    };
+  }
+}
+
+export const bulletListExtension = BulletList.extend({
+  addAttributes() {
+    return {
+      listStyle: {
+        default: 'disc',
+        parseHTML: element => element.style.listStyleType,
+        renderHTML: attributes => ({
+          style: `list-style: ${attributes.listStyle};`
+        })
+      }
+    };
+  },
+
+  addCommands() {
+    const setNodeIndentMarkup = (tr: Transaction, pos: number, newStyle: string): Transaction => {
+      const node = tr?.doc?.nodeAt(pos);
+      if (node) {
+        const nodeAttrs = { ...node.attrs, listStyle: newStyle };
+        return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks);
+      }
+      return tr;
+    };
+
+    const applyListStyle: (newStyle: string) => () => Command =
+      newStyle => () => ({ tr, state, dispatch }) => {
+        const { selection } = state;
+        let transaction;
+        tr.doc.nodesBetween(selection.from, selection.to, (node, pos) => {
+          transaction = setNodeIndentMarkup(tr, pos, newStyle);
+        });
+        dispatch?.(transaction);
+        return true;
+      };
+
+    return {
+      setBulletListStyle: (newStyle: string) => applyListStyle(newStyle)()
+    };
+  }
+});
diff --git a/projects/editor/src/app/text-editor/orderedList-extension.ts b/projects/editor/src/app/text-editor/orderedList-extension.ts
new file mode 100644
index 000000000..fccac3710
--- /dev/null
+++ b/projects/editor/src/app/text-editor/orderedList-extension.ts
@@ -0,0 +1,53 @@
+import { Command } from '@tiptap/core';
+import { Transaction } from 'prosemirror-state';
+import { OrderedList } from '@tiptap/extension-ordered-list';
+
+declare module '@tiptap/core' {
+  interface Commands<ReturnType> {
+    orderedListExtension: {
+      setOrderedListStyle: (newStyle: string) => ReturnType;
+    };
+  }
+}
+
+export const orderedListExtension = OrderedList.extend({
+  addAttributes() {
+    return {
+      listStyle: {
+        default: 'decimal',
+        parseHTML: element => {
+          return element.style.listStyleType;
+        },
+        renderHTML: attributes => ({
+          style: `list-style: ${attributes.listStyle};`
+        })
+      }
+    };
+  },
+
+  addCommands() {
+    const setNodeIndentMarkup = (tr: Transaction, pos: number, newStyle: string): Transaction => {
+      const node = tr?.doc?.nodeAt(pos);
+      if (node) {
+        const nodeAttrs = { ...node.attrs, listStyle: newStyle };
+        return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks);
+      }
+      return tr;
+    };
+
+    const applyListStyle: (newStyle: string) => () => Command =
+      newStyle => () => ({ tr, state, dispatch }) => {
+        const { selection } = state;
+        let transaction;
+        tr.doc.nodesBetween(selection.from, selection.to, (node, pos) => {
+          transaction = setNodeIndentMarkup(tr, pos, newStyle);
+        });
+        dispatch?.(transaction);
+        return true;
+      };
+
+    return {
+      setOrderedListStyle: (newStyle: string) => applyListStyle(newStyle)()
+    };
+  }
+});
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 6dc31b514..5f38c4108 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
@@ -160,7 +160,8 @@
   <mat-menu #optionsMenu="matMenu">
     <mat-form-field>
       <mat-label>Aufzählungszeichen</mat-label>
-      <mat-select [value]="'disc'" (click)="$any($event).stopPropagation()"
+      <mat-select [value]="editor.getAttributes('bulletList').listStyle"
+                  (click)="$any($event).stopPropagation()"
                   (valueChange)="applyListStyle('bulletList', $event)">
         <mat-option value="disc">disc</mat-option>
         <mat-option value="circle">circle</mat-option>
@@ -169,7 +170,8 @@
     </mat-form-field>
     <mat-form-field>
       <mat-label>Aufzählungsnummerierung</mat-label>
-      <mat-select [value]="'decimal'" (click)="$any($event).stopPropagation()"
+      <mat-select [value]="editor.getAttributes('orderedList').listStyle"
+                  (click)="$any($event).stopPropagation()"
                   (valueChange)="applyListStyle('orderedList', $event)">
         <mat-option value="decimal">decimal</mat-option>
         <mat-option value="lower-latin">lower-latin</mat-option>
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 80f33a8ab..7bd795972 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
@@ -2,7 +2,7 @@ import {
   Component, EventEmitter, Input, Output, ViewEncapsulation,
   AfterViewInit
 } from '@angular/core';
-import { Editor, Extension } from '@tiptap/core';
+import { Editor } from '@tiptap/core';
 import StarterKit from '@tiptap/starter-kit';
 import { Underline } from '@tiptap/extension-underline';
 import { Superscript } from '@tiptap/extension-superscript';
@@ -15,6 +15,8 @@ import { Heading } from '@tiptap/extension-heading';
 import { Indent } from './indent';
 import { customParagraph } from './paragraph-extension';
 import { fontSizeExtension } from './font-size-extension';
+import { bulletListExtension } from './bulletList-extension';
+import { orderedListExtension } from './orderedList-extension';
 
 @Component({
   selector: 'app-rich-text-editor',
@@ -45,7 +47,9 @@ export class RichTextEditorComponent implements AfterViewInit {
         levels: [1, 2, 3, 4]
       }),
       customParagraph,
-      fontSizeExtension
+      fontSizeExtension,
+      bulletListExtension,
+      orderedListExtension
     ]
   });
 
@@ -110,11 +114,11 @@ export class RichTextEditorComponent implements AfterViewInit {
   }
 
   applyListStyle(listType: string, style: string): void {
-    (this.editor.extensionManager.extensions
-      .filter(ext => ext.name === listType)[0] as Extension).options.HTMLAttributes =
-      {
-        style: `list-style-type:${style}`
-      };
+    if (listType === 'bulletList') {
+      this.editor.commands.setBulletListStyle(style);
+    } else {
+      this.editor.commands.setOrderedListStyle(style);
+    }
   }
 
   toggleHeading(level?: string): void {
-- 
GitLab