230 lines
8.7 KiB
JavaScript
230 lines
8.7 KiB
JavaScript
/*!
|
|
* Jodit Editor (https://xdsoft.net/jodit/)
|
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
* Copyright (c) 2013-2025 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
|
|
*/
|
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
r = Reflect.decorate(decorators, target, key, desc);
|
|
else
|
|
for (var i = decorators.length - 1; i >= 0; i--)
|
|
if (d = decorators[i])
|
|
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
import { autobind, throttle, watch } from "../../core/decorators/index.js";
|
|
import { pluginSystem } from "../../core/global.js";
|
|
import { call, offset, position, scrollIntoViewIfNeeded } from "../../core/helpers/index.js";
|
|
import { Dom, Icon, Plugin } from "../../modules/index.js";
|
|
import "./config.js";
|
|
const ns = 'addnewline';
|
|
/**
|
|
* Create helper for adding new paragraph(Jodit.defaultOptions.enter tag) before iframe, table or image
|
|
*/
|
|
export class addNewLine extends Plugin {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.__line = this.j.c.fromHTML(`<div role="button" tabindex="-1" title="${this.j.i18n('Break')}" class="jodit-add-new-line"><span>${Icon.get('enter')}</span></div>`);
|
|
this.__isMatchedTag = (node) => Boolean(node &&
|
|
this.j.o.addNewLineTagsTriggers.includes(node.nodeName.toLowerCase()));
|
|
this.__isBeforeContent = false;
|
|
this.__lineInFocus = false;
|
|
this.__isShown = false;
|
|
this.__hideForce = () => {
|
|
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-anl-handle-offset', '0');
|
|
};
|
|
this.__canGetFocus = (elm) => {
|
|
return (elm != null &&
|
|
Dom.isBlock(elm) &&
|
|
!/^(img|table|iframe|hr)$/i.test(elm.nodeName));
|
|
};
|
|
this.__onClickLine = (e) => {
|
|
const editor = this.j;
|
|
const p = editor.createInside.element(editor.o.enter);
|
|
if (this.__isBeforeContent &&
|
|
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.synchronizeValues();
|
|
this.__hideForce();
|
|
e.preventDefault();
|
|
};
|
|
}
|
|
__show() {
|
|
if (this.j.o.readonly || this.j.isLocked) {
|
|
return;
|
|
}
|
|
this.j.async.clearTimeout(this.__timeout);
|
|
if (this.__isShown) {
|
|
return;
|
|
}
|
|
this.__isShown = true;
|
|
this.j.container.appendChild(this.__line);
|
|
this.__line.style.width = this.j.container.clientWidth + 'px';
|
|
}
|
|
onLock(isLocked) {
|
|
if (isLocked && this.__isShown) {
|
|
this.__hideForce();
|
|
}
|
|
}
|
|
__hide() {
|
|
if (!this.__isShown || this.__lineInFocus) {
|
|
return;
|
|
}
|
|
this.__timeout = this.j.async.setTimeout(this.__hideForce, {
|
|
timeout: 500,
|
|
label: 'add-new-line-hide'
|
|
});
|
|
}
|
|
afterInit(editor) {
|
|
if (!editor.o.addNewLine) {
|
|
return;
|
|
}
|
|
editor.e
|
|
.on(this.__line, 'mousemove', (e) => {
|
|
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();
|
|
}
|
|
__addEventListeners() {
|
|
const editor = this.j;
|
|
editor.e
|
|
.off(editor.editor, '.' + ns)
|
|
.off(editor.container, '.' + ns)
|
|
.off('.' + ns)
|
|
.on([editor.ow, editor.ew, editor.editor], 'scroll' + '.' + ns, this.__hideForce)
|
|
.on('finishedCleanHTMLWorker' + '.' + ns, this.__hideForce)
|
|
.on(editor.editor, 'click' + '.' + ns, this.__hide)
|
|
.on(editor.container, 'mouseleave' + '.' + ns, this.__hide)
|
|
.on(editor.editor, 'mousemove' + '.' + ns, this.__onMouseMove);
|
|
}
|
|
onDblClickEditor(e) {
|
|
const editor = this.j;
|
|
if (!editor.o.readonly &&
|
|
editor.o.addNewLineOnDBLClick &&
|
|
e.target === editor.editor &&
|
|
editor.s.isCollapsed()) {
|
|
const editorBound = offset(editor.editor, editor, editor.ed);
|
|
const top = e.pageY - editor.ew.scrollX;
|
|
const p = editor.createInside.element(editor.o.enter);
|
|
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();
|
|
}
|
|
}
|
|
__onMouseMove(e) {
|
|
const editor = this.j;
|
|
let currentElement = editor.ed.elementFromPoint(e.clientX, e.clientY);
|
|
if (!Dom.isHTMLElement(currentElement) ||
|
|
!Dom.isOrContains(editor.editor, currentElement)) {
|
|
return;
|
|
}
|
|
if (editor.editor !== currentElement &&
|
|
!this.__isMatchedTag(currentElement)) {
|
|
currentElement = Dom.closest(currentElement, this.__isMatchedTag, editor.editor);
|
|
}
|
|
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;
|
|
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.__isBeforeContent = true;
|
|
}
|
|
else if (Math.abs(clientY - (pos.top + pos.height)) <= delta) {
|
|
top = pos.top + pos.height;
|
|
this.__isBeforeContent = false;
|
|
}
|
|
const isEditor = editor.editor === currentElement;
|
|
if (top !== false &&
|
|
((isEditor && !this.__isBeforeContent) ||
|
|
(!isEditor &&
|
|
!call(this.__isBeforeContent ? 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-anl-handle-offset', clientX - pos.left - 10 + 'px');
|
|
}
|
|
else {
|
|
this.__current = false;
|
|
this.__hide();
|
|
}
|
|
}
|
|
/** @override */
|
|
beforeDestruct() {
|
|
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)
|
|
.off('.' + ns);
|
|
}
|
|
}
|
|
__decorate([
|
|
watch(':lock')
|
|
], addNewLine.prototype, "onLock", null);
|
|
__decorate([
|
|
autobind
|
|
], addNewLine.prototype, "__hide", null);
|
|
__decorate([
|
|
watch(':dblclick')
|
|
], addNewLine.prototype, "onDblClickEditor", null);
|
|
__decorate([
|
|
throttle(ctx => ctx.defaultTimeout)
|
|
], addNewLine.prototype, "__onMouseMove", null);
|
|
pluginSystem.add('addNewLine', addNewLine);
|