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