github.com/ngocphuongnb/tetua@v0.0.7-alpha/packages/editor/src/index.ts (about)

     1  import { Editor as TipTapEditor } from '@tiptap/core';
     2  import { getExtensions } from './extensions';
     3  import { ImageExtensionProps } from './extensions/image';
     4  import { MarkdownEditorType } from './global';
     5  import { Menu } from './menu';
     6  
     7  const createMarkdownEditor = require('tiptap-markdown').createMarkdownEditor;
     8  const MarkdownEditor: typeof TipTapEditor = createMarkdownEditor(TipTapEditor);
     9  
    10  export interface TetuaEditorProps extends ImageExtensionProps {
    11    commentMode?: boolean;
    12    disableTitle?: boolean;
    13  }
    14  
    15  export class TetuaEditor {
    16    public tiptapEditor: TipTapEditor;
    17    public textareaElement: HTMLTextAreaElement;
    18    public tiptapEditorElement: HTMLDivElement;
    19    public editorElement: HTMLDivElement;
    20    public editorContentElement: HTMLDivElement;
    21    public useMarkdown = false;
    22    public menu: Menu = null;
    23    public inititalized: boolean = false;
    24    public props: TetuaEditorProps;
    25  
    26    constructor(selector: string, props?: TetuaEditorProps) {
    27      this.textareaElement = document.querySelector<HTMLTextAreaElement>(selector);
    28      this.props = props || {};
    29  
    30      if (!this.textareaElement) {
    31        throw new Error('Element not found');
    32      }
    33  
    34      if (this.textareaElement.tagName !== 'TEXTAREA') {
    35        throw new Error('Element must be a textarea');
    36      }
    37  
    38      this.setTextareaRows();
    39      this.menu = new Menu(this);
    40      this.createEditorElement();
    41      this.createTiptapEditorElement();
    42      this.menu.initialize();
    43    }
    44    
    45    private setTextareaRows() {
    46      const rows = this.textareaElement.value.split('\n').length;
    47      this.textareaElement.rows = (rows > 100 ? rows : 100) *1.2;
    48    }
    49  
    50    private createEditorElement() {
    51      this.editorElement = document.createElement('div');
    52      this.editorElement.className = 'mely-editor';
    53      this.insertElementAfter(this.textareaElement, this.editorElement);
    54  
    55      this.editorContentElement = document.createElement('div');
    56      this.editorContentElement.className = 'mely-editor-content';
    57      this.editorContentElement.appendChild(this.textareaElement);
    58      // this.editorContentElement.setAttribute('spellcheck', 'false');
    59      this.editorElement.appendChild(this.editorContentElement);
    60  
    61  
    62      document.addEventListener('scroll', () => {
    63        const menuHeight = this.menu.menuContainerElement.getBoundingClientRect().height;
    64        const contentTop = window.scrollY + this.editorContentElement.offsetTop;
    65  
    66        if (contentTop > menuHeight + 10) {
    67          this.menu.menuContainerElement.classList.add('mely-menu-fixed');
    68        } else {
    69          this.menu.menuContainerElement.classList.remove('mely-menu-fixed');
    70        }
    71      });
    72    }
    73  
    74    private createTiptapEditorElement() {
    75      this.tiptapEditorElement = document.createElement('div');
    76      this.tiptapEditorElement.className = 'mely-tiptap-editor';
    77      this.insertElementAfter(this.textareaElement, this.tiptapEditorElement);
    78      this.textareaElement.style.display = 'none';
    79      this.initialize();
    80    }
    81  
    82    private insertElementAfter(referenceNode: Node, newNode: Node) {
    83      referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
    84    }
    85  
    86    public initialize() {
    87      this.tiptapEditor = new MarkdownEditor({
    88        markdown: {
    89          html: true,
    90          breaks: true,
    91        },
    92        element: this.tiptapEditorElement,
    93        extensions: getExtensions(this.props),
    94        content: this.textareaElement.value,
    95        onUpdate: ({ editor }) => {
    96          // const contentElmBound = this.tiptapEditorElement.getBoundingClientRect();
    97          if (!this.useMarkdown) {
    98            this.textareaElement.value = (editor as MarkdownEditorType).getMarkdown();
    99          }
   100        },
   101        onCreate: () => {
   102          this.inititalized = true;
   103          this.resize();
   104        },
   105        onSelectionUpdate: this.menu.update.bind(this.menu),
   106        onFocus: () => {
   107          this.editorElement.classList.add('focused');
   108        },
   109        onBlur: () => {
   110          this.editorElement.classList.remove('focused');
   111        },
   112      });
   113  
   114      const updateTiptapContent = () => {
   115        this.setTextareaRows();
   116        if (this.useMarkdown) {
   117          this.tiptapEditor.commands.setContent(this.textareaElement.value);
   118        }
   119      }
   120  
   121      this.textareaElement.addEventListener('change', updateTiptapContent);
   122      this.textareaElement.addEventListener('keyup', updateTiptapContent);
   123      window.addEventListener('resize', this.resize.bind(this));
   124    }
   125  
   126    public resize() {
   127      const editorWidth = this.editorElement.clientWidth;
   128      this.editorElement.setAttribute('data-width', editorWidth.toString());
   129  
   130      if (editorWidth < 620) {
   131        this.editorElement.classList.add('mely-editor-small');
   132        this.menu.menuElement.appendChild(this.menu.menuSwitcherElement);
   133      } else {
   134        this.editorElement.classList.remove('mely-editor-small');
   135        this.menu.menuContainerElement.appendChild(this.menu.menuSwitcherElement);
   136      }
   137    }
   138  
   139    public destroy() {
   140      this.tiptapEditor.destroy();
   141    }
   142  }
   143  
   144  window.TetuaEditor = TetuaEditor