: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
var headList = isEscapseToBody ? dom.lastAncestor(head, dom.isList) :
var lastList = headList.childNodes.length > 1 ? dom.splitTree(headList, {
offset: dom.position(last) + 1
isSkipPaddingBlankHTML: true
var middleList = dom.splitTree(headList, {
offset: dom.position(head)
isSkipPaddingBlankHTML: true
paras = isEscapseToBody ? dom.listDescendant(middleList, dom.isLi) :
list.from(middleList.childNodes).filter(dom.isLi);
if (isEscapseToBody || !dom.isList(headList.parentNode)) {
paras = paras.map(function (para) {
return dom.replace(para, 'P');
$.each(list.from(paras).reverse(), function (idx, para) {
dom.insertAfter(para, headList);
var rootLists = list.compact([headList, middleList, lastList]);
$.each(rootLists, function (idx, rootList) {
var listNodes = [rootList].concat(dom.listDescendant(rootList, dom.isList));
$.each(listNodes.reverse(), function (idx, listNode) {
if (!dom.nodeLength(listNode)) {
dom.remove(listNode, true);
releasedParas = releasedParas.concat(paras);
var Typing = function () {
// a Bullet instance to toggle lists off
var bullet = new Bullet();
* @param {jQuery} $editable
* @param {WrappedRange} rng
* @param {Number} tabsize
this.insertTab = function ($editable, rng, tabsize) {
var tab = dom.createText(new Array(tabsize + 1).join(dom.NBSP_CHAR));
rng = rng.deleteContents();
rng.insertNode(tab, true);
rng = range.create(tab, tabsize);
this.insertParagraph = function ($editable) {
var rng = range.create();
// deleteContents on range.
rng = rng.deleteContents();
// Wrap range if it needs to be wrapped by paragraph
rng = rng.wrapBodyInlineWithPara();
var splitRoot = dom.ancestor(rng.sc, dom.isPara);
// on paragraph: split paragraph
// if it is an empty line with li
if (dom.isEmpty(splitRoot) && dom.isLi(splitRoot)) {
// toogle UL/OL and escape
bullet.toggleList(splitRoot.parentNode.nodeName);
// if it is an empty line with para on blockquote
} else if (dom.isEmpty(splitRoot) && dom.isPara(splitRoot) && dom.isBlockquote(splitRoot.parentNode)) {
dom.insertAfter(splitRoot, splitRoot.parentNode);
// if new line has content (not a line break)
nextPara = dom.splitTree(splitRoot, rng.getStartPoint());
var emptyAnchors = dom.listDescendant(splitRoot, dom.isEmptyAnchor);
emptyAnchors = emptyAnchors.concat(dom.listDescendant(nextPara, dom.isEmptyAnchor));
$.each(emptyAnchors, function (idx, anchor) {
// replace empty heading or pre with P tag
if ((dom.isHeading(nextPara) || dom.isPre(nextPara)) && dom.isEmpty(nextPara)) {
nextPara = dom.replace(nextPara, 'p');
// no paragraph: insert empty paragraph
var next = rng.sc.childNodes[rng.so];
nextPara = $(dom.emptyPara)[0];
rng.sc.insertBefore(nextPara, next);
rng.sc.appendChild(nextPara);
range.create(nextPara, 0).normalize().select().scrollIntoView($editable);
var Table = function () {
* @param {WrappedRange} rng
* @param {Boolean} isShift
this.tab = function (rng, isShift) {
var cell = dom.ancestor(rng.commonAncestor(), dom.isCell);
var table = dom.ancestor(cell, dom.isTable);
var cells = dom.listDescendant(table, dom.isCell);
var nextCell = list[isShift ? 'prev' : 'next'](cells, cell);
range.create(nextCell, 0).select();
* create empty table element
* @param {Number} rowCount
* @param {Number} colCount
this.createTable = function (colCount, rowCount, options) {
for (var idxCol = 0; idxCol < colCount; idxCol++) {
tds.push('<td>' + dom.blank + '</td>');
for (var idxRow = 0; idxRow < rowCount; idxRow++) {
trs.push('<tr>' + tdHTML + '</tr>');
var $table = $('<table>' + trHTML + '</table>');
if (options && options.tableClassName) {
$table.addClass(options.tableClassName);
var Editor = function (context) {
var $note = context.layoutInfo.note;
var $editor = context.layoutInfo.editor;
var $editable = context.layoutInfo.editable;
var options = context.options;
var lang = options.langInfo;
var typing = new Typing();
var bullet = new Bullet();
var history = new History($editable);
this.initialize = function () {
$editable.on('keydown', function (event) {
if (event.keyCode === key.code.ENTER) {
context.triggerEvent('enter', event);
context.triggerEvent('keydown', event);
if (options.shortcuts && !event.isDefaultPrevented()) {
self.handleKeyMap(event);
}).on('keyup', function (event) {
context.triggerEvent('keyup', event);
}).on('focus', function (event) {
context.triggerEvent('focus', event);
}).on('blur', function (event) {
context.triggerEvent('blur', event);
}).on('mousedown', function (event) {
context.triggerEvent('mousedown', event);
}).on('mouseup', function (event) {
context.triggerEvent('mouseup', event);
}).on('scroll', function (event) {
context.triggerEvent('scroll', event);
}).on('paste', function (event) {
context.triggerEvent('paste', event);
// [workaround] IE doesn't have input events for contentEditable
// - see: https://goo.gl/4bfIvA
var changeEventName = agent.isMSIE ? 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted' : 'input';
$editable.on(changeEventName, function () {
context.triggerEvent('change', $editable.html());
$editor.on('focusin', function (event) {
context.triggerEvent('focusin', event);
}).on('focusout', function (event) {
context.triggerEvent('focusout', event);
if (!options.airMode && options.height) {
$editable.outerHeight(options.height);
if (!options.airMode && options.maxHeight) {
$editable.css('max-height', options.maxHeight);
if (!options.airMode && options.minHeight) {
$editable.css('min-height', options.minHeight);
$editable.html(dom.html($note) || dom.emptyPara);
this.destroy = function () {
this.handleKeyMap = function (event) {
var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
if (event.metaKey) { keys.push('CMD'); }
if (event.ctrlKey && !event.altKey) { keys.push('CTRL'); }
if (event.shiftKey) { keys.push('SHIFT'); }
var keyName = key.nameFromCode[event.keyCode];
var eventName = keyMap[keys.join('+')];
context.invoke(eventName);
} else if (key.isEdit(event.keyCode)) {
this.createRange = function () {
* @param {Boolean} [thenCollapse=false]
this.saveRange = function (thenCollapse) {
$editable.data('range', range.create());
range.create().collapse().select();
this.restoreRange = function () {
var rng = $editable.data('range');
this.saveTarget = function (node) {
$editable.data('target', node);
this.clearTarget = function () {
$editable.removeData('target');
this.restoreTarget = function () {
return $editable.data('target');
* @return {Object|Boolean} unfocus
this.currentStyle = function () {
var rng = range.create();
return rng ? style.current(rng) : style.fromNode($editable);
this.styleFromNode = function ($node) {
return style.fromNode($node);
this.undo = function () {
context.triggerEvent('before.command', $editable.html());
context.triggerEvent('change', $editable.html());
context.memo('help.undo', lang.help.undo);
this.redo = function () {
context.triggerEvent('before.command', $editable.html());
context.triggerEvent('change', $editable.html());
context.memo('help.redo', lang.help.redo);
var beforeCommand = this.beforeCommand = function () {
context.triggerEvent('before.command', $editable.html());
// keep focus on editable before command execution
* @param {Boolean} isPreventTrigger
var afterCommand = this.afterCommand = function (isPreventTrigger) {
context.triggerEvent('change', $editable.html());
/* jshint ignore:start */
// native commands(with execCommand), generate function for execCommand
var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript',
'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
'formatBlock', 'removeFormat',
'backColor', 'foreColor', 'fontName'];
for (var idx = 0, len = commands.length; idx < len; idx ++) {
this[commands[idx]] = (function (sCmd) {
return function (value) {
document.execCommand(sCmd, false, value);
context.memo('help.' + commands[idx], lang.help[commands[idx]]);
var rng = this.createRange();
if (rng.isCollapsed() && rng.isOnCell()) {
typing.insertTab($editable, rng, options.tabSize);
context.memo('help.tab', lang.help.tab);
this.untab = function () {
var rng = this.createRange();
if (rng.isCollapsed() && rng.isOnCell()) {
context.memo('help.untab', lang.help.untab);
* run given function between beforeCommand and afterCommand
this.wrapCommand = function (fn) {
fn.apply(self, arguments);
this.insertParagraph = this.wrapCommand(function () {
typing.insertParagraph($editable);
context.memo('help.insertParagraph', lang.help.insertParagraph);
this.insertOrderedList = this.wrapCommand(function () {
bullet.insertOrderedList($editable);
context.memo('help.insertOrderedList', lang.help.insertOrderedList);
this.insertUnorderedList = this.wrapCommand(function () {
bullet.insertUnorderedList($editable);
context.memo('help.insertUnorderedList', lang.help.insertUnorderedList);
this.indent = this.wrapCommand(function () {
bullet.indent($editable);
context.memo('help.indent', lang.help.indent);
this.outdent = this.wrapCommand(function () {
bullet.outdent($editable);
context.memo('help.outdent', lang.help.outdent);