define([
'summernote/core/func',
'summernote/core/list',
'summernote/core/agent'
], function (func, list, agent) {
var NBSP_CHAR = String.fromCharCode(160);
var ZERO_WIDTH_NBSP_CHAR = '\ufeff';
/**
* @class core.dom
*
* Dom functions
*
* @singleton
* @alternateClassName dom
*/
var dom = (function () {
/**
* @method isEditable
*
* returns whether node is `note-editable` or not.
*
* @param {Node} node
* @return {Boolean}
*/
var isEditable = function (node) {
return node && $(node).hasClass('note-editable');
};
/**
* @method isControlSizing
*
* returns whether node is `note-control-sizing` or not.
*
* @param {Node} node
* @return {Boolean}
*/
var isControlSizing = function (node) {
return node && $(node).hasClass('note-control-sizing');
};
/**
* @method buildLayoutInfo
*
* build layoutInfo from $editor(.note-editor)
*
* @param {jQuery} $editor
* @return {Object}
* @return {Function} return.editor
* @return {Node} return.dropzone
* @return {Node} return.toolbar
* @return {Node} return.editable
* @return {Node} return.codable
* @return {Node} return.popover
* @return {Node} return.handle
* @return {Node} return.dialog
*/
var buildLayoutInfo = function ($editor) {
var makeFinder;
// air mode
if ($editor.hasClass('note-air-editor')) {
var id = list.last($editor.attr('id').split('-'));
makeFinder = function (sIdPrefix) {
return function () { return $(sIdPrefix + id); };
};
return {
editor: function () { return $editor; },
editable: function () { return $editor; },
popover: makeFinder('#note-popover-'),
handle: makeFinder('#note-handle-'),
dialog: makeFinder('#note-dialog-')
};
// frame mode
} else {
makeFinder = function (sClassName) {
return function () { return $editor.find(sClassName); };
};
return {
editor: function () { return $editor; },
dropzone: makeFinder('.note-dropzone'),
toolbar: makeFinder('.note-toolbar'),
editable: makeFinder('.note-editable'),
codable: makeFinder('.note-codable'),
statusbar: makeFinder('.note-statusbar'),
popover: makeFinder('.note-popover'),
handle: makeFinder('.note-handle'),
dialog: makeFinder('.note-dialog')
};
}
};
/**
* @method makePredByNodeName
*
* returns predicate which judge whether nodeName is same
*
* @param {String} nodeName
* @return {Function}
*/
var makePredByNodeName = function (nodeName) {
nodeName = nodeName.toUpperCase();
return function (node) {
return node && node.nodeName.toUpperCase() === nodeName;
};
};
/**
* @method isText
*
*
*
* @param {Node} node
* @return {Boolean} true if node's type is text(3)
*/
var isText = function (node) {
return node && node.nodeType === 3;
};
/**
* ex) br, col, embed, hr, img, input, ...
* @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
*/
var isVoid = function (node) {
return node && /^BR|^IMG|^HR/.test(node.nodeName.toUpperCase());
};
var isPara = function (node) {
if (isEditable(node)) {
return false;
}
// Chrome(v31.0), FF(v25.0.1) use DIV for paragraph
return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName.toUpperCase());
};
var isLi = makePredByNodeName('LI');
var isPurePara = function (node) {
return isPara(node) && !isLi(node);
};
var isTable = makePredByNodeName('TABLE');
var isInline = function (node) {
return !isBodyContainer(node) &&
!isList(node) &&
!isPara(node) &&
!isTable(node) &&
!isBlockquote(node);
};
var isList = function (node) {
return node && /^UL|^OL/.test(node.nodeName.toUpperCase());
};
var isCell = function (node) {
return node && /^TD|^TH/.test(node.nodeName.toUpperCase());
};
var isBlockquote = makePredByNodeName('BLOCKQUOTE');
var isBodyContainer = function (node) {
return isCell(node) || isBlockquote(node) || isEditable(node);
};
var isAnchor = makePredByNodeName('A');
var isParaInline = function (node) {
return isInline(node) && !!ancestor(node, isPara);
};
var isBodyInline = function (node) {
return isInline(node) && !ancestor(node, isPara);
};
var isBody = makePredByNodeName('BODY');
/**
* returns whether nodeB is closest sibling of nodeA
*
* @param {Node} nodeA
* @param {Node} nodeB
* @return {Boolean}
*/
var isClosestSibling = function (nodeA, nodeB) {
return nodeA.nextSibling === nodeB ||
nodeA.previousSibling === nodeB;
};
/**
* returns array of closest siblings with node
*
* @param {Node} node
* @param {function} [pred] - predicate function
* @return {Node[]}
*/
var withClosestSiblings = function (node, pred) {
pred = pred || func.ok;
var siblings = [];
if (node.previousSibling && pred(node.previousSibling)) {
siblings.push(node.previousSibling);
}
siblings.push(node);
if (node.nextSibling && pred(node.nextSibling)) {
siblings.push(node.nextSibling);
}
return siblings;
};
/**
* blank HTML for cursor position
*/
var blankHTML = agent.isMSIE ? ' ' : '
';
/**
* @method nodeLength
*
* returns #text's text size or element's childNodes size
*
* @param {Node} node
*/
var nodeLength = function (node) {
if (isText(node)) {
return node.nodeValue.length;
}
return node.childNodes.length;
};
/**
* returns whether node is empty or not.
*
* @param {Node} node
* @return {Boolean}
*/
var isEmpty = function (node) {
var len = nodeLength(node);
if (len === 0) {
return true;
} else if (!dom.isText(node) && len === 1 && node.innerHTML === blankHTML) {
// ex)
] var ancestors = listAncestor(point.node, func.eq(root)); if (!ancestors.length) { return null; } else if (ancestors.length === 1) { return splitNode(point, isSkipPaddingBlankHTML); } return ancestors.reduce(function (node, parent) { var clone = insertAfter(parent.cloneNode(false), parent); if (node === point.node) { node = splitNode(point, isSkipPaddingBlankHTML); } appendChildNodes(clone, listNext(node)); if (!isSkipPaddingBlankHTML) { paddingBlankHTML(parent); paddingBlankHTML(clone); } return clone; }); }; /** * split point * * @param {Point} point * @param {Boolean} isInline * @return {Object} */ var splitPoint = function (point, isInline) { // find splitRoot, container // - inline: splitRoot is a child of paragraph // - block: splitRoot is a child of bodyContainer var pred = isInline ? isPara : isBodyContainer; var ancestors = listAncestor(point.node, pred); var topAncestor = list.last(ancestors) || point.node; var splitRoot, container; if (pred(topAncestor)) { splitRoot = ancestors[ancestors.length - 2]; container = topAncestor; } else { splitRoot = topAncestor; container = splitRoot.parentNode; } // split with splitTree var pivot = splitRoot && splitTree(splitRoot, point, isInline); return { rightNode: pivot, container: container }; }; var create = function (nodeName) { return document.createElement(nodeName); }; var createText = function (text) { return document.createTextNode(text); }; /** * @method remove * * remove node, (isRemoveChild: remove child or not) * * @param {Node} node * @param {Boolean} isRemoveChild */ var remove = function (node, isRemoveChild) { if (!node || !node.parentNode) { return; } if (node.removeNode) { return node.removeNode(isRemoveChild); } var parent = node.parentNode; if (!isRemoveChild) { var nodes = []; var i, len; for (i = 0, len = node.childNodes.length; i < len; i++) { nodes.push(node.childNodes[i]); } for (i = 0, len = nodes.length; i < len; i++) { parent.insertBefore(nodes[i], node); } } parent.removeChild(node); }; /** * @method removeWhile * * @param {Node} node * @param {Function} pred */ var removeWhile = function (node, pred) { while (node) { if (isEditable(node) || !pred(node)) { break; } var parent = node.parentNode; remove(node); node = parent; } }; /** * @method replace * * replace node with provided nodeName * * @param {Node} node * @param {String} nodeName * @return {Node} - new node */ var replace = function (node, nodeName) { if (node.nodeName.toUpperCase() === nodeName.toUpperCase()) { return node; } var newNode = create(nodeName); if (node.style.cssText) { newNode.style.cssText = node.style.cssText; } appendChildNodes(newNode, list.from(node.childNodes)); insertAfter(newNode, node); remove(node); return newNode; }; var isTextarea = makePredByNodeName('TEXTAREA'); /** * @method html * * get the HTML contents of node * * @param {jQuery} $node * @param {Boolean} [isNewlineOnBlock] */ var html = function ($node, isNewlineOnBlock) { var markup = isTextarea($node[0]) ? $node.val() : $node.html(); if (isNewlineOnBlock) { var regexTag = /<(\/?)(\b(?!!)[^>\s]*)(.*?)(\s*\/?>)/g; markup = markup.replace(regexTag, function (match, endSlash, name) { name = name.toUpperCase(); var isEndOfInlineContainer = /^DIV|^TD|^TH|^P|^LI|^H[1-7]/.test(name) && !!endSlash; var isBlockNode = /^BLOCKQUOTE|^TABLE|^TBODY|^TR|^HR|^UL|^OL/.test(name); return match + ((isEndOfInlineContainer || isBlockNode) ? '\n' : ''); }); markup = $.trim(markup); } return markup; }; var value = function ($textarea, stripLinebreaks) { var val = $textarea.val(); if (stripLinebreaks) { return val.replace(/[\n\r]/g, ''); } return val; }; return { /** @property {String} NBSP_CHAR */ NBSP_CHAR: NBSP_CHAR, /** @property {String} ZERO_WIDTH_NBSP_CHAR */ ZERO_WIDTH_NBSP_CHAR: ZERO_WIDTH_NBSP_CHAR, /** @property {String} blank */ blank: blankHTML, /** @property {String} emptyPara */ emptyPara: '
' + blankHTML + '
', makePredByNodeName: makePredByNodeName, isEditable: isEditable, isControlSizing: isControlSizing, buildLayoutInfo: buildLayoutInfo, isText: isText, isVoid: isVoid, isPara: isPara, isPurePara: isPurePara, isInline: isInline, isBodyInline: isBodyInline, isBody: isBody, isParaInline: isParaInline, isList: isList, isTable: isTable, isCell: isCell, isBlockquote: isBlockquote, isBodyContainer: isBodyContainer, isAnchor: isAnchor, isDiv: makePredByNodeName('DIV'), isLi: isLi, isBR: makePredByNodeName('BR'), isSpan: makePredByNodeName('SPAN'), isB: makePredByNodeName('B'), isU: makePredByNodeName('U'), isS: makePredByNodeName('S'), isI: makePredByNodeName('I'), isImg: makePredByNodeName('IMG'), isTextarea: isTextarea, isEmpty: isEmpty, isEmptyAnchor: func.and(isAnchor, isEmpty), isClosestSibling: isClosestSibling, withClosestSiblings: withClosestSiblings, nodeLength: nodeLength, isLeftEdgePoint: isLeftEdgePoint, isRightEdgePoint: isRightEdgePoint, isEdgePoint: isEdgePoint, isLeftEdgeOf: isLeftEdgeOf, isRightEdgeOf: isRightEdgeOf, prevPoint: prevPoint, nextPoint: nextPoint, isSamePoint: isSamePoint, isVisiblePoint: isVisiblePoint, prevPointUntil: prevPointUntil, nextPointUntil: nextPointUntil, walkPoint: walkPoint, ancestor: ancestor, singleChildAncestor: singleChildAncestor, listAncestor: listAncestor, lastAncestor: lastAncestor, listNext: listNext, listPrev: listPrev, listDescendant: listDescendant, commonAncestor: commonAncestor, wrap: wrap, insertAfter: insertAfter, appendChildNodes: appendChildNodes, position: position, hasChildren: hasChildren, makeOffsetPath: makeOffsetPath, fromOffsetPath: fromOffsetPath, splitTree: splitTree, splitPoint: splitPoint, create: create, createText: createText, remove: remove, removeWhile: removeWhile, replace: replace, html: html, value: value }; })(); return dom; });