/**
* [[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);