Skip to content
Snippets Groups Projects
rich-text-editor.component.ts 11.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • import {
    
      Component, EventEmitter, Input, Output,
      AfterViewInit, Injector, OnInit
    
    } from '@angular/core';
    
    import { Editor } from '@tiptap/core';
    
    import { Underline } from '@tiptap/extension-underline';
    import { Superscript } from '@tiptap/extension-superscript';
    import { Subscript } from '@tiptap/extension-subscript';
    import { TextStyle } from '@tiptap/extension-text-style';
    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';
    
    rhenck's avatar
    rhenck committed
    import { Blockquote } from '@tiptap/extension-blockquote';
    
    import { Document } from '@tiptap/extension-document';
    
    import { History } from '@tiptap/extension-history';
    
    import { Text } from '@tiptap/extension-text';
    import { ListItem } from '@tiptap/extension-list-item';
    import { Bold } from '@tiptap/extension-bold';
    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 { Tooltip } from 'editor/src/app/text-editor/extensions/tooltip';
    import { DialogService } from 'editor/src/app/services/dialog.service';
    
    import { AnchorId } from './extensions/anchorId';
    
    import { Indent } from './extensions/indent';
    import { HangingIndent } from './extensions/hanging-indent';
    
    import { ParagraphExtension } from './extensions/paragraph-extension';
    
    import { FontSize } from './extensions/font-size';
    
    import { BulletListExtension } from './extensions/bullet-list';
    
    import { OrderedListExtension } from './extensions/ordered-list';
    
    import ToggleButtonComponentExtension from './angular-node-views/toggle-button-component-extension';
    import DropListComponentExtension from './angular-node-views/drop-list-component-extension';
    import TextFieldComponentExtension from './angular-node-views/text-field-component-extension';
    
    import CheckboxComponentExtension from './angular-node-views/checkbox-component-extension';
    
    @Component({
    
      selector: 'aspect-rich-text-editor',
    
      templateUrl: './rich-text-editor.component.html',
    
      styleUrls: ['./rich-text-editor.component.scss']
    
    export class RichTextEditorComponent implements OnInit, AfterViewInit {
    
      @Input() content!: string | Record<string, any>;
    
      @Input() defaultFontSize!: number;
    
      @Input() clozeMode: boolean = false;
    
      @Output() contentChange = new EventEmitter<string | Record<string, any>>();
    
      selectedFontColor = 'black';
    
      selectedHighlightColor = 'lightgrey';
    
      selectedAnchorColor = '#dccce6';
    
      selectedAnchorIdText = '';
    
      selectedFontSize = '20px';
    
      selectedIndentSize = 20;
    
      bulletListStyle: string = 'disc';
      orderedListStyle: string = 'decimal';
    
    
      defaultExtensions = [
        Document, Text, ListItem,
        Underline, Superscript, Subscript,
    
        TextStyle, Color,
    
        Bold, Italic, Strike, History,
    
        Highlight.configure({
          multicolor: true
        }),
    
        TextAlign.configure({
          types: ['paragraph', 'heading']
        }),
        Indent.configure({
          types: ['listItem', 'paragraph'],
          minLevel: 0,
          maxLevel: 4
        }),
        Heading.configure({
          levels: [1, 2, 3, 4]
        }),
        ParagraphExtension,
        FontSize,
        BulletListExtension,
        OrderedListExtension,
        HangingIndent,
    
        Blockquote,
        Tooltip
    
      editor: Editor = new Editor({
    
        extensions: this.defaultExtensions
    
      constructor(private injector: Injector, private dialogService: DialogService) { }
    
      ngOnInit(): void {
        const activeExtensions = this.defaultExtensions;
        if (this.clozeMode) {
          activeExtensions.push(ToggleButtonComponentExtension(this.injector));
          activeExtensions.push(DropListComponentExtension(this.injector));
          activeExtensions.push(TextFieldComponentExtension(this.injector));
    
          activeExtensions.push(ButtonComponentExtension(this.injector));
    
          activeExtensions.push(CheckboxComponentExtension(this.injector));
    
        this.editor = new Editor({
    
          extensions: activeExtensions
    
      ngAfterViewInit(): void {
        this.editor.commands.focus();
      }
    
      toggleBold(): void {
        this.editor.chain().toggleBold().focus().run();
      }
    
      toggleItalic(): void {
        this.editor.chain().toggleItalic().focus().run();
      }
    
      toggleUnderline(): void {
        this.editor.chain().toggleUnderline().focus().run();
      }
    
      toggleStrike(): void {
        this.editor.commands.toggleStrike();
      }
    
      toggleSuperscript(): void {
        this.editor.chain().toggleSuperscript().focus().run();
      }
    
    
      showTooltipPropertiesDialog(): void {
        this.dialogService.showTooltipDialog(
          this.editor.getAttributes('tooltip').tooltipText,
          this.editor.getAttributes('tooltip').tooltipPosition
        ).subscribe(result => {
          if (result) {
            if (result.action === 'delete') {
              this.editor.chain().focus().unsetTooltip().run();
            } else {
              this.editor.chain().focus().setTooltip({
                tooltipText: result.tooltipText,
                tooltipPosition: result.tooltipPosition
              }).run();
            }
          }
        });
      }
    
    
      toggleSubscript(): void {
        this.editor.chain().toggleSubscript().focus().run();
      }
    
    
      applyFontSize(size: string): void {
        this.selectedFontSize = size;
    
        this.editor.commands.setFontSize(size);
    
      applyFontColor(): void {
        this.editor.chain().focus().setColor(this.selectedFontColor).run();
    
      applyHighlightColor(): void {
        this.editor.chain().focus().toggleHighlight({ color: this.selectedHighlightColor }).run();
    
        const id = this.getAnchorIdFromSelection();
        if (id) {
          const activeAnchorId = this.editor.getAttributes('anchorId').anchorId;
          const activeAnchorColor = this.editor.getAttributes('anchorId').anchorColor;
          const activeParentAnchorId = this.editor.getAttributes('anchorId').parentAnchorId;
          const activeParentAnchorColor = this.editor.getAttributes('anchorId').parentAnchorColor;
          if (activeParentAnchorId) { // reset nested child
            if (this.selectedAnchorColor === activeParentAnchorColor || this.selectedAnchorColor === activeAnchorColor) {
              this.editor.chain().focus().setAnchorId({
                anchorId: activeParentAnchorId,
                parentAnchorId: '',
                anchorColor: activeParentAnchorColor,
                parentAnchorColor: ''
              }).run();
            } else { // set new color for nested Child
              this.editor.chain().focus().setAnchorId({
                anchorId: activeAnchorId,
                parentAnchorId: activeParentAnchorId,
                anchorColor: this.selectedAnchorColor,
                parentAnchorColor: activeParentAnchorColor
              }).run();
            }
          } else { // standard toggle
            this.editor.chain().focus().toggleAnchorId({
              anchorId: id,
              parentAnchorId: (activeAnchorId !== id) ? activeAnchorId : '',
              anchorColor: this.selectedAnchorColor,
              parentAnchorColor: (activeAnchorId !== id) ? activeAnchorColor : ''
            }).run();
          }
          this.resetSelectedAnchorIdText();
    
        } else {
          console.warn('No text selected for anchor!');
        }
      }
    
    
      private getAnchorIdFromSelection(): string {
        const selection = window?.getSelection()?.toString() || this.selectedAnchorIdText;
        return selection.replace(/[^0-9a-zA-Z]/g, '_').substring(0, 20);
      }
    
      private resetSelectedAnchorIdText(): void {
        this.selectedAnchorIdText = '';
      }
    
    
      alignText(direction: string): void {
        this.editor.chain().focus().setTextAlign(direction).run();
      }
    
      indent(): void {
    
        this.editor.commands.indent(this.selectedIndentSize);
    
      }
    
      outdent(): void {
    
        this.editor.commands.outdent(this.selectedIndentSize);
    
      }
    
      toggleBulletList(): void {
        this.editor.chain().toggleBulletList().focus().run();
    
        this.editor.commands.setBulletListStyle(this.bulletListStyle);
    
      toggleOrderedList(): void {
    
        this.editor.chain().toggleOrderedList().focus().run();
    
        this.editor.commands.setOrderedListStyle(this.orderedListStyle);
        this.editor.commands.setOrderedListFontSize(this.selectedFontSize);
    
      }
    
      applyListStyle(listType: string, style: string): void {
    
        if (listType === 'bulletList') {
    
          this.bulletListStyle = style;
    
          this.editor.commands.setBulletListStyle(style);
    
          if (!this.editor.isActive('bulletList')) {
            this.toggleBulletList();
          }
    
          this.orderedListStyle = style;
    
          this.editor.commands.setOrderedListStyle(style);
          this.editor.commands.setOrderedListFontSize(this.selectedFontSize);
    
          if (!this.editor.isActive('orderedList')) {
    
            this.toggleOrderedList();
    
      }
    
      toggleHeading(level?: string): void {
        if (!level) {
          this.editor.commands.toggleNode('heading', 'paragraph');
        } else {
          this.editor.commands.toggleHeading({ level: Number(level) as 1 | 2 | 3 | 4 });
        }
      }
    
      applyParagraphStyle(margin: number): void {
    
        this.editor.commands.setMargin(margin);
    
      }
    
      insertSpecialChar(char: string): void {
        this.editor.chain().insertContent(char).focus().run();
      }
    
      hangIndent(): void {
    
        this.editor.commands.indent(this.selectedIndentSize);
        this.editor.commands.hangIndent(this.selectedIndentSize);
    
      unhangIndent(): void {
    
        this.editor.commands.outdent(this.selectedIndentSize);
        this.editor.commands.unhangIndent(this.selectedIndentSize);
    
      async insertImage(): Promise<void> {
    
        const mediaSrc = await FileService.loadImage();
    
        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 });
          }
        }
    
    rhenck's avatar
    rhenck committed
    
      toggleBlockquote(): void {
        this.editor.commands.toggleBlockquote();
      }
    
        this.editor.commands.insertContent('<aspect-nodeview-toggle-button></aspect-nodeview-toggle-button>');
    
        this.editor.commands.focus();
    
        this.editor.commands.insertContent('<aspect-nodeview-drop-list></aspect-nodeview-drop-list>');
    
        this.editor.commands.focus();
    
        this.editor.commands.insertContent('<aspect-nodeview-text-field></aspect-nodeview-text-field>');
    
        this.editor.commands.focus();
    
    
      insertButton() {
        this.editor.commands.insertContent('<aspect-nodeview-button></aspect-nodeview-button>');
        this.editor.commands.focus();
      }
    
    
      insertCheckbox() {
        this.editor.commands.insertContent('<aspect-nodeview-checkbox></aspect-nodeview-checkbox>');
        this.editor.commands.focus();
      }