jodit
Version:
Jodit is awesome and usefully wysiwyg editor with filebrowser
334 lines (276 loc) • 8.21 kB
text/typescript
/*!
* Jodit Editor (https://xdsoft.net/jodit/)
* Licensed under GNU General Public License version 2 or later or a commercial license or MIT;
* For GPL see LICENSE-GPL.txt in the project root for license information.
* For MIT see LICENSE-MIT.txt in the project root for license information.
* For commercial licenses see https://xdsoft.net/jodit/commercial/
* Copyright (c) 2013-2019 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
*/
import * as consts from '../constants';
import { Dom } from '../modules/Dom';
import { $$, scrollIntoView } from '../modules/helpers/';
import { HTMLTagNames, IJodit } from '../types';
/**
* Insert default paragraph
*
* @param {Jodit} editor
* @param {Node} [fake]
* @param {String} [wrapperTag]
* @param {CSSStyleSheet} [style]
* @return {HTMLElement}
*/
export const insertParagraph = (
editor: IJodit,
fake: Text | false,
wrapperTag: HTMLTagNames,
style?: CSSStyleDeclaration
): HTMLElement => {
const p: HTMLElement = editor.create.inside.element(wrapperTag),
helper_node: HTMLBRElement = editor.create.inside.element('br');
p.appendChild(helper_node);
if (style && style.cssText) {
p.setAttribute('style', style.cssText);
}
editor.selection.insertNode(p, false, false);
editor.selection.setCursorBefore(helper_node);
const range: Range = editor.editorDocument.createRange();
range.setStartBefore(wrapperTag.toLowerCase() !== 'br' ? helper_node : p);
range.collapse(true);
editor.selection.selectRange(range);
Dom.safeRemove(fake);
scrollIntoView(p, editor.editor, editor.editorDocument);
editor.events && editor.events.fire('synchro'); // fire change
return p;
};
/**
* One of most important core plugins. It is responsible for all the browsers to have the same effect when the Enter
* button is pressed. By default, it should insert the <p>
*/
export function enter(editor: IJodit) {
// use 'enter' option if no set
if (!editor.options.enterBlock) {
editor.options.enterBlock =
editor.options.enter.toLowerCase() === 'br'
? consts.PARAGRAPH
: (editor.options.enter.toLowerCase() as 'p' | 'div');
}
editor.events.on(
'keydown',
(event: KeyboardEvent): false | void => {
if (event.which === consts.KEY_ENTER) {
/**
* Fired on processing `Enter` key. If return some value, plugin `enter` will do nothing.
* if return false - prevent default Enter behavior
*
* @event beforeEnter
*/
const beforeEnter = editor.events.fire('beforeEnter', event);
if (beforeEnter !== undefined) {
return beforeEnter;
}
if (!editor.selection.isCollapsed()) {
editor.execCommand('Delete');
}
editor.selection.focus();
let current: Node = editor.selection.current(false) as Node;
const sel = editor.selection.sel;
let range = editor.selection.range;
if (!current || current === editor.editor) {
editor.selection.current();
current = editor.create.inside.text(consts.INVISIBLE_SPACE);
if (sel && sel.rangeCount) {
range.insertNode(current);
} else {
editor.editor.appendChild(current);
}
range.selectNode(current);
range.collapse(false);
if (sel) {
sel.removeAllRanges();
sel.addRange(range);
}
}
let currentBox: HTMLElement | false = current
? (Dom.up(
current,
node => Dom.isBlock(node, editor.editorWindow),
editor.editor
) as HTMLElement)
: false;
const isLi: boolean =
currentBox && currentBox.nodeName === 'LI';
// if use <br> tag for break line or when was entered SHIFt key or in <td> or <th> or <blockquote>
if (
!isLi &&
(editor.options.enter.toLowerCase() ===
consts.BR.toLowerCase() ||
event.shiftKey ||
Dom.closest(current, 'PRE|BLOCKQUOTE', editor.editor))
) {
const br: HTMLBRElement = editor.create.inside.element(
'br'
);
editor.selection.insertNode(br, true);
scrollIntoView(br, editor.editor, editor.editorDocument);
return false;
}
// wrap no wrapped element
if (
!currentBox &&
current &&
!Dom.prev(
current,
(elm: Node | null) =>
Dom.isBlock(elm, editor.editorWindow) ||
(!!elm && Dom.isImage(elm, editor.editorWindow)),
editor.editor
)
) {
let needWrap: Node = current;
Dom.up(
needWrap,
node => {
if (
node &&
node.hasChildNodes() &&
node !== editor.editor
) {
needWrap = node;
}
},
editor.editor
);
currentBox = Dom.wrapInline(
needWrap,
editor.options.enter,
editor
);
if (Dom.isEmpty(currentBox)) {
const helper_node: HTMLBRElement = editor.editorDocument.createElement(
'br'
);
currentBox.appendChild(helper_node);
editor.selection.setCursorBefore(helper_node);
}
range =
sel && sel.rangeCount
? sel.getRangeAt(0)
: editor.editorDocument.createRange();
}
let fake: Text | false = false,
insertNew: boolean = false;
if (currentBox) {
if (!Dom.canSplitBlock(currentBox, editor.editorWindow)) {
const br = editor.create.inside.element('br');
editor.selection.insertNode(br, false);
editor.selection.setCursorAfter(br);
return false;
}
if (isLi) {
if (Dom.isEmpty(currentBox)) {
let fakeTextNode: Text | false = false;
const ul: HTMLUListElement = Dom.closest(
currentBox,
'ol|ul',
editor.editor
) as HTMLUListElement;
// If there is no LI element before
if (
!Dom.prev(
currentBox,
(elm: Node | null) =>
elm && elm.nodeName === 'LI',
ul
)
) {
fakeTextNode = editor.selection.setCursorBefore(
ul
);
// If there is no LI element after
} else if (
!Dom.next(
currentBox,
(elm: Node | null) =>
elm && elm.nodeName === 'LI',
ul
)
) {
fakeTextNode = editor.selection.setCursorAfter(
ul
);
} else {
const leftRange = editor.editorDocument.createRange();
leftRange.setStartBefore(ul);
leftRange.setEndAfter(currentBox);
const fragment: DocumentFragment = leftRange.extractContents();
if (ul.parentNode) {
ul.parentNode.insertBefore(fragment, ul);
}
fakeTextNode = editor.selection.setCursorBefore(
ul
);
}
Dom.safeRemove(currentBox);
insertParagraph(
editor,
fakeTextNode,
editor.options.enter
);
if (!$$('li', ul).length) {
Dom.safeRemove(ul);
}
return false;
}
}
if (editor.selection.cursorInTheEdge(true, currentBox)) {
// if we are in the left edge of paragraph
fake = editor.selection.setCursorBefore(currentBox);
insertParagraph(
editor,
fake,
isLi ? 'li' : editor.options.enter,
currentBox.style
);
currentBox &&
editor.selection.setCursorIn(currentBox, true);
return false;
}
if (
editor.selection.cursorInTheEdge(false, currentBox) ===
false
) {
// if we are not in right edge of paragraph
// split p,h1 etc on two parts
const leftRange: Range = editor.editorDocument.createRange();
leftRange.setStartBefore(currentBox);
leftRange.setEnd(
range.startContainer,
range.startOffset
);
const fragment: DocumentFragment = leftRange.extractContents();
if (currentBox.parentNode) {
currentBox.parentNode.insertBefore(
fragment,
currentBox
);
}
editor.selection.setCursorIn(currentBox, true);
} else {
fake = editor.selection.setCursorAfter(currentBox);
}
} else {
insertNew = true;
}
if (insertNew || fake) {
insertParagraph(
editor,
fake,
isLi ? 'li' : editor.options.enter,
currentBox ? currentBox.style : void 0
);
}
return false;
}
}
);
}