jodit
Version:
Jodit is an awesome and useful wysiwyg editor with filebrowser
211 lines (210 loc) • 7.67 kB
JavaScript
/*!
* Jodit Editor (https://xdsoft.net/jodit/)
* Released under MIT see LICENSE.txt in the project root for license information.
* Copyright (c) 2013-2026 Valerii 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 { IS_INLINE } from "../../core/constants.js";
import { autobind, watch } from "../../core/decorators/index.js";
import { Dom } from "../../core/dom/dom.js";
import { pluginSystem } from "../../core/global.js";
import { camelCase } from "../../core/helpers/string/camel-case.js";
import { Plugin } from "../../core/plugin/index.js";
import { Popup, UIElement } from "../../core/ui/index.js";
import "./config.js";
/**
* A utility plugin that allows you to subscribe to a click/mousedown/touchstart/mouseup on an element in DOM order
*
* @example
* ```js
* const editor = Jodit.make('#editor');
* editor.e.on('clickImg', (img) => {
* console.log(img.src);
* })
* ```
*/
export class select extends Plugin {
constructor() {
super(...arguments);
this.proxyEventsList = [
'click',
'mousedown',
'touchstart',
'mouseup',
'touchend'
];
}
afterInit(jodit) {
this.proxyEventsList.forEach(eventName => {
jodit.e.on(eventName + '.select', this.onStartSelection);
});
}
beforeDestruct(jodit) {
this.proxyEventsList.forEach(eventName => {
jodit.e.on(eventName + '.select', this.onStartSelection);
});
}
onStartSelection(e) {
const { j } = this;
let result, target = e.target;
while (result === undefined && target && target !== j.editor) {
result = j.e.fire(camelCase(e.type + '_' + target.nodeName.toLowerCase()), target, e);
target = target.parentElement;
}
if (e.type === 'click' && result === undefined && target === j.editor) {
j.e.fire(e.type + 'Editor', target, e);
}
}
/**
* @event outsideClick(e) - when user clicked on the outside of editor
*/
onOutsideClick(e) {
const node = e.target;
if (Dom.up(node, elm => elm === this.j.editor)) {
return;
}
const box = UIElement.closestElement(node, Popup);
if (!box) {
this.j.e.fire('outsideClick', e);
}
}
beforeCommandCut() {
const { s } = this.j;
if (!s.isCollapsed()) {
const current = s.current();
if (current && Dom.isOrContains(this.j.editor, current)) {
this.onCopyNormalizeSelectionBound();
}
}
}
beforeCommandSelectAll() {
const { s } = this.j;
s.focus();
s.select(this.j.editor, true);
s.expandSelection();
return false;
}
/**
* Fix caret position when clicking to the right of a list item that has a
* nested list. Blink/WebKit place the caret at the start of the line instead
* of the end (#1296); move it to the end of the item's own text.
*/
onClickRightOfNestedListItem(e) {
const { s } = this.j;
const range = s.range;
if (!range.collapsed ||
range.startOffset !== 0 ||
!Dom.isText(range.startContainer)) {
return;
}
const text = range.startContainer;
const li = text.parentNode;
// The text must be the direct content of a list item that has a nested
// list (the last level has no nested list and behaves correctly).
if (!Dom.isTag(li, 'li') ||
!(li.querySelector('ul') || li.querySelector('ol'))) {
return;
}
const measure = this.j.ed.createRange();
measure.selectNodeContents(text);
const rect = measure.getBoundingClientRect();
// Only when the click happened to the right of the text.
if (e.clientX > rect.right) {
s.setCursorAfter(text);
}
}
/**
* Keep pending inline formatting after a click. Toggling Bold/Italic/etc. on
* a collapsed cursor leaves empty marker elements with the caret inside; a
* click puts the caret right before them (outside), so they get cleaned up
* and the formatting is lost. Move the caret back into the innermost marker
* so the next typed character keeps every pending format (#1291).
*/
onClickKeepPendingFormat() {
var _a, _b;
const { s } = this.j;
const range = s.range;
if (!range.collapsed || !Dom.isText(range.startContainer)) {
return;
}
const text = range.startContainer;
// Caret must sit at the very end of the text, right before the markers.
if (range.startOffset !== ((_b = (_a = text.nodeValue) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0)) {
return;
}
const marker = text.nextSibling;
if (!Dom.isElement(marker) ||
!marker.nodeName.match(IS_INLINE) ||
!Dom.isEmpty(marker)) {
return;
}
let inner = marker;
// Descend into the innermost empty formatting marker.
while (Dom.isElement(inner.firstElementChild) &&
inner.firstElementChild.nodeName.match(IS_INLINE)) {
inner = inner.firstElementChild;
}
s.setCursorIn(inner);
}
/**
* Normalize selection after triple click
*/
onTripleClickNormalizeSelection(e) {
if (e.detail !== 3 || !this.j.o.select.normalizeTripleClick) {
return;
}
const { s } = this.j;
const { startContainer, startOffset } = s.range;
if (startOffset === 0 && Dom.isText(startContainer)) {
s.select(Dom.closest(startContainer, Dom.isBlock, this.j.editor) ||
startContainer, true);
}
}
onCopyNormalizeSelectionBound(e) {
const { s, editor, o } = this.j;
if (!o.select.normalizeSelectionBeforeCutAndCopy || s.isCollapsed()) {
return;
}
if (e &&
(!e.isTrusted ||
!Dom.isNode(e.target) ||
!Dom.isOrContains(editor, e.target))) {
return;
}
this.jodit.s.expandSelection();
}
}
__decorate([
autobind
], select.prototype, "onStartSelection", null);
__decorate([
watch('ow:click')
], select.prototype, "onOutsideClick", null);
__decorate([
watch([':beforeCommandCut'])
], select.prototype, "beforeCommandCut", null);
__decorate([
watch([':beforeCommandSelectall'])
], select.prototype, "beforeCommandSelectAll", null);
__decorate([
watch([':click'])
], select.prototype, "onClickRightOfNestedListItem", null);
__decorate([
watch([':click'])
], select.prototype, "onClickKeepPendingFormat", null);
__decorate([
watch([':click'])
], select.prototype, "onTripleClickNormalizeSelection", null);
__decorate([
watch([':copy', ':cut'])
], select.prototype, "onCopyNormalizeSelectionBound", null);
pluginSystem.add('select', select);