/** * [[include:plugins/add-new-line/README.md]] * @packageDocumentation * @module plugins/add-new-line */ import './add-new-line.less'; import type { IJodit, HTMLTagNames, Nullable } from 'jodit/types'; import { Dom, Icon, Plugin } from 'jodit/modules'; import { // offset, position, call, scrollIntoViewIfNeeded } from 'jodit/core/helpers'; import { autobind, debounce, watch } from 'jodit/core/decorators'; import { pluginSystem } from 'jodit/core/global'; import './config'; const ns = 'addnewline'; /** * Create helper for adding new paragraph(Jodit.defaultOptions.enter tag) before iframe, table or image */ export class addNewLine extends Plugin { private line = this.j.c.fromHTML( `
${Icon.get('enter')}
` ) as HTMLDivElement; private isMatchedTag = (node: Nullable): boolean => Boolean( node && this.j.o.addNewLineTagsTriggers.includes( node.nodeName.toLowerCase() as HTMLTagNames ) ); private timeout!: number; private preview: boolean = false; private current!: HTMLElement | false; private lineInFocus: boolean = false; private isShown: boolean = false; private show(): void { if (this.isShown || this.j.o.readonly || this.j.isLocked) { return; } this.isShown = true; this.j.async.clearTimeout(this.timeout); this.line.classList.toggle('jodit-add-new-line_after', !this.preview); this.j.container.appendChild(this.line); this.line.style.width = this.j.container.clientWidth + 'px'; } private hideForce = (): void => { if (!this.isShown) { return; } this.isShown = false; this.j.async.clearTimeout(this.timeout); this.lineInFocus = false; Dom.safeRemove(this.line); this.line.style.setProperty('--jd-offset-handle', '0'); }; @watch(':lock') protected onLock(isLocked: true): void { if (isLocked && this.isShown) { this.hideForce(); } } @autobind private hide(): void { if (!this.isShown || this.lineInFocus) { return; } this.timeout = this.j.async.setTimeout(this.hideForce, { timeout: 500, label: 'add-new-line-hide' }); } private canGetFocus = (elm: Node | null): boolean => { return ( elm != null && Dom.isBlock(elm) && !/^(img|table|iframe|hr)$/i.test(elm.nodeName) ); }; protected afterInit(editor: IJodit): void { if (!editor.o.addNewLine) { return; } editor.e .on(this.line, 'mousemove', (e: MouseEvent) => { e.stopPropagation(); }) .on(this.line, 'mousedown touchstart', this.onClickLine) .on('change', this.hideForce) .on(this.line, 'mouseenter', () => { this.j.async.clearTimeout(this.timeout); this.lineInFocus = true; }) .on(this.line, 'mouseleave', () => { this.lineInFocus = false; }) .on('changePlace', this.addEventListeners.bind(this)); this.addEventListeners(); } private addEventListeners(): void { const editor = this.j; editor.e .off(editor.editor, '.' + ns) .off(editor.container, '.' + ns) .on( [editor.ow, editor.ew, editor.editor], 'scroll' + '.' + ns, this.hideForce ) .on(editor.editor, 'click' + '.' + ns, this.hide) .on(editor.container, 'mouseleave' + '.' + ns, this.hide) .on(editor.editor, 'mousemove' + '.' + ns, this.onMouseMove); } private onClickLine = (e: MouseEvent): void => { const editor = this.j; const p = editor.createInside.element(editor.o.enter); if (this.preview && this.current && this.current.parentNode) { if (this.current === editor.editor) { Dom.prepend(editor.editor, p); } else { this.current.parentNode.insertBefore(p, this.current); } } else { editor.editor.appendChild(p); } editor.s.setCursorIn(p); scrollIntoViewIfNeeded(p, editor.editor, editor.ed); editor.e.fire('synchro'); this.hideForce(); e.preventDefault(); }; @watch(':dblclick') protected onDblClickEditor(e: MouseEvent): void { const editor = this.j; if ( !editor.o.readonly && editor.o.addNewLineOnDBLClick && e.target === editor.editor && editor.s.isCollapsed() ) { // const editorBound: IBound = offset( // editor.editor, // editor, // editor.ed // ); // const top = e.pageY - editor.ew.pageYOffset; const p = editor.createInside.element(editor.o.enter); /** 이거때문에 새로운 div가 기존 요소 위에 추가되는데 이 조건이 왜 걸려있는지 모르겠음 * 그리고 아래 조건이 false가 되는 경우가 안보임 */ // if ( // Math.abs(top - editorBound.top) < // Math.abs(top - (editorBound.height + editorBound.top)) && // editor.editor.firstChild // ) { // editor.editor.insertBefore(p, editor.editor.firstChild); // } else { editor.editor.appendChild(p); // } editor.s.setCursorIn(p); editor.synchronizeValues(); this.hideForce(); e.preventDefault(); } } @debounce(ctx => ctx.defaultTimeout * 5) private onMouseMove(e: MouseEvent): void { const editor = this.j; let currentElement: HTMLElement | null = editor.ed.elementFromPoint( e.clientX, e.clientY ) as HTMLElement; if ( !Dom.isHTMLElement(currentElement) || Dom.isOrContains(this.line, currentElement) ) { return; } if (!Dom.isOrContains(editor.editor, currentElement)) { return; } if ( editor.editor !== currentElement && !this.isMatchedTag(currentElement) ) { currentElement = Dom.closest( currentElement, this.isMatchedTag, editor.editor ) as HTMLElement; } if (!currentElement) { this.hide(); return; } if (this.isMatchedTag(currentElement)) { const parentBox = Dom.up( currentElement, Dom.isBlock, editor.editor ); if (parentBox && parentBox !== editor.editor) { currentElement = parentBox; } } const pos = position(currentElement, this.j); let top: false | number = false; let { clientY, clientX } = e; if (this.j.iframe) { const { top, left } = position(this.j.iframe, this.j, true); clientY += top; clientX += left; } const delta = this.j.o.addNewLineDeltaShow; if (Math.abs(clientY - pos.top) <= delta) { top = pos.top; this.preview = true; } if (Math.abs(clientY - (pos.top + pos.height)) <= delta) { top = pos.top + pos.height; this.preview = false; } if ( top !== false && ((editor.editor === currentElement && !this.preview) || !call( this.preview ? Dom.prev : Dom.next, currentElement, this.canGetFocus, editor.editor )) ) { this.line.style.top = top + 'px'; this.current = currentElement; this.show(); this.line.style.setProperty( '--jd-offset-handle', clientX - pos.left - 10 + 'px' ); } else { this.current = false; this.hide(); } } /** @override */ protected beforeDestruct(): void { this.j.async.clearTimeout(this.timeout); this.j.e.off(this.line).off('changePlace', this.addEventListeners); Dom.safeRemove(this.line); this.j.e .off([this.j.ow, this.j.ew, this.j.editor], '.' + ns) .off(this.j.container, '.' + ns); } } pluginSystem.add('addNewLine', addNewLine);