328 lines
12 KiB
JavaScript
328 lines
12 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 * as consts from "../../core/constants.js";
|
|
import { INVISIBLE_SPACE, KEY_ESC, MODE_SOURCE, MODE_SPLIT, SOURCE_CONSUMER } 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 { isString, loadNext } from "../../core/helpers/index.js";
|
|
import { Plugin } from "../../core/plugin/index.js";
|
|
import "./config.js";
|
|
import { createSourceEditor } from "./editor/factory.js";
|
|
/**
|
|
* Plug-in change simple textarea on CodeMirror editor in Source code mode
|
|
*/
|
|
export class source extends Plugin {
|
|
constructor() {
|
|
super(...arguments);
|
|
/** @override */
|
|
this.buttons = [
|
|
{
|
|
name: 'source',
|
|
group: 'source'
|
|
}
|
|
];
|
|
this.__lock = false;
|
|
this.__oldMirrorValue = '';
|
|
this.tempMarkerStart = '{start-jodit-selection}';
|
|
this.tempMarkerStartReg = /{start-jodit-selection}/g;
|
|
this.tempMarkerEnd = '{end-jodit-selection}';
|
|
this.tempMarkerEndReg = /{end-jodit-selection}/g;
|
|
// override it for ace editors
|
|
this.getSelectionStart = () => {
|
|
var _a, _b;
|
|
return (_b = (_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.getSelectionStart()) !== null && _b !== void 0 ? _b : 0;
|
|
};
|
|
this.getSelectionEnd = () => {
|
|
var _a, _b;
|
|
return (_b = (_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.getSelectionEnd()) !== null && _b !== void 0 ? _b : 0;
|
|
};
|
|
}
|
|
onInsertHTML(html) {
|
|
var _a;
|
|
if (!this.j.o.readonly && !this.j.isEditorMode()) {
|
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.insertRaw(html);
|
|
this.toWYSIWYG();
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Update source editor from WYSIWYG area
|
|
*/
|
|
fromWYSIWYG(force = false) {
|
|
if (!this.__lock || force === true) {
|
|
this.__lock = true;
|
|
const new_value = this.j.getEditorValue(false, SOURCE_CONSUMER);
|
|
if (new_value !== this.getMirrorValue()) {
|
|
this.setMirrorValue(new_value);
|
|
}
|
|
this.__lock = false;
|
|
}
|
|
}
|
|
/**
|
|
* Update WYSIWYG area from source editor
|
|
*/
|
|
toWYSIWYG() {
|
|
if (this.__lock) {
|
|
return;
|
|
}
|
|
const value = this.getMirrorValue();
|
|
if (value === this.__oldMirrorValue) {
|
|
return;
|
|
}
|
|
this.__lock = true;
|
|
this.j.value = value;
|
|
this.__lock = false;
|
|
this.__oldMirrorValue = value;
|
|
}
|
|
getNormalPosition(pos, str) {
|
|
str = str.replace(/<(script|style|iframe)[^>]*>[^]*?<\/\1>/im, m => {
|
|
let res = '';
|
|
for (let i = 0; i < m.length; i += 1) {
|
|
res += INVISIBLE_SPACE;
|
|
}
|
|
return res;
|
|
});
|
|
while (pos > 0 && str[pos] === INVISIBLE_SPACE) {
|
|
pos--;
|
|
}
|
|
let start = pos;
|
|
while (start > 0) {
|
|
start--;
|
|
if (str[start] === '<' &&
|
|
str[start + 1] !== undefined &&
|
|
str[start + 1].match(/[\w/]+/i)) {
|
|
return start;
|
|
}
|
|
if (str[start] === '>') {
|
|
return pos;
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
clnInv(str) {
|
|
return str.replace(consts.INVISIBLE_SPACE_REG_EXP(), '');
|
|
}
|
|
onSelectAll(command) {
|
|
var _a;
|
|
if (command.toLowerCase() === 'selectall' &&
|
|
this.j.getRealMode() === MODE_SOURCE) {
|
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.selectAll();
|
|
return false;
|
|
}
|
|
}
|
|
getMirrorValue() {
|
|
var _a;
|
|
return ((_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.getValue()) || '';
|
|
}
|
|
setMirrorValue(value) {
|
|
var _a;
|
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.setValue(value);
|
|
}
|
|
setFocusToMirror() {
|
|
var _a;
|
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.focus();
|
|
}
|
|
saveSelection() {
|
|
if (this.j.getRealMode() === consts.MODE_WYSIWYG) {
|
|
this.j.s.save();
|
|
this.j.synchronizeValues();
|
|
this.fromWYSIWYG(true);
|
|
}
|
|
else {
|
|
if (this.j.o.editHTMLDocumentMode) {
|
|
return;
|
|
}
|
|
const value = this.getMirrorValue();
|
|
if (this.getSelectionStart() === this.getSelectionEnd()) {
|
|
const marker = this.j.s.marker(true);
|
|
const selectionStart = this.getNormalPosition(this.getSelectionStart(), this.getMirrorValue());
|
|
this.setMirrorValue(value.substring(0, selectionStart) +
|
|
this.clnInv(marker.outerHTML) +
|
|
value.substring(selectionStart));
|
|
}
|
|
else {
|
|
const markerStart = this.j.s.marker(true);
|
|
const markerEnd = this.j.s.marker(false);
|
|
const selectionStart = this.getNormalPosition(this.getSelectionStart(), value);
|
|
const selectionEnd = this.getNormalPosition(this.getSelectionEnd(), value);
|
|
this.setMirrorValue(value.slice(0, selectionStart) +
|
|
this.clnInv(markerStart.outerHTML) +
|
|
value.slice(selectionStart, selectionEnd) +
|
|
this.clnInv(markerEnd.outerHTML) +
|
|
value.slice(selectionEnd));
|
|
}
|
|
this.toWYSIWYG();
|
|
}
|
|
}
|
|
removeSelection() {
|
|
if (this.j.getRealMode() === consts.MODE_WYSIWYG) {
|
|
this.__lock = true;
|
|
this.j.s.restore();
|
|
this.__lock = false;
|
|
return;
|
|
}
|
|
let value = this.getMirrorValue();
|
|
let selectionStart = 0, selectionEnd = 0;
|
|
try {
|
|
value = value
|
|
.replace(/<span[^>]+data-jodit-selection_marker=(["'])start\1[^>]*>[<>]*?<\/span>/gim, this.tempMarkerStart)
|
|
.replace(/<span[^>]+data-jodit-selection_marker=(["'])end\1[^>]*>[<>]*?<\/span>/gim, this.tempMarkerEnd);
|
|
if (!this.j.o.editHTMLDocumentMode && this.j.o.beautifyHTML) {
|
|
const html = this.j.e.fire('beautifyHTML', value);
|
|
if (isString(html)) {
|
|
value = html;
|
|
}
|
|
}
|
|
selectionStart = value.indexOf(this.tempMarkerStart);
|
|
selectionEnd = selectionStart;
|
|
value = value.replace(this.tempMarkerStartReg, '');
|
|
if (selectionStart !== -1) {
|
|
const selectionEndCursor = value.indexOf(this.tempMarkerEnd);
|
|
if (selectionEndCursor !== -1) {
|
|
selectionEnd = selectionEndCursor;
|
|
}
|
|
}
|
|
value = value.replace(this.tempMarkerEndReg, '');
|
|
}
|
|
finally {
|
|
value = value
|
|
.replace(this.tempMarkerEndReg, '')
|
|
.replace(this.tempMarkerStartReg, '');
|
|
}
|
|
this.setMirrorValue(value);
|
|
this.setMirrorSelectionRange(selectionStart, selectionEnd);
|
|
this.toWYSIWYG();
|
|
this.setFocusToMirror(); // need for setting focus after change mode
|
|
}
|
|
setMirrorSelectionRange(start, end) {
|
|
var _a;
|
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.setSelectionRange(start, end);
|
|
}
|
|
onReadonlyReact() {
|
|
var _a;
|
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.setReadOnly(this.j.o.readonly);
|
|
}
|
|
/** @override */
|
|
afterInit(editor) {
|
|
this.mirrorContainer = editor.c.div('jodit-source');
|
|
editor.workplace.appendChild(this.mirrorContainer);
|
|
editor.e.on('afterAddPlace changePlace afterInit', () => {
|
|
editor.workplace.appendChild(this.mirrorContainer);
|
|
});
|
|
this.sourceEditor = createSourceEditor('area', editor, this.mirrorContainer, this.toWYSIWYG, this.fromWYSIWYG);
|
|
editor.e.on(editor.ow, 'keydown', (e) => {
|
|
var _a;
|
|
if (e.key === KEY_ESC && ((_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.isFocused)) {
|
|
this.sourceEditor.blur();
|
|
}
|
|
});
|
|
this.onReadonlyReact();
|
|
editor.e
|
|
.on('placeholder.source', (text) => {
|
|
var _a;
|
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.setPlaceHolder(text);
|
|
})
|
|
.on('change.source', this.syncValueFromWYSIWYG)
|
|
.on('beautifyHTML', html => html);
|
|
if (editor.o.beautifyHTML) {
|
|
const addEventListener = () => {
|
|
var _a;
|
|
if (editor.isInDestruct) {
|
|
return false;
|
|
}
|
|
const html_beautify = editor.ow.html_beautify;
|
|
if (html_beautify && !editor.isInDestruct) {
|
|
(_a = editor.events) === null || _a === void 0 ? void 0 : _a.off('beautifyHTML').on('beautifyHTML', html => html_beautify(html));
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
if (!addEventListener()) {
|
|
loadNext(editor, editor.o.beautifyHTMLCDNUrlsJS).then(addEventListener, () => null);
|
|
}
|
|
}
|
|
this.syncValueFromWYSIWYG(true);
|
|
this.initSourceEditor(editor);
|
|
}
|
|
syncValueFromWYSIWYG(force = false) {
|
|
const editor = this.j;
|
|
if (editor.getMode() === MODE_SPLIT ||
|
|
editor.getMode() === MODE_SOURCE) {
|
|
this.fromWYSIWYG(force);
|
|
}
|
|
}
|
|
initSourceEditor(editor) {
|
|
var _a;
|
|
if (editor.o.sourceEditor !== 'area') {
|
|
const sourceEditor = createSourceEditor(editor.o.sourceEditor, editor, this.mirrorContainer, this.toWYSIWYG, this.fromWYSIWYG);
|
|
sourceEditor.onReadyAlways(() => {
|
|
var _a, _b;
|
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.destruct();
|
|
this.sourceEditor = sourceEditor;
|
|
this.syncValueFromWYSIWYG(true);
|
|
(_b = editor.events) === null || _b === void 0 ? void 0 : _b.fire('sourceEditorReady', editor);
|
|
});
|
|
}
|
|
else {
|
|
(_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.onReadyAlways(() => {
|
|
var _a;
|
|
this.syncValueFromWYSIWYG(true);
|
|
(_a = editor.events) === null || _a === void 0 ? void 0 : _a.fire('sourceEditorReady', editor);
|
|
});
|
|
}
|
|
}
|
|
/** @override */
|
|
beforeDestruct() {
|
|
if (this.sourceEditor) {
|
|
this.sourceEditor.destruct();
|
|
delete this.sourceEditor;
|
|
}
|
|
Dom.safeRemove(this.mirrorContainer);
|
|
}
|
|
}
|
|
__decorate([
|
|
watch(':insertHTML.source')
|
|
], source.prototype, "onInsertHTML", null);
|
|
__decorate([
|
|
autobind
|
|
], source.prototype, "fromWYSIWYG", null);
|
|
__decorate([
|
|
autobind
|
|
], source.prototype, "toWYSIWYG", null);
|
|
__decorate([
|
|
autobind
|
|
], source.prototype, "getNormalPosition", null);
|
|
__decorate([
|
|
watch(':beforeCommand.source')
|
|
], source.prototype, "onSelectAll", null);
|
|
__decorate([
|
|
watch(':beforeSetMode.source')
|
|
], source.prototype, "saveSelection", null);
|
|
__decorate([
|
|
watch(':afterSetMode.source')
|
|
], source.prototype, "removeSelection", null);
|
|
__decorate([
|
|
autobind
|
|
], source.prototype, "setMirrorSelectionRange", null);
|
|
__decorate([
|
|
watch(':readonly.source')
|
|
], source.prototype, "onReadonlyReact", null);
|
|
__decorate([
|
|
autobind
|
|
], source.prototype, "syncValueFromWYSIWYG", null);
|
|
pluginSystem.add('source', source);
|